Skip to content

Commit a2c99fd

Browse files
committed
Introduce configurable interface for probe management
This patch introduces the ProbeOverrides interface that enables service operators to properly configure and tune liveness, readiness and startup probes for their deployments. This implementation provides: - A ProbeConf struct for standardized probe configuration (path, delays, timeouts, periods and failure thresholds) - A ProvbeOverrides interface that allows service operators to extend their API specs and provide a common set of overrides - A CreateProbeSet function that can be called from the service operators as the main entrypoint to generate a set of probes that should be applied to a Pod. With this function operators can either provide a set of overrides or rely on the defaults defined in the service operator. - A ValidateProbes function to validate (mostly on Updates), at webhooks level, the values provided as user input The functions and the interface provided by this patch reduce the amount of duplicated code that should be implemented in the service operators. Jira: OSPRH-26731 Signed-off-by: Francesco Pantano <fpantano@redhat.com>
1 parent c6fd98c commit a2c99fd

4 files changed

Lines changed: 719 additions & 60 deletions

File tree

modules/common/probes/probes.go

Lines changed: 152 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
See the License for the specific language governing permissions and
1414
limitations 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
1821
package probes
1922

2023
import (
@@ -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

Comments
 (0)