Skip to content

Commit 55b3524

Browse files
committed
Default Probes Refactor
Migrates the default progression probes out of the CER controller into the boxcutter applier in order to use the ProgressionProbes API to transparently stamp out the checks. Also adds flag to the API to allow checking status.observedGeneration==metadata.generation before executing probes. Signed-off-by: Daniel Franz <dfranz@redhat.com>
1 parent a307a6d commit 55b3524

10 files changed

Lines changed: 310 additions & 93 deletions

File tree

api/v1/clusterextensionrevision_types.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,15 @@ type ProgressionProbe struct {
162162
// +required
163163
// <opcon:experimental>
164164
Assertions []Assertion `json:"assertions,omitempty"`
165+
166+
// observedGeneration is a flag which, when true, ensures that status.observedGeneration is equal to
167+
// .metadata.generation before running the given assertions on the selected object(s). If a probed object does
168+
// not contain the status.observedGeneration field, the given prober is executed directly.
169+
//
170+
// +optional
171+
// +kubebuilder:default:=false
172+
// <opcon:experimental>
173+
ObservedGeneration *bool `json:"observedGeneration,omitempty"`
165174
}
166175

167176
// ObjectSelector is a discriminated union which defines the method by which we select objects to make assertions against.
@@ -227,9 +236,14 @@ const (
227236
type ProbeType string
228237

229238
const (
230-
ProbeTypeFieldCondition ProbeType = "ConditionEqual"
231-
ProbeTypeFieldEqual ProbeType = "FieldsEqual"
239+
ProbeTypeConditionEqual ProbeType = "ConditionEqual"
240+
ProbeTypeFieldsEqual ProbeType = "FieldsEqual"
232241
ProbeTypeFieldValue ProbeType = "FieldValue"
242+
243+
// Deprecated: use ProbeTypeConditionEqual instead.
244+
ProbeTypeFieldCondition = ProbeTypeConditionEqual
245+
// Deprecated: use ProbeTypeFieldsEqual instead.
246+
ProbeTypeFieldEqual = ProbeTypeFieldsEqual
233247
)
234248

235249
// Assertion is a discriminated union which defines the probe type and definition used as an assertion.

api/v1/zz_generated.deepcopy.go

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

applyconfigurations/api/v1/progressionprobe.go

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

helm/olmv1/base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensionrevisions.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,13 @@ spec:
364364
minItems: 1
365365
type: array
366366
x-kubernetes-list-type: atomic
367+
observedGeneration:
368+
default: false
369+
description: |-
370+
observedGeneration is a flag which, when true, ensures that status.observedGeneration is equal to
371+
.metadata.generation before running the given assertions on the selected object(s). If a probed object does
372+
not contain the status.observedGeneration field, the given prober is executed directly.
373+
type: boolean
367374
selector:
368375
description: |-
369376
selector is a required field which defines the method by which we select objects to apply the below

internal/operator-controller/applier/boxcutter.go

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import (
1414

1515
"helm.sh/helm/v3/pkg/release"
1616
"helm.sh/helm/v3/pkg/storage/driver"
17+
appsv1 "k8s.io/api/apps/v1"
18+
corev1 "k8s.io/api/core/v1"
19+
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
1720
apierrors "k8s.io/apimachinery/pkg/api/errors"
1821
"k8s.io/apimachinery/pkg/api/meta"
1922
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -208,10 +211,12 @@ func (r *SimpleRevisionGenerator) buildClusterExtensionRevision(
208211
annotations[labels.ServiceAccountNamespaceKey] = ext.Spec.Namespace
209212

210213
phases := PhaseSort(objects)
214+
progressionProbes := defaultProgressionProbes()
211215

212216
spec := ocv1ac.ClusterExtensionRevisionSpec().
213217
WithLifecycleState(ocv1.ClusterExtensionRevisionLifecycleStateActive).
214-
WithPhases(phases...)
218+
WithPhases(phases...).
219+
WithProgressionProbes(progressionProbes...)
215220
if p := ext.Spec.ProgressDeadlineMinutes; p > 0 {
216221
spec.WithProgressDeadlineMinutes(p)
217222
}
@@ -602,6 +607,115 @@ func latestRevisionNumber(prevRevisions []ocv1.ClusterExtensionRevision) int64 {
602607
return prevRevisions[len(prevRevisions)-1].Spec.Revision
603608
}
604609

610+
func defaultProgressionProbes() []*ocv1ac.ProgressionProbeApplyConfiguration {
611+
crdProbe := ocv1ac.ProgressionProbe().
612+
WithSelector(ocv1ac.ObjectSelector().
613+
WithType(ocv1.SelectorTypeGroupKind).
614+
WithGroupKind(metav1.GroupKind{
615+
Group: "apiextensions.k8s.io",
616+
Kind: "CustomResourceDefinition",
617+
})).
618+
WithAssertions(ocv1ac.Assertion().
619+
WithType(ocv1.ProbeTypeConditionEqual).
620+
WithConditionEqual(
621+
ocv1ac.ConditionEqualProbe().
622+
WithType(string(apiextensions.Established)).
623+
WithStatus(string(corev1.ConditionTrue)))).
624+
WithObservedGeneration(true)
625+
626+
// Checks if the Type: "Ready" Condition is "True"
627+
readyConditionAssertion := ocv1ac.Assertion().
628+
WithType(ocv1.ProbeTypeConditionEqual).
629+
WithConditionEqual(
630+
ocv1ac.ConditionEqualProbe().
631+
WithType("Ready").
632+
WithStatus("True"))
633+
634+
certProbe := ocv1ac.ProgressionProbe().
635+
WithSelector(ocv1ac.ObjectSelector().
636+
WithType(ocv1.SelectorTypeGroupKind).
637+
WithGroupKind(metav1.GroupKind{
638+
Group: "acme.cert-manager.io",
639+
Kind: "Certificate",
640+
})).
641+
WithAssertions(readyConditionAssertion).
642+
WithObservedGeneration(true)
643+
issuerProbe := ocv1ac.ProgressionProbe().
644+
WithSelector(ocv1ac.ObjectSelector().
645+
WithType(ocv1.SelectorTypeGroupKind).
646+
WithGroupKind(metav1.GroupKind{
647+
Group: "acme.cert-manager.io",
648+
Kind: "Issuer",
649+
})).
650+
WithAssertions(readyConditionAssertion).
651+
WithObservedGeneration(true)
652+
653+
// namespaceActiveProbe is a probe which asserts that the namespace is in "Active" phase
654+
namespaceActiveProbe := ocv1ac.ProgressionProbe().
655+
WithSelector(ocv1ac.ObjectSelector().
656+
WithType(ocv1.SelectorTypeGroupKind).
657+
WithGroupKind(metav1.GroupKind{
658+
Group: corev1.GroupName,
659+
Kind: "Namespace",
660+
})).
661+
WithAssertions(ocv1ac.Assertion().
662+
WithType(ocv1.ProbeTypeFieldValue).
663+
WithFieldValue(ocv1ac.FieldValueProbe().
664+
WithFieldPath("status.phase").
665+
WithValue(string(corev1.NamespaceActive))))
666+
667+
// pvcBoundProbe is a probe which asserts that the PVC is in "Bound" phase
668+
pvcBoundProbe := ocv1ac.ProgressionProbe().
669+
WithSelector(ocv1ac.ObjectSelector().
670+
WithType(ocv1.SelectorTypeGroupKind).
671+
WithGroupKind(metav1.GroupKind{
672+
Group: corev1.GroupName,
673+
Kind: "PersistentVolumeClaim",
674+
})).
675+
WithAssertions(ocv1ac.Assertion().
676+
WithType(ocv1.ProbeTypeFieldValue).
677+
WithFieldValue(ocv1ac.FieldValueProbe().
678+
WithFieldPath("status.phase").
679+
WithValue(string(corev1.ClaimBound))))
680+
681+
// Checks if the Type: "Available" Condition is "True".
682+
availableConditionAssertion := ocv1ac.Assertion().
683+
WithType(ocv1.ProbeTypeConditionEqual).
684+
WithConditionEqual(ocv1ac.ConditionEqualProbe().
685+
WithType(string(appsv1.DeploymentAvailable)).
686+
WithStatus(string(corev1.ConditionTrue)))
687+
688+
// Checks if status.updatedReplicas == status.replicas.
689+
// Works for StatefulSets, Deployments and ReplicaSets.
690+
replicasUpdatedAssertion := ocv1ac.Assertion().
691+
WithType(ocv1.ProbeTypeFieldsEqual).
692+
WithFieldsEqual(ocv1ac.FieldsEqualProbe().
693+
WithFieldA("status.updatedReplicas").
694+
WithFieldB("status.replicas"))
695+
696+
statefulSetProbe := ocv1ac.ProgressionProbe().WithSelector(
697+
ocv1ac.ObjectSelector().WithType(ocv1.SelectorTypeGroupKind).
698+
WithGroupKind(metav1.GroupKind{
699+
Group: appsv1.GroupName,
700+
Kind: "StatefulSet",
701+
}),
702+
).WithAssertions(replicasUpdatedAssertion, availableConditionAssertion).
703+
WithObservedGeneration(true)
704+
705+
deploymentProbe := ocv1ac.ProgressionProbe().WithSelector(
706+
ocv1ac.ObjectSelector().WithType(ocv1.SelectorTypeGroupKind).
707+
WithGroupKind(metav1.GroupKind{
708+
Group: appsv1.GroupName,
709+
Kind: "Deployment",
710+
}),
711+
).WithAssertions(replicasUpdatedAssertion, availableConditionAssertion).
712+
WithObservedGeneration(true)
713+
714+
return []*ocv1ac.ProgressionProbeApplyConfiguration{
715+
deploymentProbe, statefulSetProbe, pvcBoundProbe, namespaceActiveProbe, issuerProbe, certProbe, crdProbe,
716+
}
717+
}
718+
605719
func splitManifestDocuments(file string) []string {
606720
// Estimate: typical manifests have ~50-100 lines per document
607721
// Pre-allocate for reasonable bundle size to reduce allocations

internal/operator-controller/applier/boxcutter_test.go

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
appsv1 "k8s.io/api/apps/v1"
1919
corev1 "k8s.io/api/core/v1"
2020
rbacv1 "k8s.io/api/rbac/v1"
21+
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
2122
apierrors "k8s.io/apimachinery/pkg/api/errors"
2223
apimeta "k8s.io/apimachinery/pkg/api/meta"
2324
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -143,7 +144,110 @@ func Test_SimpleRevisionGenerator_GenerateRevisionFromHelmRelease(t *testing.T)
143144
},
144145
},
145146
}),
146-
),
147+
)).
148+
WithProgressionProbes(
149+
ocv1ac.ProgressionProbe().WithSelector(
150+
ocv1ac.ObjectSelector().WithType(ocv1.SelectorTypeGroupKind).
151+
WithGroupKind(metav1.GroupKind{
152+
Group: appsv1.GroupName,
153+
Kind: "Deployment",
154+
})).
155+
WithAssertions(
156+
ocv1ac.Assertion().
157+
WithType(ocv1.ProbeTypeFieldsEqual).
158+
WithFieldsEqual(ocv1ac.FieldsEqualProbe().
159+
WithFieldA("status.updatedReplicas").
160+
WithFieldB("status.replicas")),
161+
ocv1ac.Assertion().
162+
WithType(ocv1.ProbeTypeConditionEqual).
163+
WithConditionEqual(ocv1ac.ConditionEqualProbe().
164+
WithType(string(appsv1.DeploymentAvailable)).
165+
WithStatus(string(corev1.ConditionTrue)))).
166+
WithObservedGeneration(true),
167+
ocv1ac.ProgressionProbe().WithSelector(
168+
ocv1ac.ObjectSelector().WithType(ocv1.SelectorTypeGroupKind).
169+
WithGroupKind(metav1.GroupKind{
170+
Group: appsv1.GroupName,
171+
Kind: "StatefulSet",
172+
})).
173+
WithAssertions(
174+
ocv1ac.Assertion().
175+
WithType(ocv1.ProbeTypeFieldsEqual).
176+
WithFieldsEqual(ocv1ac.FieldsEqualProbe().
177+
WithFieldA("status.updatedReplicas").
178+
WithFieldB("status.replicas")),
179+
ocv1ac.Assertion().
180+
WithType(ocv1.ProbeTypeConditionEqual).
181+
WithConditionEqual(ocv1ac.ConditionEqualProbe().
182+
WithType(string(appsv1.DeploymentAvailable)).
183+
WithStatus(string(corev1.ConditionTrue)))).
184+
WithObservedGeneration(true),
185+
ocv1ac.ProgressionProbe().
186+
WithSelector(ocv1ac.ObjectSelector().
187+
WithType(ocv1.SelectorTypeGroupKind).
188+
WithGroupKind(metav1.GroupKind{
189+
Group: corev1.GroupName,
190+
Kind: "PersistentVolumeClaim",
191+
})).
192+
WithAssertions(ocv1ac.Assertion().
193+
WithType(ocv1.ProbeTypeFieldValue).
194+
WithFieldValue(ocv1ac.FieldValueProbe().
195+
WithFieldPath("status.phase").
196+
WithValue(string(corev1.ClaimBound)))),
197+
ocv1ac.ProgressionProbe().
198+
WithSelector(ocv1ac.ObjectSelector().
199+
WithType(ocv1.SelectorTypeGroupKind).
200+
WithGroupKind(metav1.GroupKind{
201+
Group: corev1.GroupName,
202+
Kind: "Namespace",
203+
})).
204+
WithAssertions(ocv1ac.Assertion().
205+
WithType(ocv1.ProbeTypeFieldValue).
206+
WithFieldValue(ocv1ac.FieldValueProbe().
207+
WithFieldPath("status.phase").
208+
WithValue(string(corev1.NamespaceActive)))),
209+
ocv1ac.ProgressionProbe().
210+
WithSelector(ocv1ac.ObjectSelector().
211+
WithType(ocv1.SelectorTypeGroupKind).
212+
WithGroupKind(metav1.GroupKind{
213+
Group: "acme.cert-manager.io",
214+
Kind: "Issuer",
215+
})).
216+
WithAssertions(ocv1ac.Assertion().
217+
WithType(ocv1.ProbeTypeConditionEqual).
218+
WithConditionEqual(
219+
ocv1ac.ConditionEqualProbe().
220+
WithType("Ready").
221+
WithStatus("True"))).
222+
WithObservedGeneration(true),
223+
ocv1ac.ProgressionProbe().
224+
WithSelector(ocv1ac.ObjectSelector().
225+
WithType(ocv1.SelectorTypeGroupKind).
226+
WithGroupKind(metav1.GroupKind{
227+
Group: "acme.cert-manager.io",
228+
Kind: "Certificate",
229+
})).
230+
WithAssertions(ocv1ac.Assertion().
231+
WithType(ocv1.ProbeTypeConditionEqual).
232+
WithConditionEqual(
233+
ocv1ac.ConditionEqualProbe().
234+
WithType("Ready").
235+
WithStatus("True"))).
236+
WithObservedGeneration(true),
237+
ocv1ac.ProgressionProbe().
238+
WithSelector(ocv1ac.ObjectSelector().
239+
WithType(ocv1.SelectorTypeGroupKind).
240+
WithGroupKind(metav1.GroupKind{
241+
Group: "apiextensions.k8s.io",
242+
Kind: "CustomResourceDefinition",
243+
})).
244+
WithAssertions(ocv1ac.Assertion().
245+
WithType(ocv1.ProbeTypeConditionEqual).
246+
WithConditionEqual(
247+
ocv1ac.ConditionEqualProbe().
248+
WithType(string(apiextensions.Established)).
249+
WithStatus(string(corev1.ConditionTrue)))).
250+
WithObservedGeneration(true),
147251
),
148252
)
149253
assert.Equal(t, expected, rev)

0 commit comments

Comments
 (0)