diff --git a/docs/loadbalancer-annotations.md b/docs/loadbalancer-annotations.md index d14e721..5d5e76c 100644 --- a/docs/loadbalancer-annotations.md +++ b/docs/loadbalancer-annotations.md @@ -67,7 +67,7 @@ The default value is `5`. ### `service.beta.kubernetes.io/scw-loadbalancer-health-check-http-uri` This is the annotation to set the URI that is used by the `http` health check. -It is possible to set the uri per port, like `80:/;443,8443:/healthz`. +It is possible to set the uri per port, like `80:/;443,8443:mydomain.tld/healthz`. NB: Required when setting service.beta.kubernetes.io/scw-loadbalancer-health-check-type to `http` or `https`. ### `service.beta.kubernetes.io/scw-loadbalancer-health-check-http-method` diff --git a/scaleway/loadbalancers.go b/scaleway/loadbalancers.go index b6e7e72..e8a3ed7 100644 --- a/scaleway/loadbalancers.go +++ b/scaleway/loadbalancers.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "net" + "net/url" "os" "reflect" "strconv" @@ -79,7 +80,7 @@ const ( serviceAnnotationLoadBalancerHealthCheckMaxRetries = "service.beta.kubernetes.io/scw-loadbalancer-health-check-max-retries" // serviceAnnotationLoadBalancerHealthCheckHTTPURI is the URI that is used by the "http" health check - // It is possible to set the uri per port, like "80:/;443,8443:/healthz" + // It is possible to set the uri per port, like "80:/;443,8443:mydomain.tld/healthz" // NB: Required when setting service.beta.kubernetes.io/scw-loadbalancer-health-check-type to "http" or "https" serviceAnnotationLoadBalancerHealthCheckHTTPURI = "service.beta.kubernetes.io/scw-loadbalancer-health-check-http-uri" @@ -1510,19 +1511,29 @@ func getHTTPHealthCheck(service *v1.Service, nodePort int32) (*scwlb.HealthCheck if err != nil { return nil, err } - uri, err := getHTTPHealthCheckURI(service, nodePort) + + uriStr, err := getHTTPHealthCheckURI(service, nodePort) + if err != nil { + return nil, err + } + uri, err := url.Parse(fmt.Sprintf("http://%s", uriStr)) if err != nil { return nil, err } + if uri.Path == "" { + uri.Path = "/" + } + method, err := getHTTPHealthCheckMethod(service, nodePort) if err != nil { return nil, err } return &scwlb.HealthCheckHTTPConfig{ - Method: method, - Code: &code, - URI: uri, + Method: method, + Code: &code, + URI: uri.Path, + HostHeader: uri.Host, }, nil } @@ -1531,19 +1542,30 @@ func getHTTPSHealthCheck(service *v1.Service, nodePort int32) (*scwlb.HealthChec if err != nil { return nil, err } - uri, err := getHTTPHealthCheckURI(service, nodePort) + + uriStr, err := getHTTPHealthCheckURI(service, nodePort) if err != nil { return nil, err } + uri, err := url.Parse(fmt.Sprintf("https://%s", uriStr)) + if err != nil { + return nil, err + } + if uri.Path == "" { + uri.Path = "/" + } + method, err := getHTTPHealthCheckMethod(service, nodePort) if err != nil { return nil, err } return &scwlb.HealthCheckHTTPSConfig{ - Method: method, - Code: &code, - URI: uri, + Method: method, + Code: &code, + URI: uri.Path, + HostHeader: uri.Host, + Sni: uri.Host, }, nil } @@ -1905,65 +1927,9 @@ func backendEquals(got, want *scwlb.Backend) bool { return false } - // TODO - if got.HealthCheck != want.HealthCheck { - if got.HealthCheck == nil || want.HealthCheck == nil { - klog.V(3).Infof("backend.HealthCheck: %s - %s", got.HealthCheck, want.HealthCheck) - return false - } - - if got.HealthCheck.Port != want.HealthCheck.Port { - klog.V(3).Infof("backend.HealthCheck.Port: %s - %s", got.HealthCheck.Port, want.HealthCheck.Port) - return false - } - if !durationPtrEqual(got.HealthCheck.CheckDelay, want.HealthCheck.CheckDelay) { - klog.V(3).Infof("backend.HealthCheck.CheckDelay: %s - %s", got.HealthCheck.CheckDelay, want.HealthCheck.CheckDelay) - return false - } - if !durationPtrEqual(got.HealthCheck.CheckTimeout, want.HealthCheck.CheckTimeout) { - klog.V(3).Infof("backend.HealthCheck.CheckTimeout: %s - %s", got.HealthCheck.CheckTimeout, want.HealthCheck.CheckTimeout) - return false - } - if got.HealthCheck.CheckMaxRetries != want.HealthCheck.CheckMaxRetries { - klog.V(3).Infof("backend.HealthCheck.CheckMaxRetries: %s - %s", got.HealthCheck.CheckMaxRetries, want.HealthCheck.CheckMaxRetries) - return false - } - if got.HealthCheck.CheckSendProxy != want.HealthCheck.CheckSendProxy { - klog.V(3).Infof("backend.HealthCheck.CheckSendProxy: %s - %s", got.HealthCheck.CheckSendProxy, want.HealthCheck.CheckSendProxy) - return false - } - if (got.HealthCheck.TCPConfig == nil) != (want.HealthCheck.TCPConfig == nil) { - klog.V(3).Infof("backend.HealthCheck.TCPConfig: %s - %s", got.HealthCheck.TCPConfig, want.HealthCheck.TCPConfig) - return false - } - if (got.HealthCheck.MysqlConfig == nil) != (want.HealthCheck.MysqlConfig == nil) { - klog.V(3).Infof("backend.HealthCheck.MysqlConfig: %s - %s", got.HealthCheck.MysqlConfig, want.HealthCheck.MysqlConfig) - return false - } - if (got.HealthCheck.PgsqlConfig == nil) != (want.HealthCheck.PgsqlConfig == nil) { - klog.V(3).Infof("backend.HealthCheck.PgsqlConfig: %s - %s", got.HealthCheck.PgsqlConfig, want.HealthCheck.PgsqlConfig) - return false - } - if (got.HealthCheck.LdapConfig == nil) != (want.HealthCheck.LdapConfig == nil) { - klog.V(3).Infof("backend.HealthCheck.LdapConfig: %s - %s", got.HealthCheck.LdapConfig, want.HealthCheck.LdapConfig) - return false - } - if (got.HealthCheck.RedisConfig == nil) != (want.HealthCheck.RedisConfig == nil) { - klog.V(3).Infof("backend.HealthCheck.RedisConfig: %s - %s", got.HealthCheck.RedisConfig, want.HealthCheck.RedisConfig) - return false - } - if (got.HealthCheck.HTTPConfig == nil) != (want.HealthCheck.HTTPConfig == nil) { - klog.V(3).Infof("backend.HealthCheck.HTTPConfig: %s - %s", got.HealthCheck.HTTPConfig, want.HealthCheck.HTTPConfig) - return false - } - if (got.HealthCheck.HTTPSConfig == nil) != (want.HealthCheck.HTTPSConfig == nil) { - klog.V(3).Infof("backend.HealthCheck.HTTPSConfig: %s - %s", got.HealthCheck.HTTPSConfig, want.HealthCheck.HTTPSConfig) - return false - } - if !scwDurationPtrEqual(got.HealthCheck.TransientCheckDelay, want.HealthCheck.TransientCheckDelay) { - klog.V(3).Infof("backend.HealthCheck.TransientCheckDelay: %s - %s", got.HealthCheck.TransientCheckDelay, want.HealthCheck.TransientCheckDelay) - return false - } + if !reflect.DeepEqual(got.HealthCheck, want.HealthCheck) { + klog.V(3).Infof("backend.HealthCheck: %s - %s", got.HealthCheck, want.HealthCheck) + return false } return true @@ -2074,7 +2040,7 @@ func aclsEquals(got []*scwlb.ACL, want []*scwlb.ACLSpec) bool { slices.SortStableFunc(got, func(a, b *scwlb.ACL) bool { return a.Index < b.Index }) slices.SortStableFunc(want, func(a, b *scwlb.ACLSpec) bool { return a.Index < b.Index }) - for idx, _ := range want { + for idx := range want { if want[idx].Name != got[idx].Name { return false } @@ -2102,7 +2068,6 @@ func aclsEquals(got []*scwlb.ACL, want []*scwlb.ACLSpec) bool { } func (l *loadbalancers) createBackend(service *v1.Service, loadbalancer *scwlb.LB, backend *scwlb.Backend) (*scwlb.Backend, error) { - // TODO: implement createBackend b, err := l.api.CreateBackend(&scwlb.ZonedAPICreateBackendRequest{ Zone: getLoadBalancerZone(service), LBID: loadbalancer.ID, @@ -2130,7 +2095,6 @@ func (l *loadbalancers) createBackend(service *v1.Service, loadbalancer *scwlb.L } func (l *loadbalancers) updateBackend(service *v1.Service, loadbalancer *scwlb.LB, backend *scwlb.Backend) (*scwlb.Backend, error) { - // TODO: implement updateBackend b, err := l.api.UpdateBackend(&scwlb.ZonedAPIUpdateBackendRequest{ Zone: getLoadBalancerZone(service), BackendID: backend.ID, diff --git a/scaleway/loadbalancers_test.go b/scaleway/loadbalancers_test.go index 52ce375..cdb23c4 100644 --- a/scaleway/loadbalancers_test.go +++ b/scaleway/loadbalancers_test.go @@ -941,6 +941,30 @@ func TestBackendEquals(t *testing.T) { want bool }{"with a different TimeoutTunnel", reference, diff, false}) + httpRef := deepCloneBackend(reference) + httpRef.HealthCheck.TCPConfig = nil + httpRef.HealthCheck.HTTPConfig = &scwlb.HealthCheckHTTPConfig{ + URI: "/", + Method: "POST", + Code: scw.Int32Ptr(200), + } + httpDiff := deepCloneBackend(httpRef) + matrix = append(matrix, struct { + Name string + a *scwlb.Backend + b *scwlb.Backend + want bool + }{"with same HTTP healthchecks", httpRef, httpDiff, true}) + + httpDiff = deepCloneBackend(httpRef) + httpDiff.HealthCheck.HTTPConfig.Code = scw.Int32Ptr(404) + matrix = append(matrix, struct { + Name string + a *scwlb.Backend + b *scwlb.Backend + want bool + }{"with same HTTP healthchecks", httpRef, httpDiff, false}) + for _, tt := range matrix { t.Run(tt.Name, func(t *testing.T) { got := backendEquals(tt.a, tt.b) @@ -1137,6 +1161,76 @@ func TestMakeACLPrefix(t *testing.T) { } } +func TestGetHTTPHealthCheck(t *testing.T) { + matrix := []struct { + name string + svc *v1.Service + want *scwlb.HealthCheckHTTPConfig + }{ + {"with empty config", &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "service.beta.kubernetes.io/scw-loadbalancer-health-check-type": "http", + }, + }, + }, &scwlb.HealthCheckHTTPConfig{URI: "/", Method: "GET", Code: scw.Int32Ptr(200)}}, + + {"with just a domain", &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "service.beta.kubernetes.io/scw-loadbalancer-health-check-type": "http", + "service.beta.kubernetes.io/scw-loadbalancer-health-check-http-uri": "domain.tld", + }, + }, + }, &scwlb.HealthCheckHTTPConfig{URI: "/", Method: "GET", Code: scw.Int32Ptr(200), HostHeader: "domain.tld"}}, + + {"with a domain and path", &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "service.beta.kubernetes.io/scw-loadbalancer-health-check-type": "http", + "service.beta.kubernetes.io/scw-loadbalancer-health-check-http-uri": "domain.tld/path", + }, + }, + }, &scwlb.HealthCheckHTTPConfig{URI: "/path", Method: "GET", Code: scw.Int32Ptr(200), HostHeader: "domain.tld"}}, + + {"with just a path", &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "service.beta.kubernetes.io/scw-loadbalancer-health-check-type": "http", + "service.beta.kubernetes.io/scw-loadbalancer-health-check-http-uri": "/path", + }, + }, + }, &scwlb.HealthCheckHTTPConfig{URI: "/path", Method: "GET", Code: scw.Int32Ptr(200)}}, + + {"with a specific code", &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "service.beta.kubernetes.io/scw-loadbalancer-health-check-type": "http", + "service.beta.kubernetes.io/scw-loadbalancer-health-check-http-code": "404", + }, + }, + }, &scwlb.HealthCheckHTTPConfig{URI: "/", Method: "GET", Code: scw.Int32Ptr(404)}}, + + {"with a specific method", &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "service.beta.kubernetes.io/scw-loadbalancer-health-check-type": "http", + "service.beta.kubernetes.io/scw-loadbalancer-health-check-http-method": "POST", + }, + }, + }, &scwlb.HealthCheckHTTPConfig{URI: "/", Method: "POST", Code: scw.Int32Ptr(200)}}, + } + + for _, tt := range matrix { + t.Run(tt.name, func(t *testing.T) { + got, _ := getHTTPHealthCheck(tt.svc, int32(80)) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("want: %v, got: %v", got, tt.want) + } + }) + } +} + func deepCloneBackend(original *scwlb.Backend) *scwlb.Backend { originalJSON, err := json.Marshal(original) if err != nil {