diff --git a/docs/loadbalancer-annotations.md b/docs/loadbalancer-annotations.md index 6c19672..cf657be 100644 --- a/docs/loadbalancer-annotations.md +++ b/docs/loadbalancer-annotations.md @@ -111,6 +111,18 @@ The possible values are `false`, `true` or `*` for all ports or a comma delimite ### `service.beta.kubernetes.io/scw-loadbalancer-type` This is the annotation to set the load balancer offer type. +### `service.beta.kubernetes.io/scw-loadbalancer-connection-rate-limit` +This is the annotation to set the incoming connection rate limit per second. +Set to 0 to disable the rate limit. + +### `service.beta.kubernetes.io/scw-loadbalancer-enable-access-logs` +This is the annotation to enable access logs for the load balancer. +The default value is `false`. The possible values are `false` or `true`. + +### `service.beta.kubernetes.io/scw-loadbalancer-enable-http3` +This is the annotation to enable HTTP/3 protocol for the load balancer. +The default value is `false`. The possible values are `false` or `true`. + ### `service.beta.kubernetes.io/scw-loadbalancer-timeout-client` This is the annotation to set the maximum client connection inactivity time. The default value is `10m`. The duration are go's time.Duration (ex: `1s`, `2m`, `4h`, ...). @@ -127,6 +139,10 @@ The default value is `10m`. The duration are go's time.Duration (ex: `1s`, `2m`, This is the annotation to set the maximum tunnel inactivity time. The default value is `10m`. The duration are go's time.Duration (ex: `1s`, `2m`, `4h`, ...). +### `service.beta.kubernetes.io/scw-loadbalancer-timeout-queue` +This is the annotation to set the maximum time for a request to be left pending in queue when max_connections is reached. +The duration are go's time.Duration (ex: `1s`, `2m`, `4h`, ...). + ### `service.beta.kubernetes.io/scw-loadbalancer-on-marked-down-action` This is the annotation that modifes what occurs when a backend server is marked down. The default value is `on_marked_down_action_none` and the possible values are `on_marked_down_action_none` and `shutdown_sessions`. @@ -146,7 +162,7 @@ NB: forwarding HTTPS traffic with HTTP protocol enabled will work only if using ### `service.beta.kubernetes.io/scw-loadbalancer-http-backend-tls` This is the annotation to enable tls towards the backend when using http forward protocol -The possible values are `false`, `true` or `*` for all ports or a comma delimited list of the service port (for instance `80,443`) +Default to `false`. The possible values are `false`, `true` or `*` for all ports or a comma delimited list of the service port (for instance `80,443`) ### `service.beta.kubernetes.io/scw-loadbalancer-http-backend-tls-skip-verify` This is the annotation to skip tls verification on backends when using http forward protocol with TLS enabled @@ -167,9 +183,15 @@ Expected format: `"Key1=Val1,Key2=Val2"` This is the annotation to activate redispatch on another backend server in case of failure The default value is 0, which disable the redispatch. Only a value of 0 or 1 are allowed. +### `service.beta.kubernetes.io/scw-loadbalancer-max-connections` +This is the annotation to configure the number of connections. + ### `service.beta.kubernetes.io/scw-loadbalancer-max-retries` This is the annotation to configure the number of retry on connection failure -The default value is 2. +The default value is 3. + +### `service.beta.kubernetes.io/scw-loadbalancer-failover-host` +This 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. ### `service.beta.kubernetes.io/scw-loadbalancer-private` This is the annotation to configure the LB to be private or public diff --git a/go.mod b/go.mod index 702fb2d..850e5ae 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,18 @@ module github.com/scaleway/scaleway-cloud-controller-manager -go 1.24.5 +go 1.24.0 + +toolchain go1.24.6 require ( - github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33 - golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b - google.golang.org/protobuf v1.36.6 - k8s.io/api v0.33.2 - k8s.io/apimachinery v0.33.2 - k8s.io/client-go v0.33.2 - k8s.io/cloud-provider v0.33.2 - k8s.io/component-base v0.33.2 + github.com/scaleway/scaleway-sdk-go v1.0.0-beta.34 + golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 + google.golang.org/protobuf v1.36.7 + k8s.io/api v0.33.4 + k8s.io/apimachinery v0.33.4 + k8s.io/client-go v0.33.4 + k8s.io/cloud-provider v0.33.4 + k8s.io/component-base v0.33.4 k8s.io/klog/v2 v2.130.1 k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 ) @@ -26,7 +28,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect @@ -81,10 +83,10 @@ require ( golang.org/x/crypto v0.36.0 // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect - golang.org/x/sync v0.15.0 // indirect + golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/text v0.26.0 // indirect golang.org/x/time v0.9.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect @@ -94,10 +96,10 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiserver v0.33.2 // indirect - k8s.io/component-helpers v0.33.2 // indirect - k8s.io/controller-manager v0.33.2 // indirect - k8s.io/kms v0.33.2 // indirect + k8s.io/apiserver v0.33.4 // indirect + k8s.io/component-helpers v0.33.4 // indirect + k8s.io/controller-manager v0.33.4 // indirect + k8s.io/kms v0.33.4 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect diff --git a/go.sum b/go.sum index e11db0a..1756f07 100644 --- a/go.sum +++ b/go.sum @@ -23,9 +23,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -137,8 +136,8 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33 h1:KhF0WejiUTDbL5X55nXowP7zNopwpowa6qaMAWyIE+0= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33/go.mod h1:792k1RTU+5JeMXm35/e2Wgp71qPH/DmDoZrRc+EFZDk= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.34 h1:48+VFHsyVcAHIN2v1Ao9v1/RkjJS5AwctFucBrfYNIA= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.34/go.mod h1:zFWiHphneiey3s8HOtAEnGrRlWivNaxW5T6d5Xfco7g= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= @@ -215,8 +214,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= -golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE= +golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -230,8 +229,8 @@ golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -242,16 +241,16 @@ golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= -golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -264,8 +263,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= +google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -280,26 +279,26 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= -k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= -k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= -k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.2 h1:KGTRbxn2wJagJowo29kKBp4TchpO1DRO3g+dB/KOJN4= -k8s.io/apiserver v0.33.2/go.mod h1:9qday04wEAMLPWWo9AwqCZSiIn3OYSZacDyu/AcoM/M= -k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= -k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= -k8s.io/cloud-provider v0.33.2 h1:tP/18SbhytAapqg2/tGD5PFUR6VLYra+QfJ7Qn3FN34= -k8s.io/cloud-provider v0.33.2/go.mod h1:yS8ArLLLZV1+Tv6hkSYrZuYEVz+wQgiekUtaqe9Wxao= -k8s.io/component-base v0.33.2 h1:sCCsn9s/dG3ZrQTX/Us0/Sx2R0G5kwa0wbZFYoVp/+0= -k8s.io/component-base v0.33.2/go.mod h1:/41uw9wKzuelhN+u+/C59ixxf4tYQKW7p32ddkYNe2k= -k8s.io/component-helpers v0.33.2 h1:AjCtYzst11NV8ensxV/2LEEXRwctqS7Bs44bje9Qcnw= -k8s.io/component-helpers v0.33.2/go.mod h1:PsPpiCk74n8pGWp1d6kjK/iSKBTyQfIacv02BNkMenU= -k8s.io/controller-manager v0.33.2 h1:HIs8PbdTOaY6wTOvKKLwoAHSO6GeDjmYS0Gjnd6rF+c= -k8s.io/controller-manager v0.33.2/go.mod h1:n8maAdN06E3cD0h5N0wuYBv9Qi9FePl7y6Iz3pfc9PY= +k8s.io/api v0.33.4 h1:oTzrFVNPXBjMu0IlpA2eDDIU49jsuEorGHB4cvKupkk= +k8s.io/api v0.33.4/go.mod h1:VHQZ4cuxQ9sCUMESJV5+Fe8bGnqAARZ08tSTdHWfeAc= +k8s.io/apimachinery v0.33.4 h1:SOf/JW33TP0eppJMkIgQ+L6atlDiP/090oaX0y9pd9s= +k8s.io/apimachinery v0.33.4/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.4 h1:6N0TEVA6kASUS3owYDIFJjUH6lgN8ogQmzZvaFFj1/Y= +k8s.io/apiserver v0.33.4/go.mod h1:8ODgXMnOoSPLMUg1aAzMFx+7wTJM+URil+INjbTZCok= +k8s.io/client-go v0.33.4 h1:TNH+CSu8EmXfitntjUPwaKVPN0AYMbc9F1bBS8/ABpw= +k8s.io/client-go v0.33.4/go.mod h1:LsA0+hBG2DPwovjd931L/AoaezMPX9CmBgyVyBZmbCY= +k8s.io/cloud-provider v0.33.4 h1:et4DyeV0W8W+m2ByS34VVFMg8Aj0sz+UDVwanNkspTo= +k8s.io/cloud-provider v0.33.4/go.mod h1:cAC2s7mGpqVWwUars8TFgnvgXy+trDOF3+WSeKNsy/M= +k8s.io/component-base v0.33.4 h1:Jvb/aw/tl3pfgnJ0E0qPuYLT0NwdYs1VXXYQmSuxJGY= +k8s.io/component-base v0.33.4/go.mod h1:567TeSdixWW2Xb1yYUQ7qk5Docp2kNznKL87eygY8Rc= +k8s.io/component-helpers v0.33.4 h1:DYHQPxWB3XIk7hwAQ4YczUelJ37PcUHfnLeee0qFqV8= +k8s.io/component-helpers v0.33.4/go.mod h1:kRgidIgCKFqOW/wy7D8IL3YOT3iaIRZu6FcTEyRr7WU= +k8s.io/controller-manager v0.33.4 h1:HmlzmmNPu8H+cKEpAIRz0ptqpveKcj7KrCx9G+HXRAg= +k8s.io/controller-manager v0.33.4/go.mod h1:CpO8RarLcs7zh0sE4pqz88quF3xU3Dc4ZDfshnB8hw4= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kms v0.33.2 h1:GFwNXX4CZGQCg9DPOaJi1/+iKidCtB9/OIAGdzRo8FI= -k8s.io/kms v0.33.2/go.mod h1:C1I8mjFFBNzfUZXYt9FZVJ8MJl7ynFbGgZFbBzkBJ3E= +k8s.io/kms v0.33.4 h1:rvsVglcIFa9WeKk5vd3mBufSG4D5dqponz1Jz5d6FXU= +k8s.io/kms v0.33.4/go.mod h1:C1I8mjFFBNzfUZXYt9FZVJ8MJl7ynFbGgZFbBzkBJ3E= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= diff --git a/scaleway/loadbalancers.go b/scaleway/loadbalancers.go index ecad31d..c8a25fe 100644 --- a/scaleway/loadbalancers.go +++ b/scaleway/loadbalancers.go @@ -844,7 +844,6 @@ func (l *loadbalancers) createPrivateServiceStatus(service *v1.Service, lb *scwl status.Ingress = []v1.LoadBalancerIngress{ { Hostname: fmt.Sprintf("%s.%s", lb.ID, pn.Name), - IPMode: ipMode, }, } } else { @@ -879,7 +878,7 @@ func (l *loadbalancers) createPublicServiceStatus(service *v1.Service, lb *scwlb status.Ingress = make([]v1.LoadBalancerIngress, 0) for _, ip := range lb.IP { if getUseHostname(service) { - status.Ingress = append(status.Ingress, v1.LoadBalancerIngress{Hostname: ip.Reverse, IPMode: ipMode}) + status.Ingress = append(status.Ingress, v1.LoadBalancerIngress{Hostname: ip.Reverse}) } else { status.Ingress = append(status.Ingress, v1.LoadBalancerIngress{IP: ip.IPAddress, IPMode: ipMode}) } @@ -1025,11 +1024,32 @@ func servicePortToFrontend(service *v1.Service, loadbalancer *scwlb.LB, port v1. return nil, fmt.Errorf("error getting certificate IDs for loadbalancer %s: %v", loadbalancer.ID, err) } + connectionRateLimit, err := getConnectionRateLimit(service) + if err != nil { + return nil, fmt.Errorf("error getting %s annotation for loadbalancer %s: %v", + serviceAnnotationLoadBalancerConnectionRateLimit, loadbalancer.ID, err) + } + + enableAccessLogs, err := getEnableAccessLogs(service) + if err != nil { + return nil, fmt.Errorf("error getting %s annotation for loadbalancer %s: %v", + serviceAnnotationLoadBalancerEnableAccessLogs, loadbalancer.ID, err) + } + + enableHTTP3, err := getEnableHTTP3(service) + if err != nil { + return nil, fmt.Errorf("error getting %s annotation for loadbalancer %s: %v", + serviceAnnotationLoadBalancerEnableHTTP3, loadbalancer.ID, err) + } + return &scwlb.Frontend{ - Name: fmt.Sprintf("%s_tcp_%d", string(service.UID), port.Port), - InboundPort: port.Port, - TimeoutClient: &timeoutClient, - CertificateIDs: certificateIDs, + Name: fmt.Sprintf("%s_tcp_%d", string(service.UID), port.Port), + InboundPort: port.Port, + TimeoutClient: &timeoutClient, + ConnectionRateLimit: connectionRateLimit, + CertificateIDs: certificateIDs, + EnableAccessLogs: enableAccessLogs, + EnableHTTP3: enableHTTP3, }, nil } @@ -1080,6 +1100,11 @@ func servicePortToBackend(service *v1.Service, loadbalancer *scwlb.LB, port v1.S return nil, err } + timeoutQueue, err := getTimeoutQueue(service) + if err != nil { + return nil, err + } + onMarkedDownAction, err := getOnMarkedDownAction(service) if err != nil { return nil, err @@ -1090,11 +1115,21 @@ func servicePortToBackend(service *v1.Service, loadbalancer *scwlb.LB, port v1.S return nil, err } + maxConnections, err := getMaxConnections(service) + if err != nil { + return nil, err + } + maxRetries, err := getMaxRetries(service) if err != nil { return nil, err } + failoverHost, err := getFailoverHost(service) + if err != nil { + return nil, err + } + healthCheck := &scwlb.HealthCheck{ Port: port.NodePort, } @@ -1186,7 +1221,7 @@ func servicePortToBackend(service *v1.Service, loadbalancer *scwlb.LB, port v1.S Name: fmt.Sprintf("%s_tcp_%d", string(service.UID), port.NodePort), Pool: nodeIPs, ForwardProtocol: protocol, - SslBridging: sslBridging, + SslBridging: &sslBridging, IgnoreSslServerVerify: sslSkipVerify, ForwardPort: port.NodePort, ForwardPortAlgorithm: forwardPortAlgorithm, @@ -1195,10 +1230,13 @@ func servicePortToBackend(service *v1.Service, loadbalancer *scwlb.LB, port v1.S TimeoutServer: &timeoutServer, TimeoutConnect: &timeoutConnect, TimeoutTunnel: &timeoutTunnel, + TimeoutQueue: timeoutQueue, OnMarkedDownAction: onMarkedDownAction, HealthCheck: healthCheck, RedispatchAttemptCount: redispatchAttemptCount, + MaxConnections: maxConnections, MaxRetries: maxRetries, + FailoverHost: failoverHost, } if stickySessions == scwlb.StickySessionsTypeCookie { @@ -1263,6 +1301,21 @@ func frontendEquals(got, want *scwlb.Frontend) bool { return false } + if !uint32PtrEqual(got.ConnectionRateLimit, want.ConnectionRateLimit) { + klog.V(3).Infof("frontend.ConnectionRateLimit: %s - %s", ptrUint32ToString(got.ConnectionRateLimit), ptrUint32ToString(want.ConnectionRateLimit)) + return false + } + + if got.EnableAccessLogs != want.EnableAccessLogs { + klog.V(3).Infof("frontend.EnableAccessLogs: %t - %t", got.EnableAccessLogs, want.EnableAccessLogs) + return false + } + + if got.EnableHTTP3 != want.EnableHTTP3 { + klog.V(3).Infof("frontend.EnableHTTP3: %t - %t", got.EnableHTTP3, want.EnableHTTP3) + return false + } + return true } @@ -1316,6 +1369,10 @@ func backendEquals(got, want *scwlb.Backend) bool { klog.V(3).Infof("backend.TimeoutTunnel: %s - %s", got.TimeoutTunnel, want.TimeoutTunnel) return false } + if !durationPtrEqual(got.TimeoutQueue.ToTimeDuration(), want.TimeoutQueue.ToTimeDuration()) { + klog.V(3).Infof("backend.TimeoutQueue: %s - %s", ptrScwDurationToString(got.TimeoutQueue), ptrScwDurationToString(want.TimeoutQueue)) + return false + } if got.OnMarkedDownAction != want.OnMarkedDownAction { klog.V(3).Infof("backend.OnMarkedDownAction: %s - %s", got.OnMarkedDownAction, want.OnMarkedDownAction) return false @@ -1324,6 +1381,10 @@ func backendEquals(got, want *scwlb.Backend) bool { klog.V(3).Infof("backend.RedispatchAttemptCount: %s - %s", ptrInt32ToString(got.RedispatchAttemptCount), ptrInt32ToString(want.RedispatchAttemptCount)) return false } + if !int32PtrEqual(got.MaxConnections, want.MaxConnections) { + klog.V(3).Infof("backend.MaxConnections: %s - %s", ptrInt32ToString(got.MaxConnections), ptrInt32ToString(want.MaxConnections)) + return false + } if !int32PtrEqual(got.MaxRetries, want.MaxRetries) { klog.V(3).Infof("backend.MaxRetries: %s - %s", ptrInt32ToString(got.MaxRetries), ptrInt32ToString(want.MaxRetries)) return false @@ -1333,6 +1394,11 @@ func backendEquals(got, want *scwlb.Backend) bool { return false } + if !ptrStringEqual(got.FailoverHost, want.FailoverHost) { + klog.V(3).Infof("backend.FailoverHost: %s - %s", ptrStringToString(got.FailoverHost), ptrStringToString(want.FailoverHost)) + return false + } + if !reflect.DeepEqual(got.HealthCheck, want.HealthCheck) { klog.V(3).Infof("backend.HealthCheck: %v - %v", got.HealthCheck, want.HealthCheck) return false @@ -1499,19 +1565,23 @@ func (l *loadbalancers) createBackend(service *v1.Service, loadbalancer *scwlb.L Name: backend.Name, ForwardProtocol: backend.ForwardProtocol, SslBridging: backend.SslBridging, + IgnoreSslServerVerify: backend.IgnoreSslServerVerify, ForwardPort: backend.ForwardPort, ForwardPortAlgorithm: backend.ForwardPortAlgorithm, StickySessions: backend.StickySessions, StickySessionsCookieName: backend.StickySessionsCookieName, HealthCheck: backend.HealthCheck, ServerIP: backend.Pool, + ProxyProtocol: backend.ProxyProtocol, TimeoutServer: backend.TimeoutServer, TimeoutConnect: backend.TimeoutConnect, TimeoutTunnel: backend.TimeoutTunnel, + TimeoutQueue: backend.TimeoutQueue, OnMarkedDownAction: backend.OnMarkedDownAction, - ProxyProtocol: backend.ProxyProtocol, RedispatchAttemptCount: backend.RedispatchAttemptCount, + MaxConnections: backend.MaxConnections, MaxRetries: backend.MaxRetries, + FailoverHost: backend.FailoverHost, }) if err != nil { return nil, err @@ -1528,17 +1598,21 @@ func (l *loadbalancers) updateBackend(service *v1.Service, loadbalancer *scwlb.L Name: backend.Name, ForwardProtocol: backend.ForwardProtocol, SslBridging: backend.SslBridging, + IgnoreSslServerVerify: backend.IgnoreSslServerVerify, ForwardPort: backend.ForwardPort, ForwardPortAlgorithm: backend.ForwardPortAlgorithm, StickySessions: backend.StickySessions, StickySessionsCookieName: backend.StickySessionsCookieName, + ProxyProtocol: backend.ProxyProtocol, TimeoutServer: backend.TimeoutServer, TimeoutConnect: backend.TimeoutConnect, TimeoutTunnel: backend.TimeoutTunnel, + TimeoutQueue: backend.TimeoutQueue, OnMarkedDownAction: backend.OnMarkedDownAction, - ProxyProtocol: backend.ProxyProtocol, RedispatchAttemptCount: backend.RedispatchAttemptCount, + MaxConnections: backend.MaxConnections, MaxRetries: backend.MaxRetries, + FailoverHost: backend.FailoverHost, }) if err != nil { return nil, err @@ -1570,14 +1644,16 @@ func (l *loadbalancers) updateBackend(service *v1.Service, loadbalancer *scwlb.L // createFrontend creates a frontend on the load balancer func (l *loadbalancers) createFrontend(service *v1.Service, loadbalancer *scwlb.LB, frontend *scwlb.Frontend, backend *scwlb.Backend) (*scwlb.Frontend, error) { f, err := l.api.CreateFrontend(&scwlb.ZonedAPICreateFrontendRequest{ - Zone: loadbalancer.Zone, - LBID: loadbalancer.ID, - Name: frontend.Name, - InboundPort: frontend.InboundPort, - BackendID: backend.ID, - TimeoutClient: frontend.TimeoutClient, - CertificateIDs: &frontend.CertificateIDs, - EnableHTTP3: frontend.EnableHTTP3, + Zone: loadbalancer.Zone, + LBID: loadbalancer.ID, + Name: frontend.Name, + InboundPort: frontend.InboundPort, + BackendID: backend.ID, + TimeoutClient: frontend.TimeoutClient, + CertificateIDs: &frontend.CertificateIDs, + ConnectionRateLimit: frontend.ConnectionRateLimit, + EnableAccessLogs: frontend.EnableAccessLogs, + EnableHTTP3: frontend.EnableHTTP3, }) return f, err @@ -1586,14 +1662,16 @@ func (l *loadbalancers) createFrontend(service *v1.Service, loadbalancer *scwlb. // updateFrontend updates a frontend on the load balancer func (l *loadbalancers) updateFrontend(service *v1.Service, loadbalancer *scwlb.LB, frontend *scwlb.Frontend, backend *scwlb.Backend) (*scwlb.Frontend, error) { f, err := l.api.UpdateFrontend(&scwlb.ZonedAPIUpdateFrontendRequest{ - Zone: loadbalancer.Zone, - FrontendID: frontend.ID, - Name: frontend.Name, - InboundPort: frontend.InboundPort, - BackendID: backend.ID, - TimeoutClient: frontend.TimeoutClient, - CertificateIDs: &frontend.CertificateIDs, - EnableHTTP3: frontend.EnableHTTP3, + Zone: loadbalancer.Zone, + FrontendID: frontend.ID, + Name: frontend.Name, + InboundPort: frontend.InboundPort, + BackendID: backend.ID, + TimeoutClient: frontend.TimeoutClient, + CertificateIDs: &frontend.CertificateIDs, + ConnectionRateLimit: frontend.ConnectionRateLimit, + EnableAccessLogs: &frontend.EnableAccessLogs, + EnableHTTP3: frontend.EnableHTTP3, }) return f, err @@ -1606,6 +1684,17 @@ func stringArrayEqual(got, want []string) bool { return reflect.DeepEqual(got, want) } +// ptrStringEqual returns true if both strings are equal +func ptrStringEqual(got, want *string) bool { + if got == nil && want == nil { + return true + } + if got == nil || want == nil { + return false + } + return *got == *want +} + // stringPtrArrayEqual returns true if both arrays contains the exact same elements regardless of the order func stringPtrArrayEqual(got, want []*string) bool { slices.SortStableFunc(got, func(a, b *string) int { return strings.Compare(*a, *b) }) @@ -1646,6 +1735,17 @@ func int32PtrEqual(got, want *int32) bool { return *got == *want } +// uint32PtrEqual returns true if both integers are equal +func uint32PtrEqual(got, want *uint32) bool { + if got == nil && want == nil { + return true + } + if got == nil || want == nil { + return false + } + return *got == *want +} + // chunkArray takes an array and split it in chunks of a given size func chunkArray(array []string, maxChunkSize int) [][]string { result := [][]string{} @@ -1729,6 +1829,13 @@ func ptrInt32ToString(i *int32) string { return fmt.Sprintf("%d", *i) } +func ptrUint32ToString(i *uint32) string { + if i == nil { + return "" + } + return fmt.Sprintf("%d", *i) +} + func ptrBoolToString(b *bool) string { if b == nil { return "" @@ -1797,3 +1904,17 @@ func nodesInitialized(nodes []*v1.Node) error { return nil } + +func ptrStringToString(s *string) string { + if s == nil { + return "" + } + return *s +} + +func ptrScwDurationToString(i *scw.Duration) string { + if i == nil { + return "" + } + return i.ToTimeDuration().String() +} diff --git a/scaleway/loadbalancers_annotations.go b/scaleway/loadbalancers_annotations.go index 898d672..1d03849 100644 --- a/scaleway/loadbalancers_annotations.go +++ b/scaleway/loadbalancers_annotations.go @@ -108,6 +108,18 @@ const ( // serviceAnnotationLoadBalancerZone is the zone to create the load balancer serviceAnnotationLoadBalancerZone = "service.beta.kubernetes.io/scw-loadbalancer-zone" + // serviceAnnotationLoadBalancerConnectionRateLimit is the annotation to set the incoming connection rate limit per second. + // Set to 0 to disable the rate limit + serviceAnnotationLoadBalancerConnectionRateLimit = "service.beta.kubernetes.io/scw-loadbalancer-connection-rate-limit" + + // serviceAnnotationLoadBalancerEnableAccessLogs is the annotation to enable access logs for the load balancer. + // The default value is "false". The possible values are "false" or "true". + serviceAnnotationLoadBalancerEnableAccessLogs = "service.beta.kubernetes.io/scw-loadbalancer-enable-access-logs" + + // serviceAnnotationLoadBalancerEnableHTTP3 is the annotation to enable HTTP/3 protocol for the load balancer. + // The default value is "false". The possible values are "false" or "true". + serviceAnnotationLoadBalancerEnableHTTP3 = "service.beta.kubernetes.io/scw-loadbalancer-enable-http3" + // serviceAnnotationLoadBalancerTimeoutClient is the maximum client connection inactivity time // The default value is "10m". The duration are go's time.Duration (ex: "1s", "2m", "4h", ...) serviceAnnotationLoadBalancerTimeoutClient = "service.beta.kubernetes.io/scw-loadbalancer-timeout-client" @@ -124,6 +136,10 @@ const ( // The default value is "10m". The duration are go's time.Duration (ex: "1s", "2m", "4h", ...) serviceAnnotationLoadBalancerTimeoutTunnel = "service.beta.kubernetes.io/scw-loadbalancer-timeout-tunnel" + // serviceAnnotationLoadBalancerTimeoutQueue is the maximum time for a request to be left pending in queue when max_connections is reached. + // The duration are go's time.Duration (ex: "1s", "2m", "4h", ...) + serviceAnnotationLoadBalancerTimeoutQueue = "service.beta.kubernetes.io/scw-loadbalancer-timeout-queue" + // serviceAnnotationLoadBalancerOnMarkedDownAction is the annotation that modifes what occurs when a backend server is marked down // The default value is "on_marked_down_action_none" and the possible values are "on_marked_down_action_none" and "shutdown_sessions" serviceAnnotationLoadBalancerOnMarkedDownAction = "service.beta.kubernetes.io/scw-loadbalancer-on-marked-down-action" @@ -141,8 +157,8 @@ const ( // (for instance "80,443") serviceAnnotationLoadBalancerProtocolHTTP = "service.beta.kubernetes.io/scw-loadbalancer-protocol-http" - // serviceAnnotationLoadBalancerHTTPBackendTLS is the annotation to enable tls towards the backend when using http forward protocol - // The possible values are "false", "true" or "*" for all ports or a comma delimited list of the service port + // serviceAnnotationLoadBalancerHTTPBackendTLS is the annotation to enable tls towards the backend when using http forward protocol. + // Default to "false". The possible values are "false", "true" or "*" for all ports or a comma delimited list of the service port // (for instance "80,443") serviceAnnotationLoadBalancerHTTPBackendTLS = "service.beta.kubernetes.io/scw-loadbalancer-http-backend-tls" @@ -168,10 +184,16 @@ const ( // The default value is "0", which disable the redispatch serviceAnnotationLoadBalancerRedispatchAttemptCount = "service.beta.kubernetes.io/scw-loadbalancer-redispatch-attempt-count" + // serviceAnnotationLoadBalancerMaxConnections is the annotation to configure the number of connections + serviceAnnotationLoadBalancerMaxConnections = "service.beta.kubernetes.io/scw-loadbalancer-max-connections" + // serviceAnnotationLoadBalancerMaxRetries is the annotation to configure the number of retry on connection failure // The default value is 3. serviceAnnotationLoadBalancerMaxRetries = "service.beta.kubernetes.io/scw-loadbalancer-max-retries" + // 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. + serviceAnnotationLoadBalancerFailoverHost = "service.beta.kubernetes.io/scw-loadbalancer-failover-host" + // serviceAnnotationLoadBalancerPrivate is the annotation to configure the LB to be private or public // The LB will be public if unset or false. serviceAnnotationLoadBalancerPrivate = "service.beta.kubernetes.io/scw-loadbalancer-private" @@ -422,6 +444,21 @@ func getTimeoutTunnel(service *v1.Service) (time.Duration, error) { return timeoutTunnelDuration, nil } +func getTimeoutQueue(service *v1.Service) (*scw.Duration, error) { + timeoutQueue, ok := service.Annotations[serviceAnnotationLoadBalancerTimeoutQueue] + if !ok { + return nil, nil + } + + timeoutQueueDuration, err := time.ParseDuration(timeoutQueue) + if err != nil { + klog.Errorf("invalid value for annotation %s", serviceAnnotationLoadBalancerTimeoutQueue) + return nil, errLoadBalancerInvalidAnnotation + } + + return scw.NewDurationFromTimeDuration(timeoutQueueDuration), nil +} + func getOnMarkedDownAction(service *v1.Service) (scwlb.OnMarkedDownAction, error) { onMarkedDownAction, ok := service.Annotations[serviceAnnotationLoadBalancerOnMarkedDownAction] if !ok { @@ -454,6 +491,21 @@ func getRedisatchAttemptCount(service *v1.Service) (*int32, error) { return &redispatchAttemptCountInt32, nil } +func getMaxConnections(service *v1.Service) (*int32, error) { + maxConnectionsCount, ok := service.Annotations[serviceAnnotationLoadBalancerMaxConnections] + if !ok { + return nil, nil + } + maxConnectionsCountInt, err := strconv.Atoi(maxConnectionsCount) + if err != nil { + klog.Errorf("invalid value for annotation %s", serviceAnnotationLoadBalancerMaxConnections) + return nil, errLoadBalancerInvalidAnnotation + + } + maxConnectionsCountInt32 := int32(maxConnectionsCountInt) + return &maxConnectionsCountInt32, nil +} + func getMaxRetries(service *v1.Service) (*int32, error) { maxRetriesCount, ok := service.Annotations[serviceAnnotationLoadBalancerMaxRetries] if !ok { @@ -470,6 +522,14 @@ func getMaxRetries(service *v1.Service) (*int32, error) { return &maxRetriesCountInt32, nil } +func getFailoverHost(service *v1.Service) (*string, error) { + failoverHost, ok := service.Annotations[serviceAnnotationLoadBalancerFailoverHost] + if !ok { + return nil, nil + } + return &failoverHost, nil +} + func getHealthCheckDelay(service *v1.Service) (time.Duration, error) { healthCheckDelay, ok := service.Annotations[serviceAnnotationLoadBalancerHealthCheckDelay] if !ok { @@ -599,10 +659,10 @@ func getForwardProtocol(service *v1.Service, nodePort int32) (scwlb.Protocol, er return scwlb.ProtocolTCP, nil } -func getSSLBridging(service *v1.Service, nodePort int32) (*bool, error) { +func getSSLBridging(service *v1.Service, nodePort int32) (bool, error) { tlsEnabled, found := service.Annotations[serviceAnnotationLoadBalancerHTTPBackendTLS] if !found { - return nil, nil + return false, nil } var svcPort int32 = -1 @@ -613,16 +673,16 @@ func getSSLBridging(service *v1.Service, nodePort int32) (*bool, error) { } if svcPort == -1 { klog.Errorf("no valid port found") - return nil, errLoadBalancerInvalidAnnotation + return false, errLoadBalancerInvalidAnnotation } isTLSEnabled, err := isPortInRange(tlsEnabled, svcPort) if err != nil { klog.Errorf("unable to check if port %d is in range %s", svcPort, tlsEnabled) - return nil, err + return false, err } - return scw.BoolPtr(isTLSEnabled), nil + return isTLSEnabled, nil } func getSSLBridgingSkipVerify(service *v1.Service, nodePort int32) (*bool, error) { @@ -946,3 +1006,33 @@ func getKeyValueFromAnnotation(annotation string) map[string]string { return additionalTags } + +func getConnectionRateLimit(service *v1.Service) (*uint32, error) { + connectionRateLimit, ok := service.Annotations[serviceAnnotationLoadBalancerConnectionRateLimit] + if !ok { + return nil, nil + } + connectionRateLimitInt, err := strconv.Atoi(connectionRateLimit) + if err != nil { + klog.Errorf("invalid value for annotation %s", serviceAnnotationLoadBalancerConnectionRateLimit) + return nil, errLoadBalancerInvalidAnnotation + } + connectionRateLimitUint32 := uint32(connectionRateLimitInt) + return &connectionRateLimitUint32, nil +} + +func getEnableAccessLogs(service *v1.Service) (bool, error) { + enableAccessLogs, ok := service.Annotations[serviceAnnotationLoadBalancerEnableAccessLogs] + if !ok { + return false, nil + } + return strconv.ParseBool(enableAccessLogs) +} + +func getEnableHTTP3(service *v1.Service) (bool, error) { + enableHTTP3, ok := service.Annotations[serviceAnnotationLoadBalancerEnableHTTP3] + if !ok { + return false, nil + } + return strconv.ParseBool(enableHTTP3) +} diff --git a/scaleway/loadbalancers_test.go b/scaleway/loadbalancers_test.go index 660700e..7110673 100644 --- a/scaleway/loadbalancers_test.go +++ b/scaleway/loadbalancers_test.go @@ -511,6 +511,63 @@ func TestServicePortToFrontend(t *testing.T) { }, false, }, + { + "with connection rate limit", + &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + UID: "uid", + Annotations: map[string]string{ + "service.beta.kubernetes.io/scw-loadbalancer-connection-rate-limit": "100", + }, + }, + }, + &scwlb.Frontend{ + Name: "uid_tcp_1234", + InboundPort: 1234, + TimeoutClient: &defaultTimeout, + CertificateIDs: []string{}, + ConnectionRateLimit: func() *uint32 { v := uint32(100); return &v }(), + }, + false, + }, + { + "with enable access logs", + &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + UID: "uid", + Annotations: map[string]string{ + "service.beta.kubernetes.io/scw-loadbalancer-enable-access-logs": "true", + }, + }, + }, + &scwlb.Frontend{ + Name: "uid_tcp_1234", + InboundPort: 1234, + TimeoutClient: &defaultTimeout, + CertificateIDs: []string{}, + EnableAccessLogs: true, + }, + false, + }, + { + "with enable http3", + &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + UID: "uid", + Annotations: map[string]string{ + "service.beta.kubernetes.io/scw-loadbalancer-enable-http3": "true", + }, + }, + }, + &scwlb.Frontend{ + Name: "uid_tcp_1234", + InboundPort: 1234, + TimeoutClient: &defaultTimeout, + CertificateIDs: []string{}, + EnableHTTP3: true, + }, + false, + }, { "with invalid timeout", &v1.Service{ @@ -537,6 +594,45 @@ func TestServicePortToFrontend(t *testing.T) { nil, true, }, + { + "with invalid connection rate limit", + &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + UID: "uid", + Annotations: map[string]string{ + "service.beta.kubernetes.io/scw-loadbalancer-connection-rate-limit": "not-a-number", + }, + }, + }, + nil, + true, + }, + { + "with invalid enable access logs", + &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + UID: "uid", + Annotations: map[string]string{ + "service.beta.kubernetes.io/scw-loadbalancer-enable-access-logs": "not-a-bool", + }, + }, + }, + nil, + true, + }, + { + "with invalid enable http3", + &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + UID: "uid", + Annotations: map[string]string{ + "service.beta.kubernetes.io/scw-loadbalancer-enable-http3": "invalid-bool", + }, + }, + }, + nil, + true, + }, } for _, tt := range matrix { @@ -729,13 +825,165 @@ func TestFrontendEquals(t *testing.T) { }, false, }, + { + "with same connection rate limit", + &scwlb.Frontend{ + Name: "uid_tcp_1234", + InboundPort: 1234, + TimeoutClient: &defaultTimeout, + CertificateIDs: []string{}, + ConnectionRateLimit: func() *uint32 { v := uint32(100); return &v }(), + }, + &scwlb.Frontend{ + ID: "uid-1", + Name: "uid_tcp_1234", + InboundPort: 1234, + TimeoutClient: &defaultTimeout, + CertificateIDs: []string{}, + ConnectionRateLimit: func() *uint32 { v := uint32(100); return &v }(), + }, + true, + }, + { + "with different connection rate limit", + &scwlb.Frontend{ + Name: "uid_tcp_1234", + InboundPort: 1234, + TimeoutClient: &defaultTimeout, + CertificateIDs: []string{}, + ConnectionRateLimit: func() *uint32 { v := uint32(100); return &v }(), + }, + &scwlb.Frontend{ + ID: "uid-1", + Name: "uid_tcp_1234", + InboundPort: 1234, + TimeoutClient: &defaultTimeout, + CertificateIDs: []string{}, + ConnectionRateLimit: func() *uint32 { v := uint32(200); return &v }(), + }, + false, + }, + { + "with nil connection rate limit", + &scwlb.Frontend{ + Name: "uid_tcp_1234", + InboundPort: 1234, + TimeoutClient: &defaultTimeout, + CertificateIDs: []string{}, + ConnectionRateLimit: nil, + }, + &scwlb.Frontend{ + ID: "uid-1", + Name: "uid_tcp_1234", + InboundPort: 1234, + TimeoutClient: &defaultTimeout, + CertificateIDs: []string{}, + ConnectionRateLimit: nil, + }, + true, + }, + { + "with one nil connection rate limit", + &scwlb.Frontend{ + Name: "uid_tcp_1234", + InboundPort: 1234, + TimeoutClient: &defaultTimeout, + CertificateIDs: []string{}, + ConnectionRateLimit: func() *uint32 { v := uint32(100); return &v }(), + }, + &scwlb.Frontend{ + ID: "uid-1", + Name: "uid_tcp_1234", + InboundPort: 1234, + TimeoutClient: &defaultTimeout, + CertificateIDs: []string{}, + ConnectionRateLimit: nil, + }, + false, + }, + { + "with same enable access logs", + &scwlb.Frontend{ + Name: "uid_tcp_1234", + InboundPort: 1234, + TimeoutClient: &defaultTimeout, + CertificateIDs: []string{}, + EnableAccessLogs: true, + }, + &scwlb.Frontend{ + ID: "uid-1", + Name: "uid_tcp_1234", + InboundPort: 1234, + TimeoutClient: &defaultTimeout, + CertificateIDs: []string{}, + EnableAccessLogs: true, + }, + true, + }, + { + "with different enable access logs", + &scwlb.Frontend{ + Name: "uid_tcp_1234", + InboundPort: 1234, + TimeoutClient: &defaultTimeout, + CertificateIDs: []string{}, + EnableAccessLogs: true, + }, + &scwlb.Frontend{ + ID: "uid-1", + Name: "uid_tcp_1234", + InboundPort: 1234, + TimeoutClient: &defaultTimeout, + CertificateIDs: []string{}, + EnableAccessLogs: false, + }, + false, + }, + { + "with same enable http3", + &scwlb.Frontend{ + Name: "uid_tcp_1234", + InboundPort: 1234, + TimeoutClient: &defaultTimeout, + CertificateIDs: []string{}, + EnableHTTP3: true, + }, + &scwlb.Frontend{ + ID: "uid-1", + Name: "uid_tcp_1234", + InboundPort: 1234, + TimeoutClient: &defaultTimeout, + CertificateIDs: []string{}, + EnableHTTP3: true, + }, + true, + }, + { + "with different enable http3", + &scwlb.Frontend{ + Name: "uid_tcp_1234", + InboundPort: 1234, + TimeoutClient: &defaultTimeout, + CertificateIDs: []string{}, + EnableHTTP3: false, + }, + &scwlb.Frontend{ + ID: "uid-1", + Name: "uid_tcp_1234", + InboundPort: 1234, + TimeoutClient: &defaultTimeout, + CertificateIDs: []string{}, + EnableHTTP3: true, + }, + false, + }, } for _, tt := range matrix { t.Run(tt.name, func(t *testing.T) { got := frontendEquals(tt.a, tt.b) if got != tt.want { - t.Errorf("want: %v, got: %v", got, tt.want) + t.Errorf("want: %v, got: %v", tt.want, got) } }) } @@ -747,8 +995,13 @@ func TestBackendEquals(t *testing.T) { defaultTimeoutServer, _ := time.ParseDuration("3s") defaultTimeoutConnect, _ := time.ParseDuration("4s") defaultTimeoutTunnel, _ := time.ParseDuration("5s") + defaultTimeoutQueueTime, _ := time.ParseDuration("5s") + defaultTimeoutQueue := scw.NewDurationFromTimeDuration(defaultTimeoutQueueTime) otherDuration, _ := time.ParseDuration("50m") + defaultString := "default" + otherString := "other" boolTrue := true + boolFalse := false var int0 int32 = 0 var int1 int32 = 1 var intOther int32 = 5 @@ -777,13 +1030,18 @@ func TestBackendEquals(t *testing.T) { }, Pool: []string{"1.2.3.4", "2.3.4.5"}, SendProxyV2: &boolTrue, + SslBridging: &boolFalse, + IgnoreSslServerVerify: &boolFalse, TimeoutServer: &defaultTimeoutServer, TimeoutConnect: &defaultTimeoutConnect, TimeoutTunnel: &defaultTimeoutTunnel, + TimeoutQueue: defaultTimeoutQueue, OnMarkedDownAction: "action", ProxyProtocol: "proxy", RedispatchAttemptCount: &int0, + MaxConnections: &int1, MaxRetries: &int1, + FailoverHost: &defaultString, } matrix := []struct { @@ -854,6 +1112,15 @@ func TestBackendEquals(t *testing.T) { want bool }{"with a different ForwardProtocol", reference, diff, false}) + diff = deepCloneBackend(reference) + diff.MaxConnections = &intOther + matrix = append(matrix, struct { + Name string + a *scwlb.Backend + b *scwlb.Backend + want bool + }{"with a different MaxConnections", reference, diff, false}) + diff = deepCloneBackend(reference) diff.MaxRetries = &intOther matrix = append(matrix, struct { @@ -944,6 +1211,24 @@ func TestBackendEquals(t *testing.T) { want bool }{"with a different TimeoutTunnel", reference, diff, false}) + diff = deepCloneBackend(reference) + diff.TimeoutQueue = scw.NewDurationFromTimeDuration(otherDuration) + matrix = append(matrix, struct { + Name string + a *scwlb.Backend + b *scwlb.Backend + want bool + }{"with a different TimeoutQueue", reference, diff, false}) + + diff = deepCloneBackend(reference) + diff.FailoverHost = &otherString + matrix = append(matrix, struct { + Name string + a *scwlb.Backend + b *scwlb.Backend + want bool + }{"with a different FailoverHost", reference, diff, false}) + httpRef := deepCloneBackend(reference) httpRef.HealthCheck.TCPConfig = nil httpRef.HealthCheck.HTTPConfig = &scwlb.HealthCheckHTTPConfig{ @@ -966,13 +1251,13 @@ func TestBackendEquals(t *testing.T) { a *scwlb.Backend b *scwlb.Backend want bool - }{"with same HTTP healthchecks", httpRef, httpDiff, false}) + }{"with different HTTP healthchecks", httpRef, httpDiff, false}) for _, tt := range matrix { t.Run(tt.Name, func(t *testing.T) { got := backendEquals(tt.a, tt.b) if got != tt.want { - t.Errorf("want: %v, got: %v", got, tt.want) + t.Errorf("want: %v, got: %v", tt.want, got) } }) } @@ -999,7 +1284,7 @@ func TestStringArrayEqual(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got := stringArrayEqual(tt.a, tt.b) if got != tt.want { - t.Errorf("want: %v, got: %v", got, tt.want) + t.Errorf("want: %v, got: %v", tt.want, got) } }) } @@ -1025,7 +1310,7 @@ func TestStringPtrArrayEqual(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got := stringPtrArrayEqual(tt.a, tt.b) if got != tt.want { - t.Errorf("want: %v, got: %v", got, tt.want) + t.Errorf("want: %v, got: %v", tt.want, got) } }) } @@ -1053,7 +1338,7 @@ func TestDurationPtrEqual(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got := durationPtrEqual(tt.a, tt.b) if got != tt.want { - t.Errorf("want: %v, got: %v", got, tt.want) + t.Errorf("want: %v, got: %v", tt.want, got) } }) } @@ -1081,7 +1366,7 @@ func TestScwDurationPtrEqual(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got := scwDurationPtrEqual(tt.a, tt.b) if got != tt.want { - t.Errorf("want: %v, got: %v", got, tt.want) + t.Errorf("want: %v, got: %v", tt.want, got) } }) } @@ -1109,7 +1394,88 @@ func TestInt32PtrEqual(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got := int32PtrEqual(tt.a, tt.b) if got != tt.want { - t.Errorf("want: %v, got: %v", got, tt.want) + t.Errorf("want: %v, got: %v", tt.want, got) + } + }) + } +} + +func TestUint32PtrEqual(t *testing.T) { + var uint1 uint32 = 1 + var otherUint1 uint32 = 1 + var uint2 uint32 = 2 + + matrix := []struct { + name string + a *uint32 + b *uint32 + want bool + }{ + {"same", &uint1, &uint1, true}, + {"same with different ptr", &uint1, &otherUint1, true}, + {"with first nil", &uint1, nil, false}, + {"with last nil", nil, &uint1, false}, + {"with both nil", nil, nil, true}, + {"with different values", &uint1, &uint2, false}, + } + + for _, tt := range matrix { + t.Run(tt.name, func(t *testing.T) { + got := uint32PtrEqual(tt.a, tt.b) + if got != tt.want { + t.Errorf("want: %v, got: %v", tt.want, got) + } + }) + } +} + +func TestPtrUint32ToString(t *testing.T) { + var uint1 uint32 = 42 + var uint2 uint32 = 0 + + matrix := []struct { + name string + a *uint32 + want string + }{ + {"with value", &uint1, "42"}, + {"with zero", &uint2, "0"}, + {"with nil", nil, ""}, + } + + for _, tt := range matrix { + t.Run(tt.name, func(t *testing.T) { + got := ptrUint32ToString(tt.a) + if got != tt.want { + t.Errorf("want: %v, got: %v", tt.want, got) + } + }) + } +} +func TestStrPtrEqual(t *testing.T) { + var str1 string = "test" + var otherStr1 string = "test" + var str2 string = "test2" + + matrix := []struct { + name string + a *string + b *string + want bool + }{ + {"same", &str1, &str1, true}, + {"same with different ptr", &str1, &otherStr1, true}, + {"with first nil", &str1, nil, false}, + {"with last nil", nil, &str1, false}, + {"with both nil", nil, nil, true}, + {"with different values", &str1, &str2, false}, + } + + for _, tt := range matrix { + t.Run(tt.name, func(t *testing.T) { + got := ptrStringEqual(tt.a, tt.b) + if got != tt.want { + t.Errorf("want: %v, got: %v", tt.want, got) } }) } @@ -1138,7 +1504,7 @@ func TestChunkArray(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got := chunkArray(tt.array, 3) if !reflect.DeepEqual(got, tt.want) { - t.Errorf("want: %v, got: %v", got, tt.want) + t.Errorf("want: %v, got: %v", tt.want, got) } }) } @@ -1158,7 +1524,7 @@ func TestMakeACLPrefix(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got := makeACLPrefix(tt.frontend) if got != tt.want { - t.Errorf("want: %v, got: %v", got, tt.want) + t.Errorf("want: %v, got: %v", tt.want, got) } }) }