Skip to content

Commit a3d8bce

Browse files
Per G. da Silvaclaude
andcommitted
🌱 Add e2e test for collision protection with higher-revision ClusterObjectSet
Verify that a conflicting ClusterExtension with a higher-revision ClusterObjectSet does not take over resources owned by the original ClusterExtension. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 72f2dd3 commit a3d8bce

2 files changed

Lines changed: 116 additions & 0 deletions

File tree

test/e2e/features/install.feature

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,3 +678,100 @@ Feature: Install ClusterExtension
678678
Then ClusterExtension is available
679679
And ClusterExtension reports Progressing as True with Reason Succeeded
680680
And ClusterExtension reports Installed as True
681+
682+
@BoxcutterRuntime
683+
@DeploymentConfig
684+
Scenario: A conflicting ClusterExtension with a higher revision does not take over resources from the original owner
685+
Given a catalog "test" with packages:
686+
| package | version | channel | replaces | contents |
687+
| test | 1.0.0 | alpha | | CRD, Deployment, ConfigMap |
688+
And ServiceAccount "olm-sa" with needed permissions is available in test namespace
689+
# Install the first ClusterExtension and verify it is installed with a single ClusterObjectSet
690+
When ClusterExtension is applied
691+
"""
692+
apiVersion: olm.operatorframework.io/v1
693+
kind: ClusterExtension
694+
metadata:
695+
name: ${NAME}
696+
spec:
697+
namespace: ${TEST_NAMESPACE}
698+
serviceAccount:
699+
name: olm-sa
700+
source:
701+
sourceType: Catalog
702+
catalog:
703+
packageName: ${PACKAGE:test}
704+
version: 1.0.0
705+
selector:
706+
matchLabels:
707+
"olm.operatorframework.io/metadata.name": ${CATALOG:test}
708+
"""
709+
Then ClusterExtension is rolled out
710+
And ClusterExtension is available
711+
And ClusterExtension "${NAME}" has 1 ClusterObjectSet
712+
# Track the first ClusterExtension for cleanup before applying the second one
713+
And the current ClusterExtension is tracked for cleanup
714+
# Apply a second ClusterExtension pointing to the same package and version
715+
When ClusterExtension is applied
716+
"""
717+
apiVersion: olm.operatorframework.io/v1
718+
kind: ClusterExtension
719+
metadata:
720+
name: ${NAME}-dup
721+
spec:
722+
namespace: ${TEST_NAMESPACE}
723+
serviceAccount:
724+
name: olm-sa
725+
source:
726+
sourceType: Catalog
727+
catalog:
728+
packageName: ${PACKAGE:test}
729+
version: 1.0.0
730+
selector:
731+
matchLabels:
732+
"olm.operatorframework.io/metadata.name": ${CATALOG:test}
733+
"""
734+
# The second ClusterExtension should be retrying because of resource collisions
735+
Then ClusterExtension reports Progressing as True with Reason Retrying and Message includes:
736+
"""
737+
revision object collisions
738+
"""
739+
# Update the conflicting ClusterExtension with a deployment config env var. At the time
740+
# of writing, there is no other way to get the operator controller to generate another
741+
# ClusterObjectSet by simply targeting another bundle version in the ClusterExtension.
742+
# Since nothing has been installed yet for this CE, the resolver returns the currently
743+
# installed bundle. Injecting an environment variable via DeploymentConfig changes the
744+
# desired state, which causes the controller to create a second ClusterObjectSet
745+
# (revision 2) after the first one collides.
746+
When ClusterExtension is updated
747+
"""
748+
apiVersion: olm.operatorframework.io/v1
749+
kind: ClusterExtension
750+
metadata:
751+
name: ${NAME}
752+
spec:
753+
namespace: ${TEST_NAMESPACE}
754+
serviceAccount:
755+
name: olm-sa
756+
config:
757+
configType: Inline
758+
inline:
759+
deploymentConfig:
760+
env:
761+
- name: MY_VAR
762+
value: "my-value"
763+
source:
764+
sourceType: Catalog
765+
catalog:
766+
packageName: ${PACKAGE:test}
767+
version: 1.0.0
768+
selector:
769+
matchLabels:
770+
"olm.operatorframework.io/metadata.name": ${CATALOG:test}
771+
"""
772+
Then ClusterExtension "${NAME}" has 2 ClusterObjectSets
773+
# The higher-revision COS (revision 2) should also collide, not take over resources
774+
And ClusterExtension reports Progressing as True with Reason Retrying and Message includes:
775+
"""
776+
revision object collisions
777+
"""

test/e2e/steps/steps.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ func RegisterSteps(sc *godog.ScenarioContext) {
183183
sc.Step(`^(?i)min value for (ClusterExtension|ClusterObjectSet) ((?:\.[a-zA-Z]+)+) is set to (\d+)$`, SetCRDFieldMinValue)
184184

185185
sc.Step(`^(?i)the current ClusterExtension is tracked for cleanup$`, TrackCurrentClusterExtensionForCleanup)
186+
sc.Step(`^(?i)ClusterExtension "([^"]+)" has (\d+) ClusterObjectSets?$`, ClusterExtensionHasClusterObjectSets)
186187

187188
// TLS profile enforcement steps — deployment configuration
188189
sc.Step(`^(?i)the "([^"]+)" deployment is configured with custom TLS minimum version "([^"]+)"$`, ConfigureDeploymentWithCustomTLSVersion)
@@ -393,6 +394,24 @@ func TrackCurrentClusterExtensionForCleanup(ctx context.Context) error {
393394
return nil
394395
}
395396

397+
// ClusterExtensionHasClusterObjectSets waits for the named ClusterExtension to own exactly the
398+
// expected number of ClusterObjectSets. Polls with timeout.
399+
func ClusterExtensionHasClusterObjectSets(ctx context.Context, extName string, expectedCount int) error {
400+
sc := scenarioCtx(ctx)
401+
extName = substituteScenarioVars(extName, sc)
402+
waitFor(ctx, func() bool {
403+
out, err := k8sClient("get", "clusterobjectsets",
404+
"-l", fmt.Sprintf("olm.operatorframework.io/owner-name=%s", extName),
405+
"-o", "jsonpath={.items[*].metadata.name}")
406+
if err != nil {
407+
return false
408+
}
409+
names := strings.Fields(strings.TrimSpace(out))
410+
return len(names) == expectedCount
411+
})
412+
return nil
413+
}
414+
396415
// ClusterExtensionVersionUpdate patches the ClusterExtension's catalog version to the specified value.
397416
func ClusterExtensionVersionUpdate(ctx context.Context, version string) error {
398417
sc := scenarioCtx(ctx)

0 commit comments

Comments
 (0)