@@ -13,8 +13,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313See the License for the specific language governing permissions and
1414limitations under the License.
1515*/
16+ // +kubebuilder:object:generate:=true
17+
18+ // The probes package provides utilities for configuring Kubernetes liveness
19+ // and readiness probes
1620
17- // Package probes provides utilities for configuring Kubernetes liveness and readiness probes
1821package probes
1922
2023import (
@@ -23,61 +26,172 @@ import (
2326 "github.com/openstack-k8s-operators/lib-common/modules/common/util"
2427 v1 "k8s.io/api/core/v1"
2528 "k8s.io/apimachinery/pkg/util/intstr"
29+ "k8s.io/apimachinery/pkg/util/validation/field"
30+ "strings"
2631)
2732
28- // ProbeConfig - the configuration for liveness and readiness probes
29- // LivenessPath - Endpoint path for the liveness probe
30- // ReadinessPath - Endpoint path for the readiness probe
31- // InitialDelaySeconds - Number of seconds after the container starts before liveness/readiness probes are initiated
32- // TimeoutSeconds - Number of seconds after which the probe times out
33- // PeriodSeconds - How often (in seconds) to perform the probe
34- type ProbeConfig struct {
35- LivenessPath string
36- ReadinessPath string
37- InitialDelaySeconds int32
38- TimeoutSeconds int32
39- PeriodSeconds int32
33+ func (p * ProbeConf ) merge (overrides ProbeConf ) {
34+ // Override path if provided
35+ if overrides .Path != "" {
36+ p .Path = overrides .Path
37+ }
38+ // Override timing values if they are non-zero
39+ if overrides .InitialDelaySeconds > 0 {
40+ p .InitialDelaySeconds = overrides .InitialDelaySeconds
41+ }
42+ if overrides .TimeoutSeconds > 0 {
43+ p .TimeoutSeconds = overrides .TimeoutSeconds
44+ }
45+ if overrides .PeriodSeconds > 0 {
46+ p .PeriodSeconds = overrides .PeriodSeconds
47+ }
48+ if overrides .FailureThreshold > 0 {
49+ p .FailureThreshold = overrides .FailureThreshold
50+ }
4051}
4152
42- // SetProbes - configures and returns liveness and readiness probes based on the provided settings
43- func SetProbes (port int , disableNonTLSListeners bool , config ProbeConfig ) (* v1.Probe , * v1.Probe , error ) {
53+ // CreateProbeSet - creates all probes at once using the interface
54+ func CreateProbeSet (
55+ port int32 ,
56+ scheme * v1.URIScheme ,
57+ overrides ProbeOverrides ,
58+ defaults OverrideSpec ,
59+ ) (* ProbeSet , error ) {
4460
45- if port < 1 || port > 65535 {
46- return nil , nil , fmt .Errorf ("%w: %d" , util .ErrInvalidPort , port )
61+ livenessProbe , err := SetProbeConf (
62+ port ,
63+ scheme ,
64+ func () ProbeConf {
65+ if defaults .LivenessProbes == nil {
66+ defaults .LivenessProbes = & ProbeConf {}
67+ }
68+ baseConf := * defaults .LivenessProbes
69+ if p := overrides .GetLivenessProbes (); p != nil {
70+ baseConf .merge (* p )
71+ }
72+ return baseConf
73+ }(),
74+ )
75+
76+ // Could not process probes config
77+ if err != nil {
78+ return nil , err
4779 }
4880
49- var scheme v1.URIScheme
50- if disableNonTLSListeners {
51- scheme = v1 .URISchemeHTTPS
52- } else {
53- scheme = v1 .URISchemeHTTP
81+ readinessProbe , err := SetProbeConf (
82+ port ,
83+ scheme ,
84+ func () ProbeConf {
85+ if defaults .ReadinessProbes == nil {
86+ defaults .ReadinessProbes = & ProbeConf {}
87+ }
88+ baseConf := * defaults .ReadinessProbes
89+ if p := overrides .GetReadinessProbes (); p != nil {
90+ baseConf .merge (* p )
91+ }
92+ return baseConf
93+ }(),
94+ )
95+
96+ // Could not process probes config
97+ if err != nil {
98+ return nil , err
5499 }
55100
56- livenessProbe := & v1.Probe {
57- ProbeHandler : v1.ProbeHandler {
58- HTTPGet : & v1.HTTPGetAction {
59- Path : config .LivenessPath ,
60- Port : intstr .FromInt (port ),
61- Scheme : scheme ,
62- },
63- },
64- InitialDelaySeconds : config .InitialDelaySeconds ,
65- TimeoutSeconds : config .TimeoutSeconds ,
66- PeriodSeconds : config .PeriodSeconds ,
101+ startupProbe , err := SetProbeConf (
102+ port ,
103+ scheme ,
104+ func () ProbeConf {
105+ if defaults .StartupProbes == nil {
106+ defaults .StartupProbes = & ProbeConf {}
107+ }
108+ baseConf := * defaults .StartupProbes
109+ if p := overrides .GetStartupProbes (); p != nil {
110+ baseConf .merge (* p )
111+ }
112+ return baseConf
113+ }(),
114+ )
115+
116+ // Could not process probes config
117+ if err != nil {
118+ return nil , err
67119 }
68120
69- readinessProbe := & v1.Probe {
121+ return & ProbeSet {
122+ Liveness : livenessProbe ,
123+ Readiness : readinessProbe ,
124+ Startup : startupProbe ,
125+ }, nil
126+ }
127+
128+ // SetProbeConf configures and returns liveness and readiness probes based on
129+ // the provided settings
130+ func SetProbeConf (port int32 , scheme * v1.URIScheme , config ProbeConf ) (* v1.Probe , error ) {
131+ if port < 1 || port > 65535 {
132+ return nil , fmt .Errorf ("%w: %d" , util .ErrInvalidPort , port )
133+ }
134+ probe := & v1.Probe {
70135 ProbeHandler : v1.ProbeHandler {
71136 HTTPGet : & v1.HTTPGetAction {
72- Path : config .ReadinessPath ,
73- Port : intstr .FromInt (port ),
74- Scheme : scheme ,
137+ Path : config .Path ,
138+ Port : intstr .FromInt32 (port ),
75139 },
76140 },
77141 InitialDelaySeconds : config .InitialDelaySeconds ,
78142 TimeoutSeconds : config .TimeoutSeconds ,
79143 PeriodSeconds : config .PeriodSeconds ,
144+ FailureThreshold : config .FailureThreshold ,
145+ }
146+ if scheme != nil {
147+ probe .HTTPGet .Scheme = * scheme
148+ }
149+ return probe , nil
150+ }
151+
152+ // ValidateProbeConf - This function can be used at webhooks level to explicitly
153+ // validate the overrides
154+ func ValidateProbeConf (basePath * field.Path , config * ProbeConf ) field.ErrorList {
155+ errorList := field.ErrorList {}
156+ // nothing to validate, return an empty errorList
157+ if config == nil {
158+ return errorList
159+ }
160+ // Path validation: fail is explicitly set as an empty string
161+ // or the endpoint does't start with "/"
162+ if config .Path != "" && ! strings .HasPrefix (config .Path , "/" ) {
163+ err := field .Invalid (basePath .Child ("path" ), config .Path ,
164+ "path must start with '/' if specified" )
165+ errorList = append (errorList , err )
166+ }
167+
168+ // InitialDelaySeconds validation: must be > 0
169+ if config .InitialDelaySeconds < 0 {
170+ err := field .Invalid (basePath .Child ("initialDelaySeconds" ), config .InitialDelaySeconds ,
171+ "initialDelaySeconds must be non-negative" )
172+ errorList = append (errorList , err )
173+ }
174+
175+ // TimeoutSeconds validation: fail if it's a negative number
176+ if config .TimeoutSeconds != 0 && config .TimeoutSeconds < 1 {
177+ err := field .Invalid (basePath .Child ("timeoutSeconds" ), config .TimeoutSeconds ,
178+ "timeoutSeconds must be at least 1 second when set" )
179+ errorList = append (errorList , err )
180+ }
181+
182+ // PeriodSeconds validation: fail if it's set as a negative number
183+ if config .PeriodSeconds != 0 && config .PeriodSeconds < 1 {
184+ err := field .Invalid (basePath .Child ("periodSeconds" ), config .PeriodSeconds ,
185+ "periodSeconds must be at least 1 second when set" )
186+ errorList = append (errorList , err )
187+ }
188+
189+ // FailureThreshold validation: fail if it's set as a negative number
190+ if config .FailureThreshold != 0 && config .FailureThreshold < 1 {
191+ err := field .Invalid (basePath .Child ("failureThreshold" ), config .FailureThreshold ,
192+ "failureThreshold must be at least 1 when set" )
193+ errorList = append (errorList , err )
80194 }
81195
82- return livenessProbe , readinessProbe , nil
196+ return errorList
83197}
0 commit comments