diff --git a/tests-extension/.openshift-tests-extension/openshift_payload_olmv0.json b/tests-extension/.openshift-tests-extension/openshift_payload_olmv0.json index de45643cea..3ad5ce8ed1 100644 --- a/tests-extension/.openshift-tests-extension/openshift_payload_olmv0.json +++ b/tests-extension/.openshift-tests-extension/openshift_payload_olmv0.json @@ -1,6 +1,6 @@ [ { - "name": "[sig-operator][Jira:OLM] OLMv0 within all namespace PolarionID:21418-PolarionID:25679-[OTP][Skipped:Disconnected]Cluster resource created and deleted correctly [Serial]", + "name": "[sig-operator][Jira:OLM] OLMv0 within all namespace PolarionID:21418-PolarionID:25679-[OTP]Cluster resource created and deleted correctly [Serial]", "originalName": "[sig-operator][Jira:OLM] OLMv0 within all namespace PolarionID:21418-PolarionID:25679-[Skipped:Disconnected]Cluster resource created and deleted correctly [Serial]", "labels": { "Extended": {}, @@ -34,7 +34,7 @@ } }, { - "name": "[sig-operator][Jira:OLM] OLMv0 within all namespace PolarionID:21484-PolarionID:21532-[OTP][Skipped:Disconnected]watch special or all namespace by operator group", + "name": "[sig-operator][Jira:OLM] OLMv0 within all namespace PolarionID:21484-PolarionID:21532-[OTP]watch special or all namespace by operator group", "originalName": "[sig-operator][Jira:OLM] OLMv0 within all namespace PolarionID:21484-PolarionID:21532-[Skipped:Disconnected]watch special or all namespace by operator group", "labels": { "Extended": {}, @@ -48,7 +48,7 @@ "environmentSelector": {} }, { - "name": "[sig-operator][Jira:OLM] OLMv0 within all namespace PolarionID:24906-[OTP][Skipped:Disconnected]Operators requesting cluster-scoped permission can trigger kube GC bug[Serial]", + "name": "[sig-operator][Jira:OLM] OLMv0 within all namespace PolarionID:24906-[OTP]Operators requesting cluster-scoped permission can trigger kube GC bug[Serial]", "originalName": "[sig-operator][Jira:OLM] OLMv0 within all namespace PolarionID:24906-[Skipped:Disconnected]Operators requesting cluster-scoped permission can trigger kube GC bug[Serial]", "labels": { "Extended": {}, @@ -348,7 +348,7 @@ "environmentSelector": {} }, { - "name": "[sig-operator][Jira:OLM] OLMv0 with multi ns PolarionID:71119-[OTP][Skipped:Disconnected]pod does not start for installing operator of multi-ns mode when og is in one of the ns[Serial]", + "name": "[sig-operator][Jira:OLM] OLMv0 with multi ns PolarionID:71119-[OTP]pod does not start for installing operator of multi-ns mode when og is in one of the ns[Serial][Slow]", "originalName": "[sig-operator][Jira:OLM] OLMv0 with multi ns PolarionID:71119-[Skipped:Disconnected]pod does not start for installing operator of multi-ns mode when og is in one of the ns[Serial]", "labels": { "Extended": {}, @@ -472,7 +472,7 @@ } }, { - "name": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:23170-[OTP][Skipped:Disconnected]API labels should be hash", + "name": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:23170-[OTP]API labels should be hash", "originalName": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:23170-[Skipped:Disconnected]API labels should be hash", "labels": { "Extended": {}, @@ -486,7 +486,7 @@ "environmentSelector": {} }, { - "name": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:20979-[OTP][Skipped:Disconnected]only one IP is generated", + "name": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:20979-[OTP]only one IP is generated", "originalName": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:20979-[Skipped:Disconnected]only one IP is generated", "labels": { "Extended": {}, @@ -565,7 +565,7 @@ "environmentSelector": {} }, { - "name": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:29723-[OTP][Skipped:Disconnected]As cluster admin find abnormal status condition via components of operator resource", + "name": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:29723-[OTP]As cluster admin find abnormal status condition via components of operator resource", "originalName": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:29723-[Skipped:Disconnected]As cluster admin find abnormal status condition via components of operator resource", "labels": { "Extended": {}, @@ -582,7 +582,7 @@ } }, { - "name": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:30762-[OTP][Skipped:Disconnected]installs bundles with v1 CRDs", + "name": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:30762-[OTP]installs bundles with v1 CRDs", "originalName": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:30762-[Skipped:Disconnected]installs bundles with v1 CRDs", "labels": { "Extended": {}, @@ -596,7 +596,7 @@ "environmentSelector": {} }, { - "name": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:27683-[OTP][Skipped:Disconnected]InstallPlans can install from extracted bundles", + "name": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:27683-[OTP]InstallPlans can install from extracted bundles", "originalName": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:27683-[Skipped:Disconnected]InstallPlans can install from extracted bundles", "labels": { "Extended": {}, @@ -610,7 +610,7 @@ "environmentSelector": {} }, { - "name": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:24513-[OTP][Skipped:Disconnected]Operator config support env only", + "name": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:24513-[OTP]Operator config support env only", "originalName": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:24513-[Skipped:Disconnected]Operator config support env only", "labels": { "Extended": {}, @@ -638,7 +638,7 @@ "environmentSelector": {} }, { - "name": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:25760-[OTP][Skipped:Disconnected]Operator upgrades does not fail after change the channel", + "name": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:25760-[OTP]Operator upgrades does not fail after change the channel", "originalName": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:25760-[Skipped:Disconnected]Operator upgrades does not fail after change the channel", "labels": { "Extended": {}, @@ -714,7 +714,7 @@ } }, { - "name": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:39897-[OTP][Skipped:Disconnected]operator objects should not be recreated after all other associated resources have been deleted[Serial]", + "name": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:39897-[OTP]operator objects should not be recreated after all other associated resources have been deleted[Serial]", "originalName": "[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:39897-[Skipped:Disconnected]operator objects should not be recreated after all other associated resources have been deleted[Serial]", "labels": { "Extended": {}, diff --git a/tests-extension/test/qe/README.md b/tests-extension/test/qe/README.md index 11b321ba9a..205469e98e 100644 --- a/tests-extension/test/qe/README.md +++ b/tests-extension/test/qe/README.md @@ -243,6 +243,52 @@ All migrated test case code needs the following changes to run in the new test f - Do NOT add `[OCPFeatureGate:xxxx]` label 20. **Exclusive**: change to `Serial` +## Disconnected Environment Support for Migrated QE cases + +**IMPORTANT**: With IDMS/ITMS mirror configuration in place, disconnected environments work exactly like connected environments. + +**What this means:** +- Write test cases the same way you would for connected environments +- Create ClusterCatalogs directly - no environment detection needed +- IDMS/ITMS automatically redirects image pulls to mirror registry +- No special helper functions or conditional logic required + +**Image Requirements for Migrated QE Cases:** +- All operator images (bundle, base, index) must be hosted under `quay.io/openshifttest` or `quay.io/olmqe` +- This ensures images are mirrored to disconnected environments via IDMS/ITMS configuration +- Images from other registries will not be available in disconnected clusters + +**Environment Validation for Disconnected-Supporting Migrated Test Cases:** + +**When to use `ValidateAccessEnvironment`:** + +1. **Test cases that create CatalogSource or Subscription**: + - If your test supports disconnected environments (both connected+disconnected, or disconnected-only) + - AND your test creates CatalogSource or Subscription resources + - **MUST** call `ValidateAccessEnvironment(oc)` at the beginning of the test + - This applies to both newly created test cases and migrated test cases + +2. **Test cases that do NOT create both CatalogSource and Subscription**: + - Optional to use `ValidateAccessEnvironment(oc)` + - Using it won't cause errors, but it's not required + - The validation is primarily for ensuring catalog images can be mirrored + +**Usage example:** + +```go +g.It("test case supporting disconnected", func() { + olmv0util.ValidateAccessEnvironment(oc) // MUST call if creating CatalogSource/Subscription + // rest of test code +}) +``` + +**What ValidateAccessEnvironment does:** +- **Proxy clusters**: Returns immediately (no validation needed, proxy provides external access) +- **Connected clusters**: Returns immediately after quick network check (no validation needed) +- **Disconnected clusters**: Validates that ImageTagMirrorSet `image-policy-aosqe` is configured + - If ITMS is configured: Test proceeds normally + - If ITMS is missing: Test is skipped with clear message explaining what's missing + ## Test Automation Code Requirements Consider these requirements when writing and reviewing code: @@ -314,6 +360,10 @@ This ensures Claude Code has access to: ## Local Development Workflow +### Environment Configuration for Migrated QE cases + +**IMPORTANT**: With IDMS/ITMS in place, tests work the same in both connected and disconnected environments. No special configuration is needed. + ### Before Submitting PR 1. **Build and compile**: diff --git a/tests-extension/test/qe/specs/olmv0_allns.go b/tests-extension/test/qe/specs/olmv0_allns.go index 31730c92b3..161ef5b9e6 100644 --- a/tests-extension/test/qe/specs/olmv0_allns.go +++ b/tests-extension/test/qe/specs/olmv0_allns.go @@ -38,7 +38,7 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 within all namespace", func() dr.RmIr(itName) }) - g.It("PolarionID:21418-PolarionID:25679-[OTP][Skipped:Disconnected]Cluster resource created and deleted correctly [Serial]", g.Label("NonHyperShiftHOST"), g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within all namespace PolarionID:21418-PolarionID:25679-[Skipped:Disconnected]Cluster resource created and deleted correctly [Serial]"), func() { + g.It("PolarionID:21418-PolarionID:25679-[OTP]Cluster resource created and deleted correctly [Serial]", g.Label("NonHyperShiftHOST"), g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within all namespace PolarionID:21418-PolarionID:25679-[Skipped:Disconnected]Cluster resource created and deleted correctly [Serial]"), func() { architecture.SkipArchitectures(oc, architecture.PPC64LE, architecture.S390X, architecture.MULTI) exutil.SkipBaselineCaps(oc, "None") exutil.SkipNoCapabilities(oc, "marketplace") @@ -56,6 +56,7 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 within all namespace", func() os.Getenv("HTTP_PROXY") != "" || os.Getenv("HTTPS_PROXY") != "" || os.Getenv("http_proxy") != "" || os.Getenv("https_proxy") != "" { g.Skip("it is not supported") } + olmv0util.ValidateAccessEnvironment(oc) var ( itName = g.CurrentSpecReport().FullText() buildPruningBaseDir = exutil.FixturePath("testdata", "olm") @@ -241,9 +242,10 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 within all namespace", func() olmv0util.NewCheck("expect", exutil.AsAdmin, exutil.WithoutNamespace, exutil.Compare, "Succeeded", exutil.Ok, []string{"csv", subCockroachdb.InstalledCSV, "-n", subCockroachdb.Namespace, "-o=jsonpath={.status.phase}"}).Check(oc) }) - g.It("PolarionID:21484-PolarionID:21532-[OTP][Skipped:Disconnected]watch special or all namespace by operator group", g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within all namespace PolarionID:21484-PolarionID:21532-[Skipped:Disconnected]watch special or all namespace by operator group"), func() { + g.It("PolarionID:21484-PolarionID:21532-[OTP]watch special or all namespace by operator group", g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within all namespace PolarionID:21484-PolarionID:21532-[Skipped:Disconnected]watch special or all namespace by operator group"), func() { architecture.SkipArchitectures(oc, architecture.PPC64LE, architecture.S390X, architecture.MULTI) exutil.SkipNoCapabilities(oc, "marketplace") + olmv0util.ValidateAccessEnvironment(oc) var ( itName = g.CurrentSpecReport().FullText() buildPruningBaseDir = exutil.FixturePath("testdata", "olm") @@ -321,7 +323,7 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 within all namespace", func() }) - g.It("PolarionID:24906-[OTP][Skipped:Disconnected]Operators requesting cluster-scoped permission can trigger kube GC bug[Serial]", g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within all namespace PolarionID:24906-[Skipped:Disconnected]Operators requesting cluster-scoped permission can trigger kube GC bug[Serial]"), func() { + g.It("PolarionID:24906-[OTP]Operators requesting cluster-scoped permission can trigger kube GC bug[Serial]", g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within all namespace PolarionID:24906-[Skipped:Disconnected]Operators requesting cluster-scoped permission can trigger kube GC bug[Serial]"), func() { architecture.SkipArchitectures(oc, architecture.PPC64LE, architecture.S390X, architecture.MULTI) exutil.SkipBaselineCaps(oc, "None") exutil.SkipNoCapabilities(oc, "marketplace") @@ -339,6 +341,7 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 within all namespace", func() os.Getenv("HTTP_PROXY") != "" || os.Getenv("HTTPS_PROXY") != "" || os.Getenv("http_proxy") != "" || os.Getenv("https_proxy") != "" { g.Skip("it is not supported") } + olmv0util.ValidateAccessEnvironment(oc) var ( itName = g.CurrentSpecReport().FullText() buildPruningBaseDir = exutil.FixturePath("testdata", "olm") diff --git a/tests-extension/test/qe/specs/olmv0_multins.go b/tests-extension/test/qe/specs/olmv0_multins.go index dba38e1d11..2c055703b6 100644 --- a/tests-extension/test/qe/specs/olmv0_multins.go +++ b/tests-extension/test/qe/specs/olmv0_multins.go @@ -115,7 +115,7 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 with multi ns", func() { olmv0util.NewCheck("expect", exutil.AsAdmin, exutil.WithoutNamespace, exutil.Contain, "MultiNamespace InstallModeType not supported", exutil.Ok, []string{"csv", sub.InstalledCSV, "-n", sub.Namespace, "-o=jsonpath={.status.message}"}).Check(oc) }) - g.It("PolarionID:71119-[OTP][Skipped:Disconnected]pod does not start for installing operator of multi-ns mode when og is in one of the ns[Serial]", g.Label("NonHyperShiftHOST"), g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 with multi ns PolarionID:71119-[Skipped:Disconnected]pod does not start for installing operator of multi-ns mode when og is in one of the ns[Serial]"), func() { + g.It("PolarionID:71119-[OTP]pod does not start for installing operator of multi-ns mode when og is in one of the ns[Serial][Slow]", g.Label("NonHyperShiftHOST"), g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 with multi ns PolarionID:71119-[Skipped:Disconnected]pod does not start for installing operator of multi-ns mode when og is in one of the ns[Serial]"), func() { exutil.SkipForSNOCluster(oc) exutil.SkipBaselineCaps(oc, "None") exutil.SkipNoCapabilities(oc, "marketplace") @@ -132,6 +132,7 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 with multi ns", func() { os.Getenv("HTTP_PROXY") != "" || os.Getenv("HTTPS_PROXY") != "" || os.Getenv("http_proxy") != "" || os.Getenv("https_proxy") != "" { g.Skip("it is not supported") } + olmv0util.ValidateAccessEnvironment(oc) var ( itName = g.CurrentSpecReport().FullText() buildPruningBaseDir = exutil.FixturePath("testdata", "olm") diff --git a/tests-extension/test/qe/specs/olmv0_nonallns.go b/tests-extension/test/qe/specs/olmv0_nonallns.go index f5ca9f7113..310b320ffb 100644 --- a/tests-extension/test/qe/specs/olmv0_nonallns.go +++ b/tests-extension/test/qe/specs/olmv0_nonallns.go @@ -611,8 +611,9 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 within a namespace", func() { }) // Group 2: OCP-23170 - g.It("PolarionID:23170-[OTP][Skipped:Disconnected]API labels should be hash", g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:23170-[Skipped:Disconnected]API labels should be hash"), func() { + g.It("PolarionID:23170-[OTP]API labels should be hash", g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:23170-[Skipped:Disconnected]API labels should be hash"), func() { architecture.SkipNonAmd64SingleArch(oc) + olmv0util.ValidateAccessEnvironment(oc) var ( itName = g.CurrentSpecReport().FullText() buildPruningBaseDir = exutil.FixturePath("testdata", "olm") @@ -685,7 +686,7 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 within a namespace", func() { }) // Group 3: OCP-20979 - g.It("PolarionID:20979-[OTP][Skipped:Disconnected]only one IP is generated", g.Label("NonHyperShiftHOST"), g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:20979-[Skipped:Disconnected]only one IP is generated"), func() { + g.It("PolarionID:20979-[OTP]only one IP is generated", g.Label("NonHyperShiftHOST"), g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:20979-[Skipped:Disconnected]only one IP is generated"), func() { architecture.SkipNonAmd64SingleArch(oc) if isAks, _ := exutil.IsAKSCluster(context.TODO(), oc); isAks { g.Skip("skip for aks cluster") @@ -706,6 +707,7 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 within a namespace", func() { os.Getenv("HTTP_PROXY") != "" || os.Getenv("HTTPS_PROXY") != "" || os.Getenv("http_proxy") != "" || os.Getenv("https_proxy") != "" { g.Skip("it is not supported") } + olmv0util.ValidateAccessEnvironment(oc) var ( itName = g.CurrentSpecReport().FullText() buildPruningBaseDir = exutil.FixturePath("testdata", "olm") @@ -1114,8 +1116,9 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 within a namespace", func() { }) // OCP-29723 - As cluster admin find abnormal status condition via components of operator resource - g.It("PolarionID:29723-[OTP][Skipped:Disconnected]As cluster admin find abnormal status condition via components of operator resource", g.Label("NonHyperShiftHOST"), g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:29723-[Skipped:Disconnected]As cluster admin find abnormal status condition via components of operator resource"), func() { + g.It("PolarionID:29723-[OTP]As cluster admin find abnormal status condition via components of operator resource", g.Label("NonHyperShiftHOST"), g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:29723-[Skipped:Disconnected]As cluster admin find abnormal status condition via components of operator resource"), func() { architecture.SkipNonAmd64SingleArch(oc) + olmv0util.ValidateAccessEnvironment(oc) var ( itName = g.CurrentSpecReport().FullText() buildPruningBaseDir = exutil.FixturePath("testdata", "olm") @@ -1184,7 +1187,7 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 within a namespace", func() { }) // OCP-30762 - installs bundles with v1 CRDs - g.It("PolarionID:30762-[OTP][Skipped:Disconnected]installs bundles with v1 CRDs", g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:30762-[Skipped:Disconnected]installs bundles with v1 CRDs"), func() { + g.It("PolarionID:30762-[OTP]installs bundles with v1 CRDs", g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:30762-[Skipped:Disconnected]installs bundles with v1 CRDs"), func() { architecture.SkipNonAmd64SingleArch(oc) platform := exutil.CheckPlatform(oc) proxy, errProxy := oc.AsAdmin().WithoutNamespace().Run("get").Args("proxy", "cluster", "-o=jsonpath={.status.httpProxy}{.status.httpsProxy}").Output() @@ -1196,6 +1199,7 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 within a namespace", func() { exutil.Is3MasterNoDedicatedWorkerNode(oc) { g.Skip("it is not supported") } + olmv0util.ValidateAccessEnvironment(oc) var ( itName = g.CurrentSpecReport().FullText() buildPruningBaseDir = exutil.FixturePath("testdata", "olm") @@ -1251,8 +1255,9 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 within a namespace", func() { }) // OCP-27683 - InstallPlans can install from extracted bundles - g.It("PolarionID:27683-[OTP][Skipped:Disconnected]InstallPlans can install from extracted bundles", g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:27683-[Skipped:Disconnected]InstallPlans can install from extracted bundles"), func() { + g.It("PolarionID:27683-[OTP]InstallPlans can install from extracted bundles", g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:27683-[Skipped:Disconnected]InstallPlans can install from extracted bundles"), func() { architecture.SkipNonAmd64SingleArch(oc) + olmv0util.ValidateAccessEnvironment(oc) var ( itName = g.CurrentSpecReport().FullText() buildPruningBaseDir = exutil.FixturePath("testdata", "olm") @@ -1320,12 +1325,13 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 within a namespace", func() { o.Expect(jobBundle).To(o.ContainSubstring(ipBundle)) }) - g.It("PolarionID:24513-[OTP][Skipped:Disconnected]Operator config support env only", g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:24513-[Skipped:Disconnected]Operator config support env only"), func() { + g.It("PolarionID:24513-[OTP]Operator config support env only", g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:24513-[Skipped:Disconnected]Operator config support env only"), func() { architecture.SkipNonAmd64SingleArch(oc) platform := exutil.CheckPlatform(oc) if strings.Contains(platform, "openstack") || strings.Contains(platform, "baremetal") || strings.Contains(platform, "vsphere") || strings.Contains(platform, "none") || exutil.Is3MasterNoDedicatedWorkerNode(oc) { g.Skip("it is not supported") } + olmv0util.ValidateAccessEnvironment(oc) var ( itName = g.CurrentSpecReport().FullText() buildPruningBaseDir = exutil.FixturePath("testdata", "olm") @@ -1518,7 +1524,7 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 within a namespace", func() { }) // Group 8: OCP-25760 + OCP-35895 - g.It("PolarionID:25760-[OTP][Skipped:Disconnected]Operator upgrades does not fail after change the channel", g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:25760-[Skipped:Disconnected]Operator upgrades does not fail after change the channel"), func() { + g.It("PolarionID:25760-[OTP]Operator upgrades does not fail after change the channel", g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:25760-[Skipped:Disconnected]Operator upgrades does not fail after change the channel"), func() { architecture.SkipNonAmd64SingleArch(oc) exutil.SkipForSNOCluster(oc) platform := exutil.CheckPlatform(oc) @@ -1533,6 +1539,7 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 within a namespace", func() { if errFips != nil || strings.Contains(efips, "FIPS mode is enabled") { g.Skip("skip it without impacting function") } + olmv0util.ValidateAccessEnvironment(oc) var ( itName = g.CurrentSpecReport().FullText() buildPruningBaseDir = exutil.FixturePath("testdata", "olm") @@ -2035,7 +2042,7 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 within a namespace", func() { }) // Group 11 - OCP-39897 - g.It("PolarionID:39897-[OTP][Skipped:Disconnected]operator objects should not be recreated after all other associated resources have been deleted[Serial]", g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:39897-[Skipped:Disconnected]operator objects should not be recreated after all other associated resources have been deleted[Serial]"), func() { + g.It("PolarionID:39897-[OTP]operator objects should not be recreated after all other associated resources have been deleted[Serial]", g.Label("original-name:[sig-operator][Jira:OLM] OLMv0 within a namespace PolarionID:39897-[Skipped:Disconnected]operator objects should not be recreated after all other associated resources have been deleted[Serial]"), func() { architecture.SkipNonAmd64SingleArch(oc) if isAks, _ := exutil.IsAKSCluster(context.TODO(), oc); isAks { g.Skip("skip for ask cluster") @@ -2061,6 +2068,7 @@ var _ = g.Describe("[sig-operator][Jira:OLM] OLMv0 within a namespace", func() { os.Getenv("HTTP_PROXY") != "" || os.Getenv("HTTPS_PROXY") != "" || os.Getenv("http_proxy") != "" || os.Getenv("https_proxy") != "" { g.Skip("it is not supported") } + olmv0util.ValidateAccessEnvironment(oc) var ( itName = g.CurrentSpecReport().FullText() buildPruningBaseDir = exutil.FixturePath("testdata", "olm") diff --git a/tests-extension/test/qe/util/architecture/architecture.go b/tests-extension/test/qe/util/architecture/architecture.go index d004c0e058..231a36959c 100644 --- a/tests-extension/test/qe/util/architecture/architecture.go +++ b/tests-extension/test/qe/util/architecture/architecture.go @@ -80,10 +80,10 @@ func SkipNonAmd64SingleArch(oc *exutil.CLI) Architecture { func getNodeArchitectures(oc *exutil.CLI) []string { output, err := oc.WithoutNamespace().AsAdmin().Run("get").Args("nodes", "-o=jsonpath={.items[*].status.nodeInfo.architecture}").Output() if err != nil { - e2e.Failf("unable to get cluster node architectures: %v", err) + g.Skip(fmt.Sprintf("unable to get cluster node architectures: %v", err)) } if output == "" { - e2e.Failf("no nodes found or architecture information missing") + g.Skip("no nodes found or architecture information missing") } return strings.Fields(output) // Use Fields instead of Split to handle multiple spaces } @@ -97,7 +97,7 @@ func getNodeArchitectures(oc *exutil.CLI) []string { func GetAvailableArchitecturesSet(oc *exutil.CLI) []Architecture { architectureStrings := getNodeArchitectures(oc) if len(architectureStrings) == 0 { - e2e.Failf("no node architectures found") + g.Skip("no node architectures found") } // Use map for deduplication with Architecture as key @@ -199,7 +199,7 @@ func (a Architecture) String() string { func ClusterArchitecture(oc *exutil.CLI) Architecture { architectureStrings := getNodeArchitectures(oc) if len(architectureStrings) == 0 { - e2e.Failf("no node architectures found") + g.Skip("no node architectures found") } // Filter out empty strings and convert to Architecture @@ -211,7 +211,7 @@ func ClusterArchitecture(oc *exutil.CLI) Architecture { } if len(architectures) == 0 { - e2e.Failf("no valid node architectures found") + g.Skip("no valid node architectures found") } // Check if all architectures are the same @@ -267,7 +267,7 @@ func GetControlPlaneArch(oc *exutil.CLI) Architecture { architectureStr = strings.TrimSpace(architectureStr) if architectureStr == "" { - e2e.Failf("Control plane node %s has no architecture information", masterNode) + g.Skip(fmt.Sprintf("Control plane node %s has no architecture information", masterNode)) } return FromString(architectureStr) diff --git a/tests-extension/test/qe/util/olmv0util/helper.go b/tests-extension/test/qe/util/olmv0util/helper.go index 6d5af83ab2..755a157485 100644 --- a/tests-extension/test/qe/util/olmv0util/helper.go +++ b/tests-extension/test/qe/util/olmv0util/helper.go @@ -794,6 +794,142 @@ func GetMetrics(oc *exutil.CLI, olmToken string, data PrometheusQueryResult, met return metrics } +// HasExternalNetworkAccess tests network connectivity from a cluster master node +// by attempting to access an external container registry (quay.io). +// This method uses DebugNodeWithChroot to avoid creating pods and pulling images, +// which would fail in disconnected environments. +// +// Parameters: +// - oc: CLI client for interacting with the OpenShift cluster +// +// Returns: +// - bool: true if external network access is available, false otherwise +func HasExternalNetworkAccess(oc *exutil.CLI) bool { + if oc == nil { + e2e.Logf("CLI client is nil, assuming connected environment") + return true + } + + e2e.Logf("Testing external network connectivity from master node using DebugNodeWithChroot") + + masterNode, masterErr := exutil.GetFirstMasterNode(oc) + if masterErr != nil { + e2e.Logf("Failed to get master node: %v", masterErr) + g.Skip(fmt.Sprintf("Cannot determine network connectivity: %v", masterErr)) + } + + // Test connectivity to quay.io (container registry) + // Use timeout to avoid hanging, and redirect output to check connection status + // Note: In disconnected environments, curl will fail and bash will return non-zero exit code, + // causing DebugNodeWithChroot to return an error. We ignore this error and rely on output checking. + cmd := `timeout 10 curl -k https://quay.io > /dev/null 2>&1; [ $? -eq 0 ] && echo "connected"` + output, _ := exutil.DebugNodeWithOptionsAndChroot(oc, masterNode, []string{"--to-namespace=default"}, "bash", "-c", cmd) + + // Check if the output contains "connected" + // - Connected environment: curl succeeds -> echo "connected" -> output contains "connected" + // - Disconnected environment: curl fails -> no echo -> output empty or only debug messages + if strings.Contains(output, "connected") { + e2e.Logf("External network connectivity test succeeded (output: %s), cluster can access quay.io", strings.TrimSpace(output)) + return true + } + + e2e.Logf("External network connectivity test failed (output: %s), cluster cannot access quay.io", strings.TrimSpace(output)) + return false +} + +// IsProxyCluster checks whether the cluster is configured with HTTP/HTTPS proxy. +// Proxy clusters are treated as connected environments since they can access external networks through the proxy. +// +// Parameters: +// - oc: CLI client for interacting with the OpenShift cluster +// +// Returns: +// - true if cluster has HTTP or HTTPS proxy configured in status +// - false if no proxy is configured +// +// Behavior: +// - Skips the test if oc is nil or if error occurs while checking proxy configuration +func IsProxyCluster(oc *exutil.CLI) bool { + if oc == nil { + e2e.Logf("CLI client is nil, cannot check proxy configuration") + g.Skip("CLI client is nil, cannot check proxy configuration") + } + + // Get proxy status in one call to check both httpProxy and httpsProxy + // Format: {"httpProxy":"","httpsProxy":""} + proxyStatus, err := oc.AsAdmin().WithoutNamespace().Run("get").Args("proxy", "cluster", "-o=jsonpath={.status}").Output() + if err != nil { + e2e.Logf("Failed to get proxy status: %v", err) + g.Skip(fmt.Sprintf("cannot get proxy status: %v", err)) + } + + // If either httpProxy or httpsProxy is configured, the status will contain http + // Connected cluster status is empty "{}" + // Proxy cluster status contains "httpProxy" or "httpsProxy" fields with non-empty values + if strings.Contains(proxyStatus, "httpProxy") || strings.Contains(proxyStatus, "httpsProxy") { + e2e.Logf("Proxy cluster detected") + return true + } + + e2e.Logf("No proxy configuration detected in cluster (status=%s)", proxyStatus) + return false +} + +// ValidateAccessEnvironment checks if the cluster is in a disconnected environment +// and validates that required mirror configurations (ImageTagMirrorSet) are present. +// This should be called at the beginning of test cases that support disconnected environments. +// +// The function recognizes three types of cluster network access: +// 1. Connected: Direct access to external networks (no proxy, no disconnected) +// 2. Proxy: Access through HTTP/HTTPS proxy (treated as connected) +// 3. Disconnected: No external access, requires ImageTagMirrorSet for image mirroring +// +// Parameters: +// - oc: CLI client for interacting with the OpenShift cluster +// +// Behavior: +// - Skips the test if master node cannot be accessed (cannot determine environment) +// - Returns immediately if proxy cluster detected (no mirror validation needed) +// - Skips the test if in disconnected environment but ImageTagMirrorSet is not configured +// - Continues normally if in connected environment or disconnected with proper configuration +// +// Usage: +// +// g.It("test case supporting disconnected", func() { +// olmv0util.ValidateAccessEnvironment(oc) +// // rest of test code +// }) +func ValidateAccessEnvironment(oc *exutil.CLI) { + // First check if this is a proxy cluster + // Proxy clusters can access external networks through proxy, so they don't need mirror validation + if IsProxyCluster(oc) { + e2e.Logf("Proxy cluster detected, treating as connected environment (no mirror validation needed)") + return + } + + // Check if we can access external network directly + hasNetwork := HasExternalNetworkAccess(oc) + + // If connected (and not proxy, already checked above), no validation needed + if hasNetwork { + e2e.Logf("Cluster has external network access (connected environment), no mirror validation needed") + return + } + + // In disconnected environment (not proxy, no external access), check for required ImageTagMirrorSet + e2e.Logf("Cluster is in disconnected environment, validating ImageTagMirrorSet configuration") + + // Check if ImageTagMirrorSet "image-policy-aosqe" exists + itmsOutput, itmsErr := oc.AsAdmin().WithoutNamespace().Run("get").Args("imagetagmirrorset", "image-policy-aosqe", "--ignore-not-found").Output() + if itmsErr != nil || !strings.Contains(itmsOutput, "image-policy-aosqe") { + g.Skip(fmt.Sprintf("Disconnected environment detected but ImageTagMirrorSet 'image-policy-aosqe' is not configured. "+ + "This test requires proper mirror configuration to run in disconnected clusters. "+ + "ITMS check result: output=%q, error=%v", itmsOutput, itmsErr)) + } + + e2e.Logf("Disconnected environment validation passed: ImageTagMirrorSet 'image-policy-aosqe' is configured") +} + // IsIPv6 check if the string is an IPv6 address. func IsIPv6(str string) bool { ip := net.ParseIP(str)