Skip to content

Commit 5d51a84

Browse files
🌱 Add e2e tests for catalog deletion resilience
These tests verify what should happen when catalogs are deleted while Helm extensions are running. 4 Test Scenarios Added: Scenario 1: Extension continues running after catalog deletion Given: Helm extension is installed and running When: Catalog is deleted Then: Extension keeps running, resources stay available Scenario 2: Resources are restored after catalog deletion Given: Helm extension is installed When: Catalog is deleted AND someone deletes a resource Then: Resource is automatically restored Scenario 3: Config changes work without catalog Given: Helm extension is installed When: Catalog is deleted AND user changes preflight config Then: Config change is accepted Scenario 4: Version upgrade blocked without catalog Given: Helm extension v1.0.0 is installed When: Catalog is deleted AND user requests upgrade to v1.0.1 Then: Upgrade is blocked (Retrying status) And: Extension stays on v1.0.0 Test Infrastructure Added: - CatalogIsDeleted() step function - Proper timeout and cleanup handling Note: These tests will FAIL until the fix is applied. This commit captures the desired behavior first.
1 parent 1fa4169 commit 5d51a84

2 files changed

Lines changed: 190 additions & 0 deletions

File tree

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
Feature: Workload resilience when catalog is deleted
2+
3+
As an OLM user, I want my installed extensions to continue working
4+
even if the catalog they were installed from is deleted.
5+
6+
Background:
7+
Given OLM is available
8+
And ClusterCatalog "test" serves bundles
9+
And ServiceAccount "olm-sa" with needed permissions is available in ${TEST_NAMESPACE}
10+
11+
# STANDARD RUNTIME TESTS
12+
13+
Scenario: Extension continues running after catalog deletion
14+
Given ClusterExtension is applied
15+
"""
16+
apiVersion: olm.operatorframework.io/v1
17+
kind: ClusterExtension
18+
metadata:
19+
name: ${NAME}
20+
spec:
21+
namespace: ${TEST_NAMESPACE}
22+
serviceAccount:
23+
name: olm-sa
24+
source:
25+
sourceType: Catalog
26+
catalog:
27+
packageName: test
28+
selector:
29+
matchLabels:
30+
"olm.operatorframework.io/metadata.name": test-catalog
31+
"""
32+
And ClusterExtension is rolled out
33+
And ClusterExtension is available
34+
And resource "deployment/test-operator" is available
35+
And resource "configmap/test-configmap" is available
36+
When ClusterCatalog "test" is deleted
37+
Then resource "deployment/test-operator" is available
38+
And resource "configmap/test-configmap" is available
39+
And ClusterExtension reports Installed as True
40+
41+
Scenario: Resources are restored after catalog deletion
42+
Given ClusterExtension is applied
43+
"""
44+
apiVersion: olm.operatorframework.io/v1
45+
kind: ClusterExtension
46+
metadata:
47+
name: ${NAME}
48+
spec:
49+
namespace: ${TEST_NAMESPACE}
50+
serviceAccount:
51+
name: olm-sa
52+
source:
53+
sourceType: Catalog
54+
catalog:
55+
packageName: test
56+
selector:
57+
matchLabels:
58+
"olm.operatorframework.io/metadata.name": test-catalog
59+
"""
60+
And ClusterExtension is rolled out
61+
And ClusterExtension is available
62+
And resource "configmap/test-configmap" exists
63+
When ClusterCatalog "test" is deleted
64+
And resource "configmap/test-configmap" is removed
65+
Then resource "configmap/test-configmap" is eventually restored
66+
67+
Scenario: Config changes work without catalog
68+
Given ClusterExtension is applied
69+
"""
70+
apiVersion: olm.operatorframework.io/v1
71+
kind: ClusterExtension
72+
metadata:
73+
name: ${NAME}
74+
spec:
75+
namespace: ${TEST_NAMESPACE}
76+
serviceAccount:
77+
name: olm-sa
78+
source:
79+
sourceType: Catalog
80+
catalog:
81+
packageName: test
82+
selector:
83+
matchLabels:
84+
"olm.operatorframework.io/metadata.name": test-catalog
85+
"""
86+
And ClusterExtension is rolled out
87+
And ClusterExtension is available
88+
When ClusterCatalog "test" is deleted
89+
And ClusterExtension is updated to add preflight config
90+
"""
91+
apiVersion: olm.operatorframework.io/v1
92+
kind: ClusterExtension
93+
metadata:
94+
name: ${NAME}
95+
spec:
96+
namespace: ${TEST_NAMESPACE}
97+
serviceAccount:
98+
name: olm-sa
99+
install:
100+
preflight:
101+
crdUpgradeSafety:
102+
enforcement: None
103+
source:
104+
sourceType: Catalog
105+
catalog:
106+
packageName: test
107+
selector:
108+
matchLabels:
109+
"olm.operatorframework.io/metadata.name": test-catalog
110+
"""
111+
Then ClusterExtension reports Installed as True
112+
113+
Scenario: Version upgrade blocked without catalog
114+
Given ClusterExtension is applied
115+
"""
116+
apiVersion: olm.operatorframework.io/v1
117+
kind: ClusterExtension
118+
metadata:
119+
name: ${NAME}
120+
spec:
121+
namespace: ${TEST_NAMESPACE}
122+
serviceAccount:
123+
name: olm-sa
124+
source:
125+
sourceType: Catalog
126+
catalog:
127+
packageName: test
128+
version: "1.0.0"
129+
selector:
130+
matchLabels:
131+
"olm.operatorframework.io/metadata.name": test-catalog
132+
"""
133+
And ClusterExtension is rolled out
134+
And ClusterExtension is available
135+
And bundle "test-operator.1.0.0" is installed in version "1.0.0"
136+
When ClusterCatalog "test" is deleted
137+
And ClusterExtension is updated to change version
138+
"""
139+
apiVersion: olm.operatorframework.io/v1
140+
kind: ClusterExtension
141+
metadata:
142+
name: ${NAME}
143+
spec:
144+
namespace: ${TEST_NAMESPACE}
145+
serviceAccount:
146+
name: olm-sa
147+
source:
148+
sourceType: Catalog
149+
catalog:
150+
packageName: test
151+
version: "1.0.1"
152+
selector:
153+
matchLabels:
154+
"olm.operatorframework.io/metadata.name": test-catalog
155+
"""
156+
Then ClusterExtension reports Progressing as True with Reason Retrying
157+
And bundle "test-operator.1.0.0" is installed in version "1.0.0"

test/e2e/steps/steps.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ func RegisterSteps(sc *godog.ScenarioContext) {
8585
sc.Step(`^(?i)ClusterCatalog "([^"]+)" serves bundles$`, CatalogServesBundles)
8686
sc.Step(`^"([^"]+)" catalog image version "([^"]+)" is also tagged as "([^"]+)"$`, TagCatalogImage)
8787
sc.Step(`^(?i)ClusterCatalog "([^"]+)" image version "([^"]+)" is also tagged as "([^"]+)"$`, TagCatalogImage)
88+
sc.Step(`^(?i)ClusterCatalog "([^"]+)" is deleted$`, CatalogIsDeleted)
8889

8990
sc.Step(`^(?i)operator "([^"]+)" target namespace is "([^"]+)"$`, OperatorTargetNamespace)
9091
sc.Step(`^(?i)Prometheus metrics are returned in the response$`, PrometheusMetricsAreReturned)
@@ -664,6 +665,38 @@ func TagCatalogImage(name, oldTag, newTag string) error {
664665
return crane.Tag(imageRef, newTag, crane.Insecure)
665666
}
666667

668+
func CatalogIsDeleted(ctx context.Context, catalogName string) error {
669+
catalogFullName := fmt.Sprintf("%s-catalog", catalogName)
670+
_, err := k8sClient("delete", "clustercatalog", catalogFullName, "--ignore-not-found=true")
671+
if err != nil {
672+
return fmt.Errorf("failed to delete catalog: %v", err)
673+
}
674+
675+
// Wait for catalog to be fully deleted, respecting context cancellation.
676+
ticker := time.NewTicker(tick)
677+
defer ticker.Stop()
678+
679+
deadline := time.Now().Add(timeout)
680+
for {
681+
select {
682+
case <-ctx.Done():
683+
// Return the context error if we timed out or were cancelled while waiting.
684+
return ctx.Err()
685+
case <-ticker.C:
686+
// Check if we've exceeded the timeout
687+
if time.Now().After(deadline) {
688+
return fmt.Errorf("timeout waiting for catalog %s to be deleted", catalogFullName)
689+
}
690+
691+
_, err := k8sClient("get", "clustercatalog", catalogFullName)
692+
if err != nil {
693+
// Catalog is deleted - verification succeeded
694+
return nil
695+
}
696+
}
697+
}
698+
}
699+
667700
func PrometheusMetricsAreReturned(ctx context.Context) error {
668701
sc := scenarioCtx(ctx)
669702
for podName, mr := range sc.metricsResponse {

0 commit comments

Comments
 (0)