From ebee713b1a89cfdf1975fffbd85d7367a9253abe Mon Sep 17 00:00:00 2001 From: Ilya Hontarau Date: Wed, 3 Apr 2024 16:46:17 +0200 Subject: [PATCH] feat: customize probe settings in pod spec (#97) Closes #82 --- api/v1alpha1/etcdcluster_types.go | 18 + api/v1alpha1/zz_generated.deepcopy.go | 15 + .../crd/bases/etcd.aenix.io_etcdclusters.yaml | 455 ++++++++++++++++++ internal/controller/factory/statefulset.go | 96 ++-- .../controller/factory/statefulset_test.go | 331 ++++++++++++- 5 files changed, 883 insertions(+), 32 deletions(-) diff --git a/api/v1alpha1/etcdcluster_types.go b/api/v1alpha1/etcdcluster_types.go index 845a7fa7..e2d9e091 100644 --- a/api/v1alpha1/etcdcluster_types.go +++ b/api/v1alpha1/etcdcluster_types.go @@ -175,6 +175,24 @@ type PodSpec struct { // ExtraEnv are the extra environment variables to pass to the etcd container. // +optional ExtraEnv []corev1.EnvVar `json:"extraEnv,omitempty"` + + // LivenessProbe defines liveness probe check for the pod. + // If not specified, default probe will be used with HTTP probe handler and path /livez on the port 2379, + // with periodSeconds 5. + // +optional + LivenessProbe *corev1.Probe `json:"livenessProbe,omitempty"` + + // ReadinessProbe defines readiness probe check for the pod. + // If not specified, default probe will be used with HTTP probe handler and path /readyz on the port 2379, + // with periodSeconds 5. + // +optional + ReadinessProbe *corev1.Probe `json:"readinessProbe,omitempty"` + + // StartupProbe defines startup probe check for the pod. + // If not specified, default probe will be used with HTTP probe handler and path /readyz?serializable=false on the port 2379, + // with periodSeconds 5. + // +optional + StartupProbe *corev1.Probe `json:"startupProbe,omitempty"` } // StorageSpec defines the configured storage for a etcd members. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index aede1f39..67fc5992 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -294,6 +294,21 @@ func (in *PodSpec) DeepCopyInto(out *PodSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.LivenessProbe != nil { + in, out := &in.LivenessProbe, &out.LivenessProbe + *out = new(corev1.Probe) + (*in).DeepCopyInto(*out) + } + if in.ReadinessProbe != nil { + in, out := &in.ReadinessProbe, &out.ReadinessProbe + *out = new(corev1.Probe) + (*in).DeepCopyInto(*out) + } + if in.StartupProbe != nil { + in, out := &in.StartupProbe, &out.StartupProbe + *out = new(corev1.Probe) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodSpec. diff --git a/config/crd/bases/etcd.aenix.io_etcdclusters.yaml b/config/crd/bases/etcd.aenix.io_etcdclusters.yaml index cd6a806d..c3f510ac 100644 --- a/config/crd/bases/etcd.aenix.io_etcdclusters.yaml +++ b/config/crd/bases/etcd.aenix.io_etcdclusters.yaml @@ -1146,6 +1146,157 @@ spec: type: object x-kubernetes-map-type: atomic type: array + livenessProbe: + description: LivenessProbe defines liveness probe check for the + pod. + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC port. + properties: + port: + description: Port number of the gRPC service. Number must + be in the range 1 to 65535. + format: int32 + type: integer + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP + allows repeated headers. + items: + description: HTTPHeader describes a custom header to + be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving a TCP + port. + properties: + host: + description: 'Optional: Host name to connect to, defaults + to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object metadata: description: PodMetadata contains metadata relevant to a PodSpec. properties: @@ -1187,6 +1338,158 @@ spec: description: PriorityClassName is the name of the PriorityClass for this pod. type: string + readinessProbe: + description: |- + ReadinessProbe defines readiness probe check for the pod. + If not specified, default probe will be used. + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC port. + properties: + port: + description: Port number of the gRPC service. Number must + be in the range 1 to 65535. + format: int32 + type: integer + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP + allows repeated headers. + items: + description: HTTPHeader describes a custom header to + be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving a TCP + port. + properties: + host: + description: 'Optional: Host name to connect to, defaults + to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object resources: description: Resources describes the compute resource requirements. properties: @@ -1425,6 +1728,158 @@ spec: type: string type: object type: object + startupProbe: + description: |- + StartupProbe defines startup probe check for the pod. + If not specified, default probe will be used. + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC port. + properties: + port: + description: Port number of the gRPC service. Number must + be in the range 1 to 65535. + format: int32 + type: integer + service: + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. HTTP + allows repeated headers. + items: + description: HTTPHeader describes a custom header to + be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving a TCP + port. + properties: + host: + description: 'Optional: Host name to connect to, defaults + to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object terminationGracePeriodSeconds: description: TerminationGracePeriodSeconds is the time to wait before forceful pod shutdown. diff --git a/internal/controller/factory/statefulset.go b/internal/controller/factory/statefulset.go index ab84eca8..52a2010b 100644 --- a/internal/controller/factory/statefulset.go +++ b/internal/controller/factory/statefulset.go @@ -118,36 +118,9 @@ func CreateOrUpdateStatefulSet( MountPath: "/var/run/etcd", }, }, - StartupProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/readyz?serializable=false", - Port: intstr.FromInt32(2379), - }, - }, - InitialDelaySeconds: 1, - PeriodSeconds: 5, - }, - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/livez", - Port: intstr.FromInt32(2379), - }, - }, - InitialDelaySeconds: 5, - PeriodSeconds: 5, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/readyz", - Port: intstr.FromInt32(2379), - }, - }, - InitialDelaySeconds: 5, - PeriodSeconds: 5, - }, + StartupProbe: getStartupProbe(cluster.Spec.PodSpec.StartupProbe), + LivenessProbe: getLivenessProbe(cluster.Spec.PodSpec.LivenessProbe), + ReadinessProbe: getReadinessProbe(cluster.Spec.PodSpec.ReadinessProbe), }, }, ImagePullSecrets: cluster.Spec.PodSpec.ImagePullSecrets, @@ -234,3 +207,66 @@ func generateEtcdArgs(cluster *etcdaenixiov1alpha1.EtcdCluster) []string { return args } + +func getStartupProbe(probe *corev1.Probe) *corev1.Probe { + defaultProbe := corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/readyz?serializable=false", + Port: intstr.FromInt32(2379), + }, + }, + PeriodSeconds: 5, + } + return mergeWithDefaultProbe(probe, defaultProbe) +} + +func getReadinessProbe(probe *corev1.Probe) *corev1.Probe { + defaultProbe := corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/readyz", + Port: intstr.FromInt32(2379), + }, + }, + PeriodSeconds: 5, + } + return mergeWithDefaultProbe(probe, defaultProbe) +} + +func getLivenessProbe(probe *corev1.Probe) *corev1.Probe { + defaultProbe := corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/livez", + Port: intstr.FromInt32(2379), + }, + }, + PeriodSeconds: 5, + } + return mergeWithDefaultProbe(probe, defaultProbe) +} + +func mergeWithDefaultProbe(probe *corev1.Probe, defaultProbe corev1.Probe) *corev1.Probe { + if probe == nil { + return &defaultProbe + } + + if probe.InitialDelaySeconds != 0 { + defaultProbe.InitialDelaySeconds = probe.InitialDelaySeconds + } + + if probe.PeriodSeconds != 0 { + defaultProbe.PeriodSeconds = probe.PeriodSeconds + } + + if hasProbeHandlerAction(*probe) { + defaultProbe.ProbeHandler = probe.ProbeHandler + } + + return &defaultProbe +} + +func hasProbeHandlerAction(probe corev1.Probe) bool { + return probe.HTTPGet != nil || probe.TCPSocket != nil || probe.Exec != nil || probe.GRPC != nil +} diff --git a/internal/controller/factory/statefulset_test.go b/internal/controller/factory/statefulset_test.go index 901d6edf..ca7d61a0 100644 --- a/internal/controller/factory/statefulset_test.go +++ b/internal/controller/factory/statefulset_test.go @@ -21,6 +21,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/ptr" . "github.com/onsi/ginkgo/v2" @@ -129,8 +130,139 @@ var _ = Describe("CreateOrUpdateStatefulSet handler", func() { By("Checking the extraArgs") Expect(sts.Spec.Template.Spec.Containers[0].Command).To(Equal(generateEtcdCommand())) - By("Deleting the statefulset") - Expect(k8sClient.Delete(ctx, sts)).To(Succeed()) + By("Checking the default startup probe", func() { + Expect(sts.Spec.Template.Spec.Containers[0].StartupProbe).To(Equal(&v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/readyz?serializable=false", + Port: intstr.FromInt32(2379), + Scheme: v1.URISchemeHTTP, + }, + }, + TimeoutSeconds: 1, + PeriodSeconds: 5, + SuccessThreshold: 1, + FailureThreshold: 3, + })) + }) + + By("Checking the default readiness probe", func() { + Expect(sts.Spec.Template.Spec.Containers[0].ReadinessProbe).To(Equal(&v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/readyz", + Port: intstr.FromInt32(2379), + Scheme: v1.URISchemeHTTP, + }, + }, + TimeoutSeconds: 1, + PeriodSeconds: 5, + SuccessThreshold: 1, + FailureThreshold: 3, + })) + }) + + By("Checking the default liveness probe", func() { + Expect(sts.Spec.Template.Spec.Containers[0].LivenessProbe).To(Equal(&v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/livez", + Port: intstr.FromInt32(2379), + Scheme: v1.URISchemeHTTP, + }, + }, + TimeoutSeconds: 1, + PeriodSeconds: 5, + SuccessThreshold: 1, + FailureThreshold: 3, + })) + }) + + By("Deleting the statefulset", func() { + Expect(k8sClient.Delete(ctx, sts)).To(Succeed()) + }) + }) + + It("should successfully override probes", func() { + etcdcluster := etcdcluster.DeepCopy() + etcdcluster.Spec.PodSpec = etcdaenixiov1alpha1.PodSpec{ + LivenessProbe: &v1.Probe{ + InitialDelaySeconds: 13, + PeriodSeconds: 11, + }, + ReadinessProbe: &v1.Probe{ + PeriodSeconds: 3, + }, + StartupProbe: &v1.Probe{ + PeriodSeconds: 7, + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/test", + Port: intstr.FromInt32(2389), + }, + }, + }, + } + + sts := &appsv1.StatefulSet{} + err := CreateOrUpdateStatefulSet(ctx, etcdcluster, k8sClient, k8sClient.Scheme()) + Expect(err).NotTo(HaveOccurred()) + + err = k8sClient.Get(ctx, typeNamespacedName, sts) + Expect(err).NotTo(HaveOccurred()) + + By("Checking the updated startup probe", func() { + Expect(sts.Spec.Template.Spec.Containers[0].StartupProbe).To(Equal(&v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/test", + Port: intstr.FromInt32(2389), + Scheme: v1.URISchemeHTTP, + }, + }, + TimeoutSeconds: 1, + PeriodSeconds: 7, + SuccessThreshold: 1, + FailureThreshold: 3, + })) + }) + + By("Checking the updated readiness probe", func() { + Expect(sts.Spec.Template.Spec.Containers[0].ReadinessProbe).To(Equal(&v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/readyz", + Port: intstr.FromInt32(2379), + Scheme: v1.URISchemeHTTP, + }, + }, + TimeoutSeconds: 1, + PeriodSeconds: 3, + SuccessThreshold: 1, + FailureThreshold: 3, + })) + }) + + By("Checking the updated liveness probe", func() { + Expect(sts.Spec.Template.Spec.Containers[0].LivenessProbe).To(Equal(&v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/livez", + Port: intstr.FromInt32(2379), + Scheme: v1.URISchemeHTTP, + }, + }, + InitialDelaySeconds: 13, + TimeoutSeconds: 1, + PeriodSeconds: 11, + SuccessThreshold: 1, + FailureThreshold: 3, + })) + }) + + By("Deleting the statefulset", func() { + Expect(k8sClient.Delete(ctx, sts)).To(Succeed()) + }) }) It("should successfully create the statefulset with emptyDir", func() { @@ -188,4 +320,199 @@ var _ = Describe("CreateOrUpdateStatefulSet handler", func() { })) }) }) + + Context("When getting liveness probe", func() { + It("should correctly get default values", func() { + probe := getLivenessProbe(nil) + Expect(probe).To(Equal(&v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/livez", + Port: intstr.FromInt32(2379), + }, + }, + PeriodSeconds: 5, + })) + }) + It("should correctly override all values", func() { + probe := getLivenessProbe(&v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/liveznew", + Port: intstr.FromInt32(2390), + }, + }, + InitialDelaySeconds: 7, + PeriodSeconds: 3, + }) + Expect(probe).To(Equal(&v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/liveznew", + Port: intstr.FromInt32(2390), + }, + }, + InitialDelaySeconds: 7, + PeriodSeconds: 3, + })) + }) + It("should correctly override partial changes ", func() { + probe := getLivenessProbe(&v1.Probe{ + InitialDelaySeconds: 7, + PeriodSeconds: 3, + }) + Expect(probe).To(Equal(&v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/livez", + Port: intstr.FromInt32(2379), + }, + }, + InitialDelaySeconds: 7, + PeriodSeconds: 3, + })) + }) + }) + + Context("When getting startup probe", func() { + It("should correctly get default values", func() { + probe := getStartupProbe(nil) + Expect(probe).To(Equal(&v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/readyz?serializable=false", + Port: intstr.FromInt32(2379), + }, + }, + PeriodSeconds: 5, + })) + }) + It("should correctly override all values", func() { + probe := getStartupProbe(&v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/readyz", + Port: intstr.FromInt32(2390), + }, + }, + InitialDelaySeconds: 7, + PeriodSeconds: 3, + }) + Expect(probe).To(Equal(&v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/readyz", + Port: intstr.FromInt32(2390), + }, + }, + InitialDelaySeconds: 7, + PeriodSeconds: 3, + })) + }) + It("should correctly override partial changes", func() { + probe := getStartupProbe(&v1.Probe{ + InitialDelaySeconds: 7, + PeriodSeconds: 3, + }) + Expect(probe).To(Equal(&v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/readyz?serializable=false", + Port: intstr.FromInt32(2379), + }, + }, + InitialDelaySeconds: 7, + PeriodSeconds: 3, + })) + }) + }) + + Context("When getting liveness probe", func() { + It("should correctly get default values", func() { + probe := getLivenessProbe(nil) + Expect(probe).To(Equal(&v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/livez", + Port: intstr.FromInt32(2379), + }, + }, + PeriodSeconds: 5, + })) + }) + It("should correctly override all values", func() { + probe := getLivenessProbe(&v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/liveznew", + Port: intstr.FromInt32(2371), + }, + }, + InitialDelaySeconds: 11, + PeriodSeconds: 13, + }) + Expect(probe).To(Equal(&v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/liveznew", + Port: intstr.FromInt32(2371), + }, + }, + InitialDelaySeconds: 11, + PeriodSeconds: 13, + })) + }) + It("should correctly override partial changes", func() { + probe := getLivenessProbe(&v1.Probe{ + InitialDelaySeconds: 11, + PeriodSeconds: 13, + }) + Expect(probe).To(Equal(&v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/livez", + Port: intstr.FromInt32(2379), + }, + }, + InitialDelaySeconds: 11, + PeriodSeconds: 13, + })) + }) + }) + Context("When merge with default probe", func() { + It("should correctly merge probe with default", func() { + defaultProbe := v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/livez", + Port: intstr.FromInt32(2379), + }, + }, + PeriodSeconds: 5, + } + defaultProbeCopy := defaultProbe.DeepCopy() + + probe := &v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + Exec: &v1.ExecAction{ + Command: []string{"test"}, + }, + }, + InitialDelaySeconds: 11, + } + result := mergeWithDefaultProbe(probe, defaultProbe) + Expect(result).To(Equal(&v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + Exec: &v1.ExecAction{ + Command: []string{"test"}, + }, + }, + InitialDelaySeconds: 11, + PeriodSeconds: 5, + })) + By("Shouldn't mutate default probe", func() { + Expect(defaultProbe).To(Equal(*defaultProbeCopy)) + }) + }) + }) })