Skip to content

Commit 253d0ea

Browse files
zzzeekclaude
andcommitted
Add exec probe handler support to probes API
Add ProbeHandlerType (HTTP/Exec) and Command/Port/Scheme fields to ProbeConf. Introduce SetProbeConfV2 and CreateProbeSetV2 with a self-contained ProbeConf signature that switches on handler type. Export Merge for downstream use. Existing SetProbeConf/CreateProbeSet delegate to V2 for full backward compatibility. References: OSPRH-30190 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 72bdd58 commit 253d0ea

5 files changed

Lines changed: 533 additions & 211 deletions

File tree

modules/common/probes/probes.go

Lines changed: 106 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,23 @@ import (
3030
"strings"
3131
)
3232

33-
func (p *ProbeConf) merge(overrides ProbeConf) {
34-
// Override path if provided
33+
// Merge applies non-zero override values onto the receiver
34+
func (p *ProbeConf) Merge(overrides ProbeConf) {
35+
if overrides.Type != "" {
36+
p.Type = overrides.Type
37+
}
3538
if overrides.Path != "" {
3639
p.Path = overrides.Path
3740
}
38-
// Override timing values if they are non-zero
41+
if len(overrides.Command) > 0 {
42+
p.Command = overrides.Command
43+
}
44+
if overrides.Port > 0 {
45+
p.Port = overrides.Port
46+
}
47+
if overrides.Scheme != nil {
48+
p.Scheme = overrides.Scheme
49+
}
3950
if overrides.InitialDelaySeconds > 0 {
4051
p.InitialDelaySeconds = overrides.InitialDelaySeconds
4152
}
@@ -50,70 +61,61 @@ func (p *ProbeConf) merge(overrides ProbeConf) {
5061
}
5162
}
5263

53-
// CreateProbeSet - creates all probes at once using the interface
64+
// CreateProbeSet creates all probes at once using the interface.
65+
// Port and scheme are applied to all probes as HTTP GET handler parameters.
66+
// For mixed probe types (e.g. HTTP and exec), use CreateProbeSetV2 instead
67+
// with Port/Scheme set in the individual ProbeConf defaults.
5468
func CreateProbeSet(
5569
port int32,
5670
scheme *v1.URIScheme,
5771
overrides ProbeOverrides,
5872
defaults OverrideSpec,
5973
) (*ProbeSet, error) {
74+
for _, p := range []*ProbeConf{defaults.LivenessProbes, defaults.ReadinessProbes, defaults.StartupProbes} {
75+
if p != nil {
76+
p.Port = port
77+
p.Scheme = scheme
78+
}
79+
}
80+
return CreateProbeSetV2(overrides, defaults)
81+
}
6082

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-
)
83+
// CreateProbeSetV2 creates all probes at once using the interface.
84+
// Each probe's handler type, port, scheme, path, and command are determined
85+
// by the ProbeConf fields in the defaults and overrides.
86+
func CreateProbeSetV2(
87+
overrides ProbeOverrides,
88+
defaults OverrideSpec,
89+
) (*ProbeSet, error) {
7590

76-
// Could not process probes config
91+
mergeConf := func(base *ProbeConf, override *ProbeConf) ProbeConf {
92+
if base == nil {
93+
base = &ProbeConf{}
94+
}
95+
conf := *base
96+
if override != nil {
97+
conf.Merge(*override)
98+
}
99+
return conf
100+
}
101+
102+
livenessProbe, err := SetProbeConfV2(
103+
mergeConf(defaults.LivenessProbes, overrides.GetLivenessProbes()),
104+
)
77105
if err != nil {
78106
return nil, err
79107
}
80108

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-
}(),
109+
readinessProbe, err := SetProbeConfV2(
110+
mergeConf(defaults.ReadinessProbes, overrides.GetReadinessProbes()),
94111
)
95-
96-
// Could not process probes config
97112
if err != nil {
98113
return nil, err
99114
}
100115

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-
}(),
116+
startupProbe, err := SetProbeConfV2(
117+
mergeConf(defaults.StartupProbes, overrides.GetStartupProbes()),
114118
)
115-
116-
// Could not process probes config
117119
if err != nil {
118120
return nil, err
119121
}
@@ -125,72 +127,88 @@ func CreateProbeSet(
125127
}, nil
126128
}
127129

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-
}
130+
// SetProbeConfV2 configures and returns a probe based on the ProbeConf
131+
// settings. The probe handler type is determined by ProbeConf.Type:
132+
// ProbeHandlerExec creates an exec probe using ProbeConf.Command, while
133+
// ProbeHandlerHTTP (or unset) creates an HTTP GET probe using ProbeConf.Port,
134+
// ProbeConf.Path, and ProbeConf.Scheme.
135+
func SetProbeConfV2(config ProbeConf) (*v1.Probe, error) {
134136
probe := &v1.Probe{
135-
ProbeHandler: v1.ProbeHandler{
136-
HTTPGet: &v1.HTTPGetAction{
137-
Path: config.Path,
138-
Port: intstr.FromInt32(port),
139-
},
140-
},
141137
InitialDelaySeconds: config.InitialDelaySeconds,
142138
TimeoutSeconds: config.TimeoutSeconds,
143139
PeriodSeconds: config.PeriodSeconds,
144140
FailureThreshold: config.FailureThreshold,
145141
}
146-
if scheme != nil {
147-
probe.HTTPGet.Scheme = *scheme
142+
143+
switch config.Type {
144+
case ProbeHandlerExec:
145+
if len(config.Command) == 0 {
146+
return nil, util.ErrExecProbeCommandRequired
147+
}
148+
probe.ProbeHandler = v1.ProbeHandler{
149+
Exec: &v1.ExecAction{
150+
Command: config.Command,
151+
},
152+
}
153+
default:
154+
if config.Port < 1 || config.Port > 65535 {
155+
return nil, fmt.Errorf("%w: %d", util.ErrInvalidPort, config.Port)
156+
}
157+
probe.ProbeHandler = v1.ProbeHandler{
158+
HTTPGet: &v1.HTTPGetAction{
159+
Path: config.Path,
160+
Port: intstr.FromInt32(config.Port),
161+
},
162+
}
163+
if config.Scheme != nil {
164+
probe.HTTPGet.Scheme = *config.Scheme
165+
}
148166
}
149167
return probe, nil
150168
}
151169

152-
// ValidateProbeConf - This function can be used at webhooks level to explicitly
153-
// validate the overrides
170+
// ValidateProbeConf validates probe configuration overrides for use at the
171+
// webhook level
154172
func ValidateProbeConf(basePath *field.Path, config *ProbeConf) field.ErrorList {
155173
errorList := field.ErrorList{}
156-
// nothing to validate, return an empty errorList
157174
if config == nil {
158175
return errorList
159176
}
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)
177+
178+
switch config.Type {
179+
case ProbeHandlerExec:
180+
if len(config.Command) == 0 {
181+
errorList = append(errorList, field.Required(basePath.Child("command"),
182+
"command is required for exec probe type"))
183+
}
184+
case "", ProbeHandlerHTTP:
185+
if config.Path != "" && !strings.HasPrefix(config.Path, "/") {
186+
errorList = append(errorList, field.Invalid(basePath.Child("path"), config.Path,
187+
"path must start with '/' if specified"))
188+
}
189+
default:
190+
errorList = append(errorList, field.Invalid(basePath.Child("type"), config.Type,
191+
"type must be one of: HTTP, Exec, or unset"))
166192
}
167193

168-
// InitialDelaySeconds validation: must be > 0
169194
if config.InitialDelaySeconds < 0 {
170-
err := field.Invalid(basePath.Child("initialDelaySeconds"), config.InitialDelaySeconds,
171-
"initialDelaySeconds must be non-negative")
172-
errorList = append(errorList, err)
195+
errorList = append(errorList, field.Invalid(basePath.Child("initialDelaySeconds"), config.InitialDelaySeconds,
196+
"initialDelaySeconds must be non-negative"))
173197
}
174198

175-
// TimeoutSeconds validation: fail if it's a negative number
176199
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)
200+
errorList = append(errorList, field.Invalid(basePath.Child("timeoutSeconds"), config.TimeoutSeconds,
201+
"timeoutSeconds must be at least 1 second when set"))
180202
}
181203

182-
// PeriodSeconds validation: fail if it's set as a negative number
183204
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)
205+
errorList = append(errorList, field.Invalid(basePath.Child("periodSeconds"), config.PeriodSeconds,
206+
"periodSeconds must be at least 1 second when set"))
187207
}
188208

189-
// FailureThreshold validation: fail if it's set as a negative number
190209
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)
210+
errorList = append(errorList, field.Invalid(basePath.Child("failureThreshold"), config.FailureThreshold,
211+
"failureThreshold must be at least 1 when set"))
194212
}
195213

196214
return errorList

0 commit comments

Comments
 (0)