From 55b35242df6e307bf3954560a3d76116dd528474 Mon Sep 17 00:00:00 2001 From: Daniel Franz Date: Mon, 23 Mar 2026 23:46:54 +0900 Subject: [PATCH] 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 --- api/v1/clusterextensionrevision_types.go | 18 ++- api/v1/zz_generated.deepcopy.go | 5 + .../api/v1/progressionprobe.go | 14 +++ ...ramework.io_clusterextensionrevisions.yaml | 7 ++ .../operator-controller/applier/boxcutter.go | 116 +++++++++++++++++- .../applier/boxcutter_test.go | 106 +++++++++++++++- .../clusterextensionrevision_controller.go | 101 ++------------- manifests/experimental-e2e.yaml | 7 ++ manifests/experimental.yaml | 7 ++ test/e2e/features/revision.feature | 22 ++++ 10 files changed, 310 insertions(+), 93 deletions(-) diff --git a/api/v1/clusterextensionrevision_types.go b/api/v1/clusterextensionrevision_types.go index 208d96d9f..a81f15601 100644 --- a/api/v1/clusterextensionrevision_types.go +++ b/api/v1/clusterextensionrevision_types.go @@ -162,6 +162,15 @@ type ProgressionProbe struct { // +required // Assertions []Assertion `json:"assertions,omitempty"` + + // observedGeneration is a flag which, when true, ensures that status.observedGeneration is equal to + // .metadata.generation before running the given assertions on the selected object(s). If a probed object does + // not contain the status.observedGeneration field, the given prober is executed directly. + // + // +optional + // +kubebuilder:default:=false + // + ObservedGeneration *bool `json:"observedGeneration,omitempty"` } // ObjectSelector is a discriminated union which defines the method by which we select objects to make assertions against. @@ -227,9 +236,14 @@ const ( type ProbeType string const ( - ProbeTypeFieldCondition ProbeType = "ConditionEqual" - ProbeTypeFieldEqual ProbeType = "FieldsEqual" + ProbeTypeConditionEqual ProbeType = "ConditionEqual" + ProbeTypeFieldsEqual ProbeType = "FieldsEqual" ProbeTypeFieldValue ProbeType = "FieldValue" + + // Deprecated: use ProbeTypeConditionEqual instead. + ProbeTypeFieldCondition = ProbeTypeConditionEqual + // Deprecated: use ProbeTypeFieldsEqual instead. + ProbeTypeFieldEqual = ProbeTypeFieldsEqual ) // Assertion is a discriminated union which defines the probe type and definition used as an assertion. diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 9c801ca77..52d9f49dd 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -677,6 +677,11 @@ func (in *ProgressionProbe) DeepCopyInto(out *ProgressionProbe) { *out = make([]Assertion, len(*in)) copy(*out, *in) } + if in.ObservedGeneration != nil { + in, out := &in.ObservedGeneration, &out.ObservedGeneration + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProgressionProbe. diff --git a/applyconfigurations/api/v1/progressionprobe.go b/applyconfigurations/api/v1/progressionprobe.go index c3c3f9f8c..16f9f0aec 100644 --- a/applyconfigurations/api/v1/progressionprobe.go +++ b/applyconfigurations/api/v1/progressionprobe.go @@ -37,6 +37,12 @@ type ProgressionProbeApplyConfiguration struct { // // Assertions []AssertionApplyConfiguration `json:"assertions,omitempty"` + // observedGeneration is a flag which, when true, ensures that status.observedGeneration is equal to + // .metadata.generation before running the given assertions on the selected object(s). If a probed object does + // not contain the status.observedGeneration field, the given prober is executed directly. + // + // + ObservedGeneration *bool `json:"observedGeneration,omitempty"` } // ProgressionProbeApplyConfiguration constructs a declarative configuration of the ProgressionProbe type for use with @@ -65,3 +71,11 @@ func (b *ProgressionProbeApplyConfiguration) WithAssertions(values ...*Assertion } return b } + +// WithObservedGeneration sets the ObservedGeneration field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ObservedGeneration field is set to the value of the last call. +func (b *ProgressionProbeApplyConfiguration) WithObservedGeneration(value bool) *ProgressionProbeApplyConfiguration { + b.ObservedGeneration = &value + return b +} diff --git a/helm/olmv1/base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensionrevisions.yaml b/helm/olmv1/base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensionrevisions.yaml index 491f8327d..dca9186f3 100644 --- a/helm/olmv1/base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensionrevisions.yaml +++ b/helm/olmv1/base/operator-controller/crd/experimental/olm.operatorframework.io_clusterextensionrevisions.yaml @@ -364,6 +364,13 @@ spec: minItems: 1 type: array x-kubernetes-list-type: atomic + observedGeneration: + default: false + description: |- + observedGeneration is a flag which, when true, ensures that status.observedGeneration is equal to + .metadata.generation before running the given assertions on the selected object(s). If a probed object does + not contain the status.observedGeneration field, the given prober is executed directly. + type: boolean selector: description: |- selector is a required field which defines the method by which we select objects to apply the below diff --git a/internal/operator-controller/applier/boxcutter.go b/internal/operator-controller/applier/boxcutter.go index cb4da7e53..6937aab0d 100644 --- a/internal/operator-controller/applier/boxcutter.go +++ b/internal/operator-controller/applier/boxcutter.go @@ -14,6 +14,9 @@ import ( "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/storage/driver" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -208,10 +211,12 @@ func (r *SimpleRevisionGenerator) buildClusterExtensionRevision( annotations[labels.ServiceAccountNamespaceKey] = ext.Spec.Namespace phases := PhaseSort(objects) + progressionProbes := defaultProgressionProbes() spec := ocv1ac.ClusterExtensionRevisionSpec(). WithLifecycleState(ocv1.ClusterExtensionRevisionLifecycleStateActive). - WithPhases(phases...) + WithPhases(phases...). + WithProgressionProbes(progressionProbes...) if p := ext.Spec.ProgressDeadlineMinutes; p > 0 { spec.WithProgressDeadlineMinutes(p) } @@ -602,6 +607,115 @@ func latestRevisionNumber(prevRevisions []ocv1.ClusterExtensionRevision) int64 { return prevRevisions[len(prevRevisions)-1].Spec.Revision } +func defaultProgressionProbes() []*ocv1ac.ProgressionProbeApplyConfiguration { + crdProbe := ocv1ac.ProgressionProbe(). + WithSelector(ocv1ac.ObjectSelector(). + WithType(ocv1.SelectorTypeGroupKind). + WithGroupKind(metav1.GroupKind{ + Group: "apiextensions.k8s.io", + Kind: "CustomResourceDefinition", + })). + WithAssertions(ocv1ac.Assertion(). + WithType(ocv1.ProbeTypeConditionEqual). + WithConditionEqual( + ocv1ac.ConditionEqualProbe(). + WithType(string(apiextensions.Established)). + WithStatus(string(corev1.ConditionTrue)))). + WithObservedGeneration(true) + + // Checks if the Type: "Ready" Condition is "True" + readyConditionAssertion := ocv1ac.Assertion(). + WithType(ocv1.ProbeTypeConditionEqual). + WithConditionEqual( + ocv1ac.ConditionEqualProbe(). + WithType("Ready"). + WithStatus("True")) + + certProbe := ocv1ac.ProgressionProbe(). + WithSelector(ocv1ac.ObjectSelector(). + WithType(ocv1.SelectorTypeGroupKind). + WithGroupKind(metav1.GroupKind{ + Group: "acme.cert-manager.io", + Kind: "Certificate", + })). + WithAssertions(readyConditionAssertion). + WithObservedGeneration(true) + issuerProbe := ocv1ac.ProgressionProbe(). + WithSelector(ocv1ac.ObjectSelector(). + WithType(ocv1.SelectorTypeGroupKind). + WithGroupKind(metav1.GroupKind{ + Group: "acme.cert-manager.io", + Kind: "Issuer", + })). + WithAssertions(readyConditionAssertion). + WithObservedGeneration(true) + + // namespaceActiveProbe is a probe which asserts that the namespace is in "Active" phase + namespaceActiveProbe := ocv1ac.ProgressionProbe(). + WithSelector(ocv1ac.ObjectSelector(). + WithType(ocv1.SelectorTypeGroupKind). + WithGroupKind(metav1.GroupKind{ + Group: corev1.GroupName, + Kind: "Namespace", + })). + WithAssertions(ocv1ac.Assertion(). + WithType(ocv1.ProbeTypeFieldValue). + WithFieldValue(ocv1ac.FieldValueProbe(). + WithFieldPath("status.phase"). + WithValue(string(corev1.NamespaceActive)))) + + // pvcBoundProbe is a probe which asserts that the PVC is in "Bound" phase + pvcBoundProbe := ocv1ac.ProgressionProbe(). + WithSelector(ocv1ac.ObjectSelector(). + WithType(ocv1.SelectorTypeGroupKind). + WithGroupKind(metav1.GroupKind{ + Group: corev1.GroupName, + Kind: "PersistentVolumeClaim", + })). + WithAssertions(ocv1ac.Assertion(). + WithType(ocv1.ProbeTypeFieldValue). + WithFieldValue(ocv1ac.FieldValueProbe(). + WithFieldPath("status.phase"). + WithValue(string(corev1.ClaimBound)))) + + // Checks if the Type: "Available" Condition is "True". + availableConditionAssertion := ocv1ac.Assertion(). + WithType(ocv1.ProbeTypeConditionEqual). + WithConditionEqual(ocv1ac.ConditionEqualProbe(). + WithType(string(appsv1.DeploymentAvailable)). + WithStatus(string(corev1.ConditionTrue))) + + // Checks if status.updatedReplicas == status.replicas. + // Works for StatefulSets, Deployments and ReplicaSets. + replicasUpdatedAssertion := ocv1ac.Assertion(). + WithType(ocv1.ProbeTypeFieldsEqual). + WithFieldsEqual(ocv1ac.FieldsEqualProbe(). + WithFieldA("status.updatedReplicas"). + WithFieldB("status.replicas")) + + statefulSetProbe := ocv1ac.ProgressionProbe().WithSelector( + ocv1ac.ObjectSelector().WithType(ocv1.SelectorTypeGroupKind). + WithGroupKind(metav1.GroupKind{ + Group: appsv1.GroupName, + Kind: "StatefulSet", + }), + ).WithAssertions(replicasUpdatedAssertion, availableConditionAssertion). + WithObservedGeneration(true) + + deploymentProbe := ocv1ac.ProgressionProbe().WithSelector( + ocv1ac.ObjectSelector().WithType(ocv1.SelectorTypeGroupKind). + WithGroupKind(metav1.GroupKind{ + Group: appsv1.GroupName, + Kind: "Deployment", + }), + ).WithAssertions(replicasUpdatedAssertion, availableConditionAssertion). + WithObservedGeneration(true) + + return []*ocv1ac.ProgressionProbeApplyConfiguration{ + deploymentProbe, statefulSetProbe, pvcBoundProbe, namespaceActiveProbe, issuerProbe, certProbe, crdProbe, + } +} + func splitManifestDocuments(file string) []string { // Estimate: typical manifests have ~50-100 lines per document // Pre-allocate for reasonable bundle size to reduce allocations diff --git a/internal/operator-controller/applier/boxcutter_test.go b/internal/operator-controller/applier/boxcutter_test.go index 4f8461250..f99d744f2 100644 --- a/internal/operator-controller/applier/boxcutter_test.go +++ b/internal/operator-controller/applier/boxcutter_test.go @@ -18,6 +18,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apierrors "k8s.io/apimachinery/pkg/api/errors" apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -143,7 +144,110 @@ func Test_SimpleRevisionGenerator_GenerateRevisionFromHelmRelease(t *testing.T) }, }, }), - ), + )). + WithProgressionProbes( + ocv1ac.ProgressionProbe().WithSelector( + ocv1ac.ObjectSelector().WithType(ocv1.SelectorTypeGroupKind). + WithGroupKind(metav1.GroupKind{ + Group: appsv1.GroupName, + Kind: "Deployment", + })). + WithAssertions( + ocv1ac.Assertion(). + WithType(ocv1.ProbeTypeFieldsEqual). + WithFieldsEqual(ocv1ac.FieldsEqualProbe(). + WithFieldA("status.updatedReplicas"). + WithFieldB("status.replicas")), + ocv1ac.Assertion(). + WithType(ocv1.ProbeTypeConditionEqual). + WithConditionEqual(ocv1ac.ConditionEqualProbe(). + WithType(string(appsv1.DeploymentAvailable)). + WithStatus(string(corev1.ConditionTrue)))). + WithObservedGeneration(true), + ocv1ac.ProgressionProbe().WithSelector( + ocv1ac.ObjectSelector().WithType(ocv1.SelectorTypeGroupKind). + WithGroupKind(metav1.GroupKind{ + Group: appsv1.GroupName, + Kind: "StatefulSet", + })). + WithAssertions( + ocv1ac.Assertion(). + WithType(ocv1.ProbeTypeFieldsEqual). + WithFieldsEqual(ocv1ac.FieldsEqualProbe(). + WithFieldA("status.updatedReplicas"). + WithFieldB("status.replicas")), + ocv1ac.Assertion(). + WithType(ocv1.ProbeTypeConditionEqual). + WithConditionEqual(ocv1ac.ConditionEqualProbe(). + WithType(string(appsv1.DeploymentAvailable)). + WithStatus(string(corev1.ConditionTrue)))). + WithObservedGeneration(true), + ocv1ac.ProgressionProbe(). + WithSelector(ocv1ac.ObjectSelector(). + WithType(ocv1.SelectorTypeGroupKind). + WithGroupKind(metav1.GroupKind{ + Group: corev1.GroupName, + Kind: "PersistentVolumeClaim", + })). + WithAssertions(ocv1ac.Assertion(). + WithType(ocv1.ProbeTypeFieldValue). + WithFieldValue(ocv1ac.FieldValueProbe(). + WithFieldPath("status.phase"). + WithValue(string(corev1.ClaimBound)))), + ocv1ac.ProgressionProbe(). + WithSelector(ocv1ac.ObjectSelector(). + WithType(ocv1.SelectorTypeGroupKind). + WithGroupKind(metav1.GroupKind{ + Group: corev1.GroupName, + Kind: "Namespace", + })). + WithAssertions(ocv1ac.Assertion(). + WithType(ocv1.ProbeTypeFieldValue). + WithFieldValue(ocv1ac.FieldValueProbe(). + WithFieldPath("status.phase"). + WithValue(string(corev1.NamespaceActive)))), + ocv1ac.ProgressionProbe(). + WithSelector(ocv1ac.ObjectSelector(). + WithType(ocv1.SelectorTypeGroupKind). + WithGroupKind(metav1.GroupKind{ + Group: "acme.cert-manager.io", + Kind: "Issuer", + })). + WithAssertions(ocv1ac.Assertion(). + WithType(ocv1.ProbeTypeConditionEqual). + WithConditionEqual( + ocv1ac.ConditionEqualProbe(). + WithType("Ready"). + WithStatus("True"))). + WithObservedGeneration(true), + ocv1ac.ProgressionProbe(). + WithSelector(ocv1ac.ObjectSelector(). + WithType(ocv1.SelectorTypeGroupKind). + WithGroupKind(metav1.GroupKind{ + Group: "acme.cert-manager.io", + Kind: "Certificate", + })). + WithAssertions(ocv1ac.Assertion(). + WithType(ocv1.ProbeTypeConditionEqual). + WithConditionEqual( + ocv1ac.ConditionEqualProbe(). + WithType("Ready"). + WithStatus("True"))). + WithObservedGeneration(true), + ocv1ac.ProgressionProbe(). + WithSelector(ocv1ac.ObjectSelector(). + WithType(ocv1.SelectorTypeGroupKind). + WithGroupKind(metav1.GroupKind{ + Group: "apiextensions.k8s.io", + Kind: "CustomResourceDefinition", + })). + WithAssertions(ocv1ac.Assertion(). + WithType(ocv1.ProbeTypeConditionEqual). + WithConditionEqual( + ocv1ac.ConditionEqualProbe(). + WithType(string(apiextensions.Established)). + WithStatus(string(corev1.ConditionTrue)))). + WithObservedGeneration(true), ), ) assert.Equal(t, expected, rev) diff --git a/internal/operator-controller/controllers/clusterextensionrevision_controller.go b/internal/operator-controller/controllers/clusterextensionrevision_controller.go index 2e9d2fce6..8924134b9 100644 --- a/internal/operator-controller/controllers/clusterextensionrevision_controller.go +++ b/internal/operator-controller/controllers/clusterextensionrevision_controller.go @@ -10,9 +10,6 @@ import ( "strings" "time" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -479,9 +476,7 @@ func (c *ClusterExtensionRevisionReconciler) buildBoxcutterPhases(ctx context.Co opts := []boxcutter.RevisionReconcileOption{ boxcutter.WithPreviousOwners(previousObjs), - boxcutter.WithProbe(boxcutter.ProgressProbeType, probing.And{ - &namespaceActiveProbe, deploymentProbe, statefulSetProbe, crdProbe, issuerProbe, certProbe, &pvcBoundProbe, progressionProbes, - }), + boxcutter.WithProbe(boxcutter.ProgressProbeType, progressionProbes), } phases := make([]boxcutter.Phase, 0) @@ -535,10 +530,10 @@ func buildProgressionProbes(progressionProbes []ocv1.ProgressionProbe) (probing. for _, probe := range progressionProbe.Assertions { switch probe.Type { // Switch based on the union discriminator - case ocv1.ProbeTypeFieldCondition: + case ocv1.ProbeTypeConditionEqual: conditionProbe := probing.ConditionProbe(probe.ConditionEqual) assertions = append(assertions, &conditionProbe) - case ocv1.ProbeTypeFieldEqual: + case ocv1.ProbeTypeFieldsEqual: fieldsEqualProbe := probing.FieldsEqualProbe(probe.FieldsEqual) assertions = append(assertions, &fieldsEqualProbe) case ocv1.ProbeTypeFieldValue: @@ -548,6 +543,13 @@ func buildProgressionProbes(progressionProbes []ocv1.ProgressionProbe) (probing. return nil, fmt.Errorf("unknown progressionProbe assertion probe type: %s", probe.Type) } } + var prober probing.Prober + if progressionProbe.ObservedGeneration != nil && *progressionProbe.ObservedGeneration { + // Wrap the and-ed probes within an ObservedGeneration probe + prober = &probing.ObservedGenerationProbe{Prober: assertions} + } else { + prober = &assertions + } // Create the selector probe based on user-requested type and provide the assertions var selectorProbe probing.Prober @@ -556,7 +558,7 @@ func buildProgressionProbes(progressionProbes []ocv1.ProgressionProbe) (probing. case ocv1.SelectorTypeGroupKind: selectorProbe = &probing.GroupKindSelector{ GroupKind: schema.GroupKind(progressionProbe.Selector.GroupKind), - Prober: assertions, + Prober: prober, } case ocv1.SelectorTypeLabel: selector, err := metav1.LabelSelectorAsSelector(&progressionProbe.Selector.Label) @@ -565,7 +567,7 @@ func buildProgressionProbes(progressionProbes []ocv1.ProgressionProbe) (probing. } selectorProbe = &probing.LabelSelector{ Selector: selector, - Prober: assertions, + Prober: prober, } default: return nil, fmt.Errorf("unknown progressionProbe selector type: %s", progressionProbe.Selector.Type) @@ -575,85 +577,6 @@ func buildProgressionProbes(progressionProbes []ocv1.ProgressionProbe) (probing. return userProbes, nil } -var ( - deploymentProbe = &probing.GroupKindSelector{ - GroupKind: schema.GroupKind{Group: appsv1.GroupName, Kind: "Deployment"}, - Prober: deplStatefulSetProbe, - } - statefulSetProbe = &probing.GroupKindSelector{ - GroupKind: schema.GroupKind{Group: appsv1.GroupName, Kind: "StatefulSet"}, - Prober: deplStatefulSetProbe, - } - crdProbe = &probing.GroupKindSelector{ - GroupKind: schema.GroupKind{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"}, - Prober: &probing.ObservedGenerationProbe{ - Prober: &probing.ConditionProbe{ // "Available" == "True" - Type: string(apiextensions.Established), - Status: string(corev1.ConditionTrue), - }, - }, - } - certProbe = &probing.GroupKindSelector{ - GroupKind: schema.GroupKind{Group: "acme.cert-manager.io", Kind: "Certificate"}, - Prober: &probing.ObservedGenerationProbe{ - Prober: readyConditionProbe, - }, - } - issuerProbe = &probing.GroupKindSelector{ - GroupKind: schema.GroupKind{Group: "acme.cert-manager.io", Kind: "Issuer"}, - Prober: &probing.ObservedGenerationProbe{ - Prober: readyConditionProbe, - }, - } - - // namespaceActiveProbe is a probe which asserts that the namespace is in "Active" phase - namespaceActiveProbe = probing.GroupKindSelector{ - GroupKind: schema.GroupKind{Group: corev1.GroupName, Kind: "Namespace"}, - Prober: &applier.FieldValueProbe{ - FieldPath: "status.phase", - Value: "Active", - }, - } - - // pvcBoundProbe is a probe which asserts that the PVC is in "Bound" phase - pvcBoundProbe = probing.GroupKindSelector{ - GroupKind: schema.GroupKind{Group: corev1.GroupName, Kind: "PersistentVolumeClaim"}, - Prober: &applier.FieldValueProbe{ - FieldPath: "status.phase", - Value: "Bound", - }, - } - - // deplStaefulSetProbe probes Deployment, StatefulSet objects. - deplStatefulSetProbe = &probing.ObservedGenerationProbe{ - Prober: probing.And{ - availableConditionProbe, - replicasUpdatedProbe, - }, - } - - // Checks if the Type: "Available" Condition is "True". - availableConditionProbe = &probing.ConditionProbe{ // "Available" == "True" - Type: string(appsv1.DeploymentAvailable), - Status: string(corev1.ConditionTrue), - } - - // Checks if the Type: "Ready" Condition is "True" - readyConditionProbe = &probing.ObservedGenerationProbe{ - Prober: &probing.ConditionProbe{ - Type: "Ready", - Status: "True", - }, - } - - // Checks if .status.updatedReplicas == .status.replicas. - // Works for StatefulSts, Deployments and ReplicaSets. - replicasUpdatedProbe = &probing.FieldsEqualProbe{ - FieldA: ".status.updatedReplicas", - FieldB: ".status.replicas", - } -) - func setRetryingConditions(cer *ocv1.ClusterExtensionRevision, message string) { markAsProgressing(cer, ocv1.ClusterExtensionRevisionReasonRetrying, message) if meta.FindStatusCondition(cer.Status.Conditions, ocv1.ClusterExtensionRevisionTypeAvailable) != nil { diff --git a/manifests/experimental-e2e.yaml b/manifests/experimental-e2e.yaml index 55a4e157c..0ed83c4ab 100644 --- a/manifests/experimental-e2e.yaml +++ b/manifests/experimental-e2e.yaml @@ -976,6 +976,13 @@ spec: minItems: 1 type: array x-kubernetes-list-type: atomic + observedGeneration: + default: false + description: |- + observedGeneration is a flag which, when true, ensures that status.observedGeneration is equal to + .metadata.generation before running the given assertions on the selected object(s). If a probed object does + not contain the status.observedGeneration field, the given prober is executed directly. + type: boolean selector: description: |- selector is a required field which defines the method by which we select objects to apply the below diff --git a/manifests/experimental.yaml b/manifests/experimental.yaml index 8604a7db8..0eb664be7 100644 --- a/manifests/experimental.yaml +++ b/manifests/experimental.yaml @@ -937,6 +937,13 @@ spec: minItems: 1 type: array x-kubernetes-list-type: atomic + observedGeneration: + default: false + description: |- + observedGeneration is a flag which, when true, ensures that status.observedGeneration is equal to + .metadata.generation before running the given assertions on the selected object(s). If a probed object does + not contain the status.observedGeneration field, the given prober is executed directly. + type: boolean selector: description: |- selector is a required field which defines the method by which we select objects to apply the below diff --git a/test/e2e/features/revision.feature b/test/e2e/features/revision.feature index 755762a8a..f2c2c7fa2 100644 --- a/test/e2e/features/revision.feature +++ b/test/e2e/features/revision.feature @@ -20,6 +20,17 @@ Feature: Install ClusterExtensionRevision spec: lifecycleState: Active collisionProtection: Prevent + progressionProbes: + - selector: + type: GroupKind + groupKind: + group: "" + kind: PersistentVolumeClaim + assertions: + - type: FieldValue + fieldValue: + fieldPath: "status.phase" + value: "Bound" phases: - name: pvc objects: @@ -72,6 +83,17 @@ Feature: Install ClusterExtensionRevision spec: lifecycleState: Active collisionProtection: Prevent + progressionProbes: + - selector: + type: GroupKind + groupKind: + group: "" + kind: PersistentVolumeClaim + assertions: + - type: FieldValue + fieldValue: + fieldPath: "status.phase" + value: "Bound" phases: - name: pvc objects: