Skip to content

Commit b96da8d

Browse files
authored
Merge pull request #673 from fmount/probes
Introduce configurable interface for probe management
2 parents c6fd98c + a2c99fd commit b96da8d

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)