Skip to content

Commit cf1f826

Browse files
committed
NE-2292: Add Gateway API OLM to NO-OLM migration upgrade test
Add upgrade test validating Gateway API migration from OLM-based Istio to CIO-managed Sail Library during 4.21 to 4.22 upgrades. Setup creates Gateway/HTTPRoute with OLM provisioning and tests connectivity. Test validates migration: Gateway remains programmed, Istiod running, Istio CRDs stay OLM-managed, GatewayClass has CIO finalizer, Istio CR deleted, subscription persists. Teardown cleans up all resources.
1 parent c10db65 commit cf1f826

File tree

3 files changed

+544
-120
lines changed

3 files changed

+544
-120
lines changed

test/e2e/upgrade/upgrade.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import (
4444
"github.com/openshift/origin/test/e2e/upgrade/manifestdelete"
4545
mc "github.com/openshift/origin/test/extended/machine_config"
4646
"github.com/openshift/origin/test/extended/prometheus"
47+
"github.com/openshift/origin/test/extended/router"
4748
"github.com/openshift/origin/test/extended/util/disruption"
4849
"github.com/openshift/origin/test/extended/util/operator"
4950
)
@@ -69,6 +70,7 @@ func AllTests() []upgrades.Test {
6970
&prometheus.ImagePullsAreFast{},
7071
&prometheus.MetricsAvailableAfterUpgradeTest{},
7172
&dns.UpgradeTest{},
73+
&router.GatewayAPIUpgradeTest{},
7274
}
7375
}
7476

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
package router
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
"time"
8+
9+
g "github.com/onsi/ginkgo/v2"
10+
o "github.com/onsi/gomega"
11+
12+
exutil "github.com/openshift/origin/test/extended/util"
13+
14+
apierrors "k8s.io/apimachinery/pkg/api/errors"
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
"k8s.io/apimachinery/pkg/util/wait"
17+
e2e "k8s.io/kubernetes/test/e2e/framework"
18+
"k8s.io/kubernetes/test/e2e/upgrades"
19+
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
20+
)
21+
22+
// GatewayAPIUpgradeTest verifies that Gateway API resources work during upgrade,
23+
// whether using OLM-based or CIO-based (NO-OLM) provisioning
24+
type GatewayAPIUpgradeTest struct {
25+
oc *exutil.CLI
26+
namespace string
27+
gatewayName string
28+
routeName string
29+
hostname string
30+
startedWithNoOLM bool // tracks if GatewayAPIWithoutOLM was enabled at start
31+
loadBalancerSupported bool
32+
managedDNS bool
33+
}
34+
35+
func (t *GatewayAPIUpgradeTest) Name() string {
36+
return "gateway-api-upgrade"
37+
}
38+
39+
func (t *GatewayAPIUpgradeTest) DisplayName() string {
40+
return "[sig-network-edge][Feature:Router][apigroup:gateway.networking.k8s.io] Verify Gateway API functionality during upgrade"
41+
}
42+
43+
// Setup creates Gateway and HTTPRoute resources and tests connectivity
44+
func (t *GatewayAPIUpgradeTest) Setup(ctx context.Context, f *e2e.Framework) {
45+
g.By("Setting up Gateway API upgrade test")
46+
47+
t.oc = exutil.NewCLIWithFramework(f).AsAdmin()
48+
t.namespace = f.Namespace.Name
49+
t.gatewayName = "upgrade-test-gateway"
50+
t.routeName = "test-httproute"
51+
52+
// Check platform support and get capabilities (LoadBalancer, DNS)
53+
t.loadBalancerSupported, t.managedDNS = checkPlatformSupportAndGetCapabilities(t.oc)
54+
55+
g.By("Checking if GatewayAPIWithoutOLM feature gate is enabled before upgrade")
56+
t.startedWithNoOLM = isNoOLMFeatureGateEnabled(t.oc)
57+
58+
// Skip on clusters missing OLM/Marketplace capabilities if starting with OLM mode
59+
if !t.startedWithNoOLM {
60+
exutil.SkipIfMissingCapabilities(t.oc, olmCapabilities...)
61+
}
62+
63+
if t.startedWithNoOLM {
64+
e2e.Logf("Starting with GatewayAPIWithoutOLM enabled (NO-OLM mode)")
65+
} else {
66+
e2e.Logf("Starting with OLM-based Gateway API provisioning")
67+
}
68+
69+
g.By("Creating default GatewayClass to trigger Gateway API installation")
70+
gatewayClassControllerName := "openshift.io/gateway-controller/v1"
71+
gatewayClass := buildGatewayClass(gatewayClassName, gatewayClassControllerName)
72+
_, err := t.oc.AdminGatewayApiClient().GatewayV1().GatewayClasses().Create(ctx, gatewayClass, metav1.CreateOptions{})
73+
if err != nil && !apierrors.IsAlreadyExists(err) {
74+
e2e.Failf("Failed to create GatewayClass %q: %v", gatewayClassName, err)
75+
}
76+
77+
g.By("Waiting for GatewayClass to be accepted")
78+
err = checkGatewayClassCondition(t.oc, gatewayClassName, string(gatewayv1.GatewayClassConditionStatusAccepted), metav1.ConditionTrue)
79+
o.Expect(err).NotTo(o.HaveOccurred(), "GatewayClass %q was not accepted", gatewayClassName)
80+
81+
g.By("Getting the default domain")
82+
defaultIngressDomain, err := getDefaultIngressClusterDomainName(t.oc, time.Minute)
83+
o.Expect(err).NotTo(o.HaveOccurred(), "Failed to find default domain name")
84+
customDomain := strings.Replace(defaultIngressDomain, "apps.", "gw-upgrade.", 1)
85+
t.hostname = "test-upgrade." + customDomain
86+
87+
g.By("Creating Gateway")
88+
_, err = createAndCheckGateway(t.oc, t.gatewayName, gatewayClassName, customDomain, t.loadBalancerSupported)
89+
o.Expect(err).NotTo(o.HaveOccurred(), "Failed to create Gateway")
90+
91+
g.By("Verify the gateway's LoadBalancer service and DNSRecords")
92+
if t.loadBalancerSupported {
93+
assertGatewayLoadbalancerReady(t.oc, t.gatewayName, t.gatewayName+"-openshift-default")
94+
}
95+
96+
if t.managedDNS {
97+
assertDNSRecordStatus(t.oc, t.gatewayName)
98+
}
99+
100+
if !t.startedWithNoOLM {
101+
g.By("Validating OLM-based provisioning before upgrade")
102+
validateOLMBasedOSSM(t.oc, 20*time.Minute)
103+
e2e.Logf("GatewayAPI resources successfully created with OLM-based provisioning")
104+
} else {
105+
g.By("Validating CIO-based (NO-OLM) provisioning before upgrade")
106+
t.validateCIOProvisioning(ctx, false) // false = no migration occurred
107+
e2e.Logf("GatewayAPI resources successfully created with CIO-based (NO-OLM) provisioning")
108+
}
109+
110+
g.By("Creating HTTPRoute with backend")
111+
backendName := "echo-backend-" + t.gatewayName
112+
createHttpRoute(t.oc, t.gatewayName, t.routeName, t.hostname, backendName)
113+
114+
g.By("Waiting for HTTPRoute to be accepted")
115+
_, err = assertHttpRouteSuccessful(t.oc, t.gatewayName, t.routeName)
116+
o.Expect(err).NotTo(o.HaveOccurred())
117+
118+
if t.loadBalancerSupported && t.managedDNS {
119+
g.By("Verifying HTTP connectivity before upgrade")
120+
assertHttpRouteConnection(t.hostname)
121+
e2e.Logf("HTTPRoute connectivity verified before upgrade")
122+
}
123+
}
124+
125+
// Test validates that resources continue working during upgrade and validates provisioning method
126+
func (t *GatewayAPIUpgradeTest) Test(ctx context.Context, f *e2e.Framework, done <-chan struct{}, _ upgrades.UpgradeType) {
127+
g.By("Validating Gateway API resources remain functional after upgrade")
128+
129+
// Block until upgrade completes
130+
g.By("Waiting for upgrade to complete")
131+
<-done
132+
133+
g.By("Verifying Gateway still exists and is programmed")
134+
_, err := checkGatewayStatus(t.oc, t.gatewayName, ingressNamespace, t.loadBalancerSupported)
135+
o.Expect(err).NotTo(o.HaveOccurred(), "Gateway should remain programmed")
136+
137+
g.By("Checking if GatewayAPIWithoutOLM feature gate is enabled after upgrade")
138+
endsWithNoOLM := isNoOLMFeatureGateEnabled(t.oc)
139+
140+
// Determine if migration happened: started with OLM, ended with NO-OLM
141+
migrationOccurred := !t.startedWithNoOLM && endsWithNoOLM
142+
if migrationOccurred {
143+
e2e.Logf("Migration detected: started with OLM, ended with NO-OLM")
144+
} else {
145+
e2e.Logf("No migration occurred (started with NO-OLM=%v, ended with NO-OLM=%v)", t.startedWithNoOLM, endsWithNoOLM)
146+
}
147+
148+
if endsWithNoOLM {
149+
g.By("GatewayAPIWithoutOLM is enabled - validating CIO-based (NO-OLM) provisioning")
150+
t.validateCIOProvisioning(ctx, migrationOccurred)
151+
} else {
152+
g.By("GatewayAPIWithoutOLM is disabled - validating OLM-based provisioning")
153+
// A shorter timeout here is because the resources should already exist post-upgrade state.
154+
validateOLMBasedOSSM(t.oc, 2*time.Minute)
155+
}
156+
157+
g.By("Verifying HTTPRoute still exists and is accepted after upgrade")
158+
_, err = assertHttpRouteSuccessful(t.oc, t.gatewayName, t.routeName)
159+
o.Expect(err).NotTo(o.HaveOccurred())
160+
161+
if t.loadBalancerSupported && t.managedDNS {
162+
g.By("Verifying HTTP connectivity after upgrade")
163+
assertHttpRouteConnection(t.hostname)
164+
}
165+
166+
if migrationOccurred {
167+
e2e.Logf("Gateway API successfully migrated from OLM to CIO (NO-OLM) during upgrade")
168+
} else if endsWithNoOLM {
169+
e2e.Logf("Gateway API using CIO-based (NO-OLM) provisioning - no migration occurred")
170+
} else {
171+
e2e.Logf("Gateway API remains on OLM-based provisioning after upgrade")
172+
}
173+
}
174+
175+
// validateCIOProvisioning validates that Gateway API is using CIO-based (NO-OLM) provisioning
176+
// If migrationOccurred is true, validates the migration from OLM to CIO Sail Library
177+
func (t *GatewayAPIUpgradeTest) validateCIOProvisioning(ctx context.Context, migrationOccurred bool) {
178+
g.By("Verifying Istiod control plane is running")
179+
err := checkIstiodRunning(t.oc, 2*time.Minute)
180+
o.Expect(err).NotTo(o.HaveOccurred())
181+
182+
g.By("Verifying CIO has taken ownership via GatewayClass")
183+
// Check GatewayClass has CIO sail library finalizer
184+
err = checkGatewayClassFinalizer(t.oc, gatewayClassName, "openshift.io/ingress-operator-sail-finalizer")
185+
o.Expect(err).NotTo(o.HaveOccurred(), "GatewayClass should have CIO sail library finalizer")
186+
187+
// Check GatewayClass has required CIO conditions
188+
err = checkGatewayClassCondition(t.oc, gatewayClassName, gatewayClassControllerInstalledConditionType, metav1.ConditionTrue)
189+
o.Expect(err).NotTo(o.HaveOccurred(), "GatewayClass should have ControllerInstalled condition")
190+
191+
err = checkGatewayClassCondition(t.oc, gatewayClassName, gatewayClassCRDsReadyConditionType, metav1.ConditionTrue)
192+
o.Expect(err).NotTo(o.HaveOccurred(), "GatewayClass should have CRDsReady condition")
193+
194+
g.By("Verifying istiod deployment is managed by sail library")
195+
err = checkIstiodManagedBySailLibrary(t.oc)
196+
o.Expect(err).NotTo(o.HaveOccurred(), "Istiod should be managed by sail library")
197+
198+
if migrationOccurred {
199+
g.By("Verifying Istio CRDs remain managed by OLM after migration")
200+
// When migrating from OLM, CRDs were installed by OLM and should remain OLM-managed
201+
err = assertIstioCRDsOwnedByOLM(t.oc)
202+
o.Expect(err).NotTo(o.HaveOccurred(), "Istio CRDs should remain OLM-managed after migration")
203+
204+
g.By("Verifying OLM subscription still exists after migration")
205+
// The OLM Subscription for Sail Operator should still exist (it's not removed during migration)
206+
_, err = t.oc.Run("get").Args("subscription", "-n", expectedSubscriptionNamespace, expectedSubscriptionName, "-o", "name").Output()
207+
o.Expect(err).NotTo(o.HaveOccurred(), "Sail Operator subscription should still exist after migration")
208+
209+
g.By("Verifying Istio CR was removed during migration")
210+
out, err := t.oc.Run("get").Args("--ignore-not-found=true", "istio", istioName, "-o", "name").Output()
211+
o.Expect(err).NotTo(o.HaveOccurred())
212+
o.Expect(strings.TrimSpace(out)).To(o.BeEmpty(), "Istio CR %q should not exist", istioName)
213+
214+
e2e.Logf("Successfully validated OLM to NO-OLM migration")
215+
} else {
216+
g.By("Verifying Istio CRDs are managed by CIO")
217+
// When using CIO from the start, CRDs are CIO-managed
218+
err := assertIstioCRDsOwnedByCIO(t.oc)
219+
o.Expect(err).NotTo(o.HaveOccurred(), "Istio CRDs should be CIO-managed when using NO-OLM from the start")
220+
221+
g.By("Verifying CIO-managed resources")
222+
223+
out, err := t.oc.Run("get").Args("--ignore-not-found=true", "istio", istioName, "-o", "name").Output()
224+
if err != nil && strings.Contains(err.Error(), "the server doesn't have a resource type") {
225+
e2e.Logf("Istio CRD does not exist on the cluster, confirming Istio CR %q is absent", istioName)
226+
} else {
227+
o.Expect(err).NotTo(o.HaveOccurred())
228+
o.Expect(strings.TrimSpace(out)).To(o.BeEmpty(), "Istio CR %q should not exist when using CIO-based provisioning", istioName)
229+
}
230+
e2e.Logf("Successfully validated CIO-based (NO-OLM) provisioning")
231+
}
232+
}
233+
234+
// Teardown cleans up Gateway API resources, Istio CR, and OSSM subscription
235+
// This runs even if the test fails, ensuring complete cleanup
236+
func (t *GatewayAPIUpgradeTest) Teardown(ctx context.Context, f *e2e.Framework) {
237+
g.By("Deleting the Gateway")
238+
err := t.oc.AdminGatewayApiClient().GatewayV1().Gateways(ingressNamespace).Delete(ctx, t.gatewayName, metav1.DeleteOptions{})
239+
if err != nil && !apierrors.IsNotFound(err) {
240+
e2e.Logf("Failed to delete Gateway %q: %v", t.gatewayName, err)
241+
}
242+
243+
// Wait for Gateway to be fully deleted before removing GatewayClass
244+
// This prevents orphaned resources if the controller (defined by GatewayClass) is removed
245+
// before Istiod completes cleanup
246+
g.By("Waiting for Gateway to be fully deleted")
247+
err = wait.PollUntilContextTimeout(ctx, 2*time.Second, 2*time.Minute, false, func(ctx context.Context) (bool, error) {
248+
_, err := t.oc.AdminGatewayApiClient().GatewayV1().Gateways(ingressNamespace).Get(ctx, t.gatewayName, metav1.GetOptions{})
249+
if apierrors.IsNotFound(err) {
250+
e2e.Logf("Gateway %q successfully deleted", t.gatewayName)
251+
return true, nil
252+
}
253+
return false, nil
254+
})
255+
if err != nil {
256+
e2e.Logf("Gateway %q still exists after 2 minutes, continuing cleanup anyway", t.gatewayName)
257+
}
258+
259+
g.By("Deleting the GatewayClass")
260+
err = t.oc.AdminGatewayApiClient().GatewayV1().GatewayClasses().Delete(ctx, gatewayClassName, metav1.DeleteOptions{})
261+
if err != nil && !apierrors.IsNotFound(err) {
262+
e2e.Logf("Failed to delete GatewayClass %q: %v", gatewayClassName, err)
263+
}
264+
265+
g.By("Deleting the Istio CR if it exists")
266+
// This should get cleaned up by the CIO, but this is here just in case of failure
267+
err = t.oc.Run("delete").Args("--ignore-not-found=true", "istio", istioName).Execute()
268+
if err != nil {
269+
e2e.Logf("Failed to delete Istio CR %q: %v", istioName, err)
270+
}
271+
272+
g.By("Waiting for istiod pods to be deleted")
273+
waitForIstiodPodDeletion(t.oc)
274+
275+
g.By("Deleting the Sail Operator subscription")
276+
// This doesn't get deleted by the CIO, so must manually clean up
277+
err = t.oc.Run("delete").Args("--ignore-not-found=true", "subscription", "-n", expectedSubscriptionNamespace, expectedSubscriptionName).Execute()
278+
if err != nil {
279+
e2e.Logf("Failed to delete Subscription %q: %v", expectedSubscriptionName, err)
280+
}
281+
282+
g.By("Deleting Sail Operator CSV by label selector")
283+
// Delete CSV using label selector to handle any version (e.g., servicemeshoperator3.v3.2.0)
284+
labelSelector := fmt.Sprintf("operators.coreos.com/%s", serviceMeshOperatorName)
285+
err = t.oc.Run("delete").Args("csv", "-n", expectedSubscriptionNamespace, "-l", labelSelector, "--ignore-not-found=true").Execute()
286+
if err != nil {
287+
e2e.Logf("Failed to delete CSV with label %q: %v", labelSelector, err)
288+
}
289+
290+
g.By("Deleting OLM-managed Istio CRDs to clean up migration state")
291+
// Delete Istio CRDs so subsequent NO-OLM tests don't see OLM-managed CRDs
292+
// Use LabelSelector to only delete OLM-managed CRDs and suffix check as additional safety
293+
crdList, err := t.oc.AdminApiextensionsClient().ApiextensionsV1().CustomResourceDefinitions().List(ctx, metav1.ListOptions{
294+
LabelSelector: "olm.managed=true",
295+
})
296+
if err != nil {
297+
e2e.Logf("Failed to list OLM-managed CRDs: %v", err)
298+
} else {
299+
for _, crd := range crdList.Items {
300+
if strings.HasSuffix(crd.Name, "istio.io") {
301+
err := t.oc.Run("delete").Args("--ignore-not-found=true", "crd", crd.Name).Execute()
302+
if err != nil {
303+
e2e.Logf("Failed to delete CRD %q: %v", crd.Name, err)
304+
}
305+
}
306+
}
307+
}
308+
309+
e2e.Logf("Gateway API resources, Istio CR, OSSM subscription, CSV, and Istio CRDs successfully cleaned up")
310+
}

0 commit comments

Comments
 (0)