Skip to content

Commit 15303e1

Browse files
authored
ProgressionProbes (#2550)
Provides an API which allows custom probe definitions to determine readiness of the CER phases. Objects can be selected for in one of two ways: by GroupKind, or by Label (matchLabels and matchExpressions). They can then be tested via any of: ConditionEqual, FieldsEqual, and FieldValue. ConditionEqual checks that the object has a condition matching the type and status provided. FieldsEqual uses two provided field paths and checks for equality. FieldValue uses a provided field path and checks that the value is equal to the provided expected value. Signed-off-by: Daniel Franz <dfranz@redhat.com>
1 parent 54f164d commit 15303e1

File tree

20 files changed

+2091
-35
lines changed

20 files changed

+2091
-35
lines changed

api/v1/clusterextensionrevision_types.go

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,25 @@ type ClusterExtensionRevisionSpec struct {
106106
// <opcon:experimental>
107107
ProgressDeadlineMinutes int32 `json:"progressDeadlineMinutes,omitempty"`
108108

109+
// progressionProbes is an optional field which provides the ability to define custom readiness probes
110+
// for objects defined within spec.phases. As documented in that field, most kubernetes-native objects
111+
// within the phases already have some kind of readiness check built-in, but this field allows for checks
112+
// which are tailored to the objects being rolled out - particularly custom resources.
113+
//
114+
// Probes defined within the progressionProbes list will apply to every phase in the revision. However, the probes will only
115+
// execute against phase objects which are a match for the provided selector type. For instance, a probe using a GroupKind selector
116+
// for ConfigMaps will automatically be considered to have passed for any non-ConfigMap object, but will halt any phase containing
117+
// a ConfigMap if that particular object does not pass the probe check.
118+
//
119+
// The maximum number of probes is 20.
120+
//
121+
// +kubebuilder:validation:MinItems=1
122+
// +kubebuilder:validation:MaxItems=20
123+
// +listType=atomic
124+
// +optional
125+
// <opcon:experimental>
126+
ProgressionProbes []ProgressionProbe `json:"progressionProbes,omitempty"`
127+
109128
// collisionProtection specifies the default collision protection strategy for all objects
110129
// in this revision. Individual phases or objects can override this value.
111130
//
@@ -120,6 +139,205 @@ type ClusterExtensionRevisionSpec struct {
120139
CollisionProtection CollisionProtection `json:"collisionProtection,omitempty"`
121140
}
122141

142+
// ProgressionProbe provides a custom probe definition, consisting of an object selection method and assertions.
143+
type ProgressionProbe struct {
144+
// selector is a required field which defines the method by which we select objects to apply the below
145+
// assertions to. Any object which matches the defined selector will have all the associated assertions
146+
// applied against it.
147+
//
148+
// If no objects within a phase are selected by the provided selector, then all assertions defined here
149+
// are considered to have succeeded.
150+
//
151+
// +required
152+
// <opcon:experimental>
153+
Selector ObjectSelector `json:"selector,omitzero"`
154+
155+
// assertions is a required list of checks which will run against the objects selected by the selector. If
156+
// one or more assertions fail then the phase within which the object lives will be not be considered
157+
// 'Ready', blocking rollout of all subsequent phases.
158+
//
159+
// +kubebuilder:validation:MinItems=1
160+
// +kubebuilder:validation:MaxItems=20
161+
// +listType=atomic
162+
// +required
163+
// <opcon:experimental>
164+
Assertions []Assertion `json:"assertions,omitempty"`
165+
}
166+
167+
// ObjectSelector is a discriminated union which defines the method by which we select objects to make assertions against.
168+
// +union
169+
// +kubebuilder:validation:XValidation:rule="self.type == 'GroupKind' ?has(self.groupKind) : !has(self.groupKind)",message="groupKind is required when type is GroupKind, and forbidden otherwise"
170+
// +kubebuilder:validation:XValidation:rule="self.type == 'Label' ?has(self.label) : !has(self.label)",message="label is required when type is Label, and forbidden otherwise"
171+
type ObjectSelector struct {
172+
// type is a required field which specifies the type of selector to use.
173+
//
174+
// The allowed selector types are "GroupKind" and "Label".
175+
//
176+
// When set to "GroupKind", all objects which match the specified group and kind will be selected.
177+
// When set to "Label", all objects which match the specified labels and/or expressions will be selected.
178+
//
179+
// +unionDiscriminator
180+
// +kubebuilder:validation:Enum=GroupKind;Label
181+
// +required
182+
// <opcon:experimental>
183+
Type SelectorType `json:"type,omitempty"`
184+
185+
// groupKind specifies the group and kind of objects to select.
186+
//
187+
// Required when type is "GroupKind".
188+
//
189+
// Uses the Kubernetes format specified here:
190+
// https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#GroupKind
191+
//
192+
// +optional
193+
// +unionMember
194+
// <opcon:experimental>
195+
GroupKind metav1.GroupKind `json:"groupKind,omitempty,omitzero"`
196+
197+
// label is the label selector definition.
198+
//
199+
// Required when type is "Label".
200+
//
201+
// A probe using a Label selector will be executed against every object matching the labels or expressions; you must use care
202+
// when using this type of selector. For example, if multiple Kind objects are selected via labels then the probe is
203+
// likely to fail because the values of different Kind objects rarely share the same schema.
204+
//
205+
// The LabelSelector field uses the following Kubernetes format:
206+
// https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#LabelSelector
207+
// Requires exactly one of matchLabels or matchExpressions.
208+
//
209+
// +optional
210+
// +unionMember
211+
// +kubebuilder:validation:XValidation:rule="(has(self.matchExpressions) && !has(self.matchLabels)) || (!has(self.matchExpressions) && has(self.matchLabels))",message="exactly one of matchLabels or matchExpressions must be set"
212+
// <opcon:experimental>
213+
Label metav1.LabelSelector `json:"label,omitempty,omitzero"`
214+
}
215+
216+
// SelectorType defines the type of selector used for progressionProbes.
217+
// +enum
218+
type SelectorType string
219+
220+
const (
221+
SelectorTypeGroupKind SelectorType = "GroupKind"
222+
SelectorTypeLabel SelectorType = "Label"
223+
)
224+
225+
// ProbeType defines the type of probe used as an assertion.
226+
// +enum
227+
type ProbeType string
228+
229+
const (
230+
ProbeTypeFieldCondition ProbeType = "ConditionEqual"
231+
ProbeTypeFieldEqual ProbeType = "FieldsEqual"
232+
ProbeTypeFieldValue ProbeType = "FieldValue"
233+
)
234+
235+
// Assertion is a discriminated union which defines the probe type and definition used as an assertion.
236+
// +union
237+
// +kubebuilder:validation:XValidation:rule="self.type == 'ConditionEqual' ?has(self.conditionEqual) : !has(self.conditionEqual)",message="conditionEqual is required when type is ConditionEqual, and forbidden otherwise"
238+
// +kubebuilder:validation:XValidation:rule="self.type == 'FieldsEqual' ?has(self.fieldsEqual) : !has(self.fieldsEqual)",message="fieldsEqual is required when type is FieldsEqual, and forbidden otherwise"
239+
// +kubebuilder:validation:XValidation:rule="self.type == 'FieldValue' ?has(self.fieldValue) : !has(self.fieldValue)",message="fieldValue is required when type is FieldValue, and forbidden otherwise"
240+
type Assertion struct {
241+
// type is a required field which specifies the type of probe to use.
242+
//
243+
// The allowed probe types are "ConditionEqual", "FieldsEqual", and "FieldValue".
244+
//
245+
// When set to "ConditionEqual", the probe checks objects that have reached a condition of specified type and status.
246+
// When set to "FieldsEqual", the probe checks that the values found at two provided field paths are matching.
247+
// When set to "FieldValue", the probe checks that the value found at the provided field path matches what was specified.
248+
//
249+
// +unionDiscriminator
250+
// +kubebuilder:validation:Enum=ConditionEqual;FieldsEqual;FieldValue
251+
// +required
252+
// <opcon:experimental>
253+
Type ProbeType `json:"type,omitempty"`
254+
255+
// conditionEqual contains the expected condition type and status.
256+
//
257+
// +unionMember
258+
// +optional
259+
// <opcon:experimental>
260+
ConditionEqual ConditionEqualProbe `json:"conditionEqual,omitzero"`
261+
262+
// fieldsEqual contains the two field paths whose values are expected to match.
263+
//
264+
// +unionMember
265+
// +optional
266+
// <opcon:experimental>
267+
FieldsEqual FieldsEqualProbe `json:"fieldsEqual,omitzero"`
268+
269+
// fieldValue contains the expected field path and value found within.
270+
//
271+
// +unionMember
272+
// +optional
273+
// <opcon:experimental>
274+
FieldValue FieldValueProbe `json:"fieldValue,omitzero"`
275+
}
276+
277+
// ConditionEqualProbe defines the condition type and status required for the probe to succeed.
278+
type ConditionEqualProbe struct {
279+
// type sets the expected condition type, i.e. "Ready".
280+
//
281+
// +kubebuilder:validation:MinLength=1
282+
// +kubebuilder:validation:MaxLength=200
283+
// +required
284+
// <opcon:experimental>
285+
Type string `json:"type,omitempty"`
286+
287+
// status sets the expected condition status.
288+
//
289+
// Allowed values are "True" and "False".
290+
//
291+
// +kubebuilder:validation:Enum=True;False
292+
// +required
293+
// <opcon:experimental>
294+
Status string `json:"status,omitempty"`
295+
}
296+
297+
// FieldsEqualProbe defines the paths of the two fields required to match for the probe to succeed.
298+
type FieldsEqualProbe struct {
299+
// fieldA sets the field path for the first field, i.e. "spec.replicas". The probe will fail
300+
// if the path does not exist.
301+
//
302+
// +kubebuilder:validation:MinLength=1
303+
// +kubebuilder:validation:MaxLength=200
304+
// +kubebuilder:validation:XValidation:rule="self.matches('^[a-zA-Z0-9]+(?:\\\\.[a-zA-Z0-9]+)*$')",message="must contain a valid field path. valid fields contain upper or lower-case alphanumeric characters separated by the \".\" character."
305+
// +required
306+
// <opcon:experimental>
307+
FieldA string `json:"fieldA,omitempty"`
308+
309+
// fieldB sets the field path for the second field, i.e. "status.readyReplicas". The probe will fail
310+
// if the path does not exist.
311+
//
312+
// +kubebuilder:validation:MinLength=1
313+
// +kubebuilder:validation:MaxLength=200
314+
// +kubebuilder:validation:XValidation:rule="self.matches('^[a-zA-Z0-9]+(?:\\\\.[a-zA-Z0-9]+)*$')",message="must contain a valid field path. valid fields contain upper or lower-case alphanumeric characters separated by the \".\" character."
315+
// +required
316+
// <opcon:experimental>
317+
FieldB string `json:"fieldB,omitempty"`
318+
}
319+
320+
// FieldValueProbe defines the path and value expected within for the probe to succeed.
321+
type FieldValueProbe struct {
322+
// fieldPath sets the field path for the field to check, i.e. "status.phase". The probe will fail
323+
// if the path does not exist.
324+
//
325+
// +kubebuilder:validation:MinLength=1
326+
// +kubebuilder:validation:MaxLength=200
327+
// +kubebuilder:validation:XValidation:rule="self.matches('^[a-zA-Z0-9]+(?:\\\\.[a-zA-Z0-9]+)*$')",message="must contain a valid field path. valid fields contain upper or lower-case alphanumeric characters separated by the \".\" character."
328+
// +required
329+
// <opcon:experimental>
330+
FieldPath string `json:"fieldPath,omitempty"`
331+
332+
// value sets the expected value found at fieldPath, i.e. "Bound".
333+
//
334+
// +kubebuilder:validation:MinLength=1
335+
// +kubebuilder:validation:MaxLength=200
336+
// +required
337+
// <opcon:experimental>
338+
Value string `json:"value,omitempty"`
339+
}
340+
123341
// ClusterExtensionRevisionLifecycleState specifies the lifecycle state of the ClusterExtensionRevision.
124342
type ClusterExtensionRevisionLifecycleState string
125343

api/v1/zz_generated.deepcopy.go

Lines changed: 108 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)