Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,10 @@ type Sourcerer interface {
}

func (c *ClusterExtensionRevisionReconciler) SetupWithManager(mgr ctrl.Manager) error {
// Initialize probes once at setup time
if err := initializeProbes(); err != nil {
return err
}
skipProgressDeadlineExceededPredicate := predicate.Funcs{
UpdateFunc: func(e event.UpdateEvent) bool {
rev, ok := e.ObjectNew.(*ocv1.ClusterExtensionRevision)
Expand Down Expand Up @@ -465,7 +469,7 @@ func (c *ClusterExtensionRevisionReconciler) toBoxcutterRevision(ctx context.Con
opts := []boxcutter.RevisionReconcileOption{
boxcutter.WithPreviousOwners(previousObjs),
boxcutter.WithProbe(boxcutter.ProgressProbeType, probing.And{
deploymentProbe, statefulSetProbe, crdProbe, issuerProbe, certProbe,
&namespaceActiveProbe, deploymentProbe, statefulSetProbe, crdProbe, issuerProbe, certProbe, &pvcBoundProbe,
}),
}

Expand Down Expand Up @@ -511,6 +515,28 @@ func EffectiveCollisionProtection(cp ...ocv1.CollisionProtection) ocv1.Collision
return ecp
}

// initializeProbes is used to initialize CEL probes at startup time, so we don't recreate them on every reconcile
func initializeProbes() error {
nsCEL, err := probing.NewCELProbe(namespaceActiveCEL, `namespace phase must be "Active"`)
if err != nil {
return fmt.Errorf("constructing namespace CEL probe: %w", err)
}
pvcCEL, err := probing.NewCELProbe(pvcBoundCEL, `persistentvolumeclaim phase must be "Bound"`)
if err != nil {
return fmt.Errorf("constructing PVC CEL probe: %w", err)
}
namespaceActiveProbe = probing.GroupKindSelector{
GroupKind: schema.GroupKind{Group: corev1.GroupName, Kind: "Namespace"},
Prober: nsCEL,
}
pvcBoundProbe = probing.GroupKindSelector{
GroupKind: schema.GroupKind{Group: corev1.GroupName, Kind: "PersistentVolumeClaim"},
Prober: pvcCEL,
}

return nil
}

var (
deploymentProbe = &probing.GroupKindSelector{
GroupKind: schema.GroupKind{Group: appsv1.GroupName, Kind: "Deployment"},
Expand Down Expand Up @@ -542,6 +568,14 @@ var (
},
}

// namespaceActiveCEL is a CEL rule which asserts that the namespace is in "Active" phase
namespaceActiveCEL = `self.status.phase == "Active"`
namespaceActiveProbe probing.GroupKindSelector

// pvcBoundCEL is a CEL rule which asserts that the PVC is in "Bound" phase
pvcBoundCEL = `self.status.phase == "Bound"`
pvcBoundProbe probing.GroupKindSelector

// deplStaefulSetProbe probes Deployment, StatefulSet objects.
deplStatefulSetProbe = &probing.ObservedGenerationProbe{
Prober: probing.And{
Expand Down
1 change: 1 addition & 0 deletions test/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ Leverage existing steps for common operations:
Use these variables in YAML templates:

- `${NAME}`: Scenario-specific ClusterExtension name (e.g., `ce-123`)
- `${CER_NAME}`: Scenario-specific ClusterExtensionRevision name (e.g., `cer-123`; for applying CERs directly)
- `${TEST_NAMESPACE}`: Scenario-specific namespace (e.g., `ns-123`)
- `${CATALOG_IMG}`: Catalog image reference (defaults to in-cluster registry, overridable via `CATALOG_IMG` env var)

Expand Down
204 changes: 204 additions & 0 deletions test/e2e/features/revision.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
Feature: Install ClusterExtensionRevision

As an OLM user I would like to install a cluster extension revision.

Background:
Given OLM is available
And ServiceAccount "olm-sa" with needed permissions is available in ${TEST_NAMESPACE}

@BoxcutterRuntime
Scenario: Install simple revision
When ClusterExtensionRevision is applied
"""
apiVersion: olm.operatorframework.io/v1
kind: ClusterExtensionRevision
metadata:
annotations:
olm.operatorframework.io/service-account-name: olm-sa
olm.operatorframework.io/service-account-namespace: ${TEST_NAMESPACE}
name: ${CER_NAME}
spec:
lifecycleState: Active
collisionProtection: Prevent
phases:
- name: policies
objects:
- object:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-operator-network-policy
namespace: ${TEST_NAMESPACE}
spec:
podSelector: {}
policyTypes:
- Ingress
- name: deploy
objects:
- object:
apiVersion: v1
data:
httpd.sh: |
#!/bin/sh
echo "Version 1.2.0"
echo true > /var/www/started
echo true > /var/www/ready
echo true > /var/www/live
exec httpd -f -h /var/www -p 80
kind: ConfigMap
metadata:
name: httpd-script
namespace: ${TEST_NAMESPACE}
- object:
apiVersion: v1
data:
name: test-configmap
version: v1.2.0
kind: ConfigMap
metadata:
annotations:
shouldNotTemplate: |
The namespace is {{ $labels.namespace }}. The templated $labels.namespace is NOT expected to be processed by OLM's rendering engine for registry+v1 bundles.
name: test-configmap
namespace: ${TEST_NAMESPACE}
revision: 1
"""

And ClusterExtensionRevision "${CER_NAME}" reports Progressing as True with Reason Succeeded
And ClusterExtensionRevision "${CER_NAME}" reports Available as True with Reason ProbesSucceeded
And resource "networkpolicy/test-operator-network-policy" is installed
And resource "configmap/test-configmap" is installed

@BoxcutterRuntime
Scenario: Probe failure for PersistentVolumeClaim halts phase progression
When ClusterExtensionRevision is applied
"""
apiVersion: olm.operatorframework.io/v1
kind: ClusterExtensionRevision
metadata:
annotations:
olm.operatorframework.io/service-account-name: olm-sa
olm.operatorframework.io/service-account-namespace: ${TEST_NAMESPACE}
name: ${CER_NAME}
spec:
lifecycleState: Active
collisionProtection: Prevent
phases:
- name: pvc
objects:
- object:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
namespace: ${TEST_NAMESPACE}
spec:
accessModes:
- ReadWriteOnce
storageClassName: ""
volumeName: test-pv
resources:
requests:
storage: 1Mi
- name: configmap
objects:
- object:
apiVersion: v1
kind: ConfigMap
metadata:
annotations:
shouldNotTemplate: |
The namespace is {{ $labels.namespace }}. The templated $labels.namespace is NOT expected to be processed by OLM's rendering engine for registry+v1 bundles.
name: test-configmap
namespace: ${TEST_NAMESPACE}
data:
name: test-configmap
version: v1.2.0
revision: 1
"""

And resource "persistentvolumeclaim/test-pvc" is installed
And ClusterExtensionRevision "${CER_NAME}" reports Available as False with Reason ProbeFailure
And resource "configmap/test-configmap" is not installed

@BoxcutterRuntime
Scenario: Phases progress when PersistentVolumeClaim becomes "Bound"
When ClusterExtensionRevision is applied
"""
apiVersion: olm.operatorframework.io/v1
kind: ClusterExtensionRevision
metadata:
annotations:
olm.operatorframework.io/service-account-name: olm-sa
olm.operatorframework.io/service-account-namespace: ${TEST_NAMESPACE}
name: ${CER_NAME}
spec:
lifecycleState: Active
collisionProtection: Prevent
phases:
- name: pvc
objects:
- object:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
namespace: ${TEST_NAMESPACE}
spec:
accessModes:
- ReadWriteOnce
storageClassName: ""
volumeName: test-pv
resources:
requests:
storage: 1Mi
- object:
apiVersion: v1
kind: PersistentVolume
metadata:
name: test-pv
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 1Mi
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: test-pvc
namespace: ${TEST_NAMESPACE}
persistentVolumeReclaimPolicy: Delete
storageClassName: ""
volumeMode: Filesystem
local:
path: /tmp/persistent-volume
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- operator-controller-e2e-control-plane
- name: configmap
objects:
- object:
apiVersion: v1
kind: ConfigMap
metadata:
annotations:
shouldNotTemplate: |
The namespace is {{ $labels.namespace }}. The templated $labels.namespace is NOT expected to be processed by OLM's rendering engine for registry+v1 bundles.
name: test-configmap
namespace: ${TEST_NAMESPACE}
data:
name: test-configmap
version: v1.2.0
revision: 1
"""

And ClusterExtensionRevision "${CER_NAME}" reports Progressing as True with Reason Succeeded
And ClusterExtensionRevision "${CER_NAME}" reports Available as True with Reason ProbesSucceeded
And resource "persistentvolume/test-pv" is installed
And resource "persistentvolumeclaim/test-pvc" is installed
And resource "configmap/test-configmap" is installed
35 changes: 20 additions & 15 deletions test/e2e/steps/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ type resource struct {
}

type scenarioContext struct {
id string
namespace string
clusterExtensionName string
removedResources []unstructured.Unstructured
backGroundCmds []*exec.Cmd
metricsResponse map[string]string
id string
namespace string
clusterExtensionName string
clusterExtensionRevisionName string
removedResources []unstructured.Unstructured
backGroundCmds []*exec.Cmd
metricsResponse map[string]string

extensionObjects []client.Object
}
Expand Down Expand Up @@ -142,9 +143,10 @@ func CheckFeatureTags(ctx context.Context, sc *godog.Scenario) (context.Context,

func CreateScenarioContext(ctx context.Context, sc *godog.Scenario) (context.Context, error) {
scCtx := &scenarioContext{
id: sc.Id,
namespace: fmt.Sprintf("ns-%s", sc.Id),
clusterExtensionName: fmt.Sprintf("ce-%s", sc.Id),
id: sc.Id,
namespace: fmt.Sprintf("ns-%s", sc.Id),
clusterExtensionName: fmt.Sprintf("ce-%s", sc.Id),
clusterExtensionRevisionName: fmt.Sprintf("cer-%s", sc.Id),
}
return context.WithValue(ctx, scenarioContextKey, scCtx), nil
}
Expand Down Expand Up @@ -176,13 +178,16 @@ func ScenarioCleanup(ctx context.Context, _ *godog.Scenario, err error) (context
if sc.clusterExtensionName != "" {
forDeletion = append(forDeletion, resource{name: sc.clusterExtensionName, kind: "clusterextension"})
}
if sc.clusterExtensionRevisionName != "" {
forDeletion = append(forDeletion, resource{name: sc.clusterExtensionRevisionName, kind: "clusterextensionrevision"})
}
forDeletion = append(forDeletion, resource{name: sc.namespace, kind: "namespace"})
go func() {
for _, r := range forDeletion {
if _, err := k8sClient("delete", r.kind, r.name, "--ignore-not-found=true"); err != nil {
logger.Info("Error deleting resource", "name", r.name, "namespace", sc.namespace, "stderr", stderrOutput(err))
for _, r := range forDeletion {
go func(res resource) {
if _, err := k8sClient("delete", res.kind, res.name, "--ignore-not-found=true"); err != nil {
logger.Info("Error deleting resource", "name", res.name, "namespace", sc.namespace, "stderr", stderrOutput(err))
}
}
}()
}(r)
}
return ctx, nil
}
8 changes: 6 additions & 2 deletions test/e2e/steps/steps.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func RegisterSteps(sc *godog.ScenarioContext) {
sc.Step(`^(?i)ClusterExtension reports ([[:alnum:]]+) as ([[:alnum:]]+)$`, ClusterExtensionReportsConditionWithoutReason)
sc.Step(`^(?i)ClusterExtensionRevision "([^"]+)" reports ([[:alnum:]]+) as ([[:alnum:]]+) with Reason ([[:alnum:]]+)$`, ClusterExtensionRevisionReportsConditionWithoutMsg)
sc.Step(`^(?i)ClusterExtension reports ([[:alnum:]]+) transition between (\d+) and (\d+) minutes since its creation$`, ClusterExtensionReportsConditionTransitionTime)
sc.Step(`^(?i)ClusterExtensionRevision is applied(?:\s+.*)?$`, ResourceIsApplied)
sc.Step(`^(?i)ClusterExtensionRevision "([^"]+)" is archived$`, ClusterExtensionRevisionIsArchived)
sc.Step(`^(?i)ClusterExtensionRevision "([^"]+)" contains annotation "([^"]+)" with value$`, ClusterExtensionRevisionHasAnnotationWithValue)
sc.Step(`^(?i)ClusterExtensionRevision "([^"]+)" has label "([^"]+)" with value "([^"]+)"$`, ClusterExtensionRevisionHasLabelWithValue)
Expand All @@ -80,7 +81,7 @@ func RegisterSteps(sc *godog.ScenarioContext) {
sc.Step(`^(?i)resource "([^"]+)" is installed$`, ResourceAvailable)
sc.Step(`^(?i)resource "([^"]+)" is available$`, ResourceAvailable)
sc.Step(`^(?i)resource "([^"]+)" is removed$`, ResourceRemoved)
sc.Step(`^(?i)resource "([^"]+)" is eventually not found$`, ResourceEventuallyNotFound)
sc.Step(`^(?i)resource "([^"]+)" is (?:eventually not found|not installed)$`, ResourceEventuallyNotFound)
sc.Step(`^(?i)resource "([^"]+)" exists$`, ResourceAvailable)
sc.Step(`^(?i)resource is applied$`, ResourceIsApplied)
sc.Step(`^(?i)resource "deployment/test-operator" reports as (not ready|ready)$`, MarkTestOperatorNotReady)
Expand Down Expand Up @@ -186,6 +187,7 @@ func substituteScenarioVars(content string, sc *scenarioContext) string {
vars := map[string]string{
"TEST_NAMESPACE": sc.namespace,
"NAME": sc.clusterExtensionName,
"CER_NAME": sc.clusterExtensionRevisionName,
"CATALOG_IMG": "docker-registry.operator-controller-e2e.svc.cluster.local:5000/e2e/test-catalog:v1",
}
if v, found := os.LookupEnv("CATALOG_IMG"); found {
Expand Down Expand Up @@ -246,10 +248,12 @@ func ResourceIsApplied(ctx context.Context, yamlTemplate *godog.DocString) error
}
out, err := k8scliWithInput(yamlContent, "apply", "-f", "-")
if err != nil {
return fmt.Errorf("failed to apply resource %v %w", out, err)
return fmt.Errorf("failed to apply resource %v; err: %w; stderr: %s", out, err, stderrOutput(err))
}
if res.GetKind() == "ClusterExtension" {
sc.clusterExtensionName = res.GetName()
} else if res.GetKind() == "ClusterExtensionRevision" {
sc.clusterExtensionRevisionName = res.GetName()
}
return nil
}
Expand Down
2 changes: 2 additions & 0 deletions test/e2e/steps/testdata/rbac-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ rules:
- serviceaccounts
- events
- namespaces
- persistentvolumes
- persistentvolumeclaims
verbs: [update, create, list, watch, get, delete, patch]
- apiGroups: ["apps"]
resources:
Expand Down
Loading