@@ -27,6 +27,7 @@ import (
2727 "k8s.io/apimachinery/pkg/util/intstr"
2828 e2e "k8s.io/kubernetes/test/e2e/framework"
2929 admissionapi "k8s.io/pod-security-admission/api"
30+ utilnet "k8s.io/utils/net"
3031 "k8s.io/utils/pointer"
3132
3233 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -104,11 +105,13 @@ var (
104105var _ = g .Describe ("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io]" , g .Ordered , g .Serial , func () {
105106 defer g .GinkgoRecover ()
106107 var (
107- oc = exutil .NewCLIWithPodSecurityLevel ("gatewayapi-controller" , admissionapi .LevelBaseline )
108- csvName string
109- err error
110- gateways []string
111- infPoolCRD = "https://raw.githubusercontent.com/kubernetes-sigs/gateway-api-inference-extension/main/config/crd/bases/inference.networking.k8s.io_inferencepools.yaml"
108+ oc = exutil .NewCLIWithPodSecurityLevel ("gatewayapi-controller" , admissionapi .LevelBaseline )
109+ csvName string
110+ err error
111+ gateways []string
112+ infPoolCRD = "https://raw.githubusercontent.com/kubernetes-sigs/gateway-api-inference-extension/main/config/crd/bases/inference.networking.k8s.io_inferencepools.yaml"
113+ managedDNS bool
114+ loadBalancerSupported bool
112115 )
113116
114117 const (
@@ -127,7 +130,9 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat
127130 g .Skip ("Skipping on OKD cluster as OSSM is not available as a community operator" )
128131 }
129132
130- skipGatewayForUnsupportedPlatform (oc )
133+ // Check platform support and get capabilities (LoadBalancer, DNS)
134+ loadBalancerSupported , managedDNS = checkPlatformSupportAndGetCapabilities (oc )
135+
131136 if ! isNoOLMFeatureGateEnabled (oc ) {
132137 // GatewayAPIController without GatewayAPIWithoutOLM featuregate
133138 // relies on OSSM OLM operator.
@@ -307,7 +312,10 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat
307312 g .By ("Check OLM catalogSource, subscription, CSV and Pod" )
308313 waitCatalogErr := wait .PollUntilContextTimeout (context .Background (), 1 * time .Second , 20 * time .Minute , false , func (context context.Context ) (bool , error ) {
309314 catalog , err := oc .AsAdmin ().Run ("get" ).Args ("-n" , "openshift-marketplace" , "catalogsource" , expectedSubscriptionSource , "-o=jsonpath={.status.connectionState.lastObservedState}" ).Output ()
310- o .Expect (err ).NotTo (o .HaveOccurred ())
315+ if err != nil {
316+ e2e .Logf ("Failed to get CatalogSource %q: %v; retrying..." , expectedSubscriptionSource , err )
317+ return false , nil
318+ }
311319 if catalog != "READY" {
312320 e2e .Logf ("CatalogSource %q is not in ready state, retrying..." , expectedSubscriptionSource )
313321 return false , nil
@@ -335,7 +343,10 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat
335343
336344 waitCSVErr := wait .PollUntilContextTimeout (context .Background (), 1 * time .Second , 20 * time .Minute , false , func (context context.Context ) (bool , error ) {
337345 csvStatus , err := oc .AsAdmin ().Run ("get" ).Args ("-n" , expectedSubscriptionNamespace , "clusterserviceversion" , csvName , "-o=jsonpath={.status.phase}" ).Output ()
338- o .Expect (err ).NotTo (o .HaveOccurred ())
346+ if err != nil {
347+ e2e .Logf ("Failed to get ClusterServiceVersion %q: %v; retrying..." , csvName , err )
348+ return false , nil
349+ }
339350 if csvStatus != "Succeeded" {
340351 e2e .Logf ("Cluster Service Version %q is not successful, retrying..." , csvName )
341352 return false , nil
@@ -440,14 +451,18 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat
440451 g .By ("Create the default Gateway" )
441452 gw := names .SimpleNameGenerator .GenerateName ("gateway-" )
442453 gateways = append (gateways , gw )
443- _ , gwerr := createAndCheckGateway (oc , gw , gatewayClassName , defaultDomain )
454+ _ , gwerr := createAndCheckGateway (oc , gw , gatewayClassName , defaultDomain , loadBalancerSupported )
444455 o .Expect (gwerr ).NotTo (o .HaveOccurred (), "failed to create Gateway" )
445456
446457 g .By ("Verify the gateway's LoadBalancer service and DNSRecords" )
447- assertGatewayLoadbalancerReady (oc , gw , gw + "-openshift-default" )
458+ if loadBalancerSupported {
459+ assertGatewayLoadbalancerReady (oc , gw , gw + "-openshift-default" )
460+ }
448461
449462 // check the dns record is created and status of the published dnsrecord of all zones are True
450- assertDNSRecordStatus (oc , gw )
463+ if managedDNS {
464+ assertDNSRecordStatus (oc , gw )
465+ }
451466 })
452467
453468 g .It ("Ensure HTTPRoute object is created" , func () {
@@ -465,11 +480,13 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat
465480 g .By ("Create a custom Gateway for the HTTPRoute" )
466481 gw := names .SimpleNameGenerator .GenerateName ("gateway-" )
467482 gateways = append (gateways , gw )
468- _ , gwerr := createAndCheckGateway (oc , gw , gatewayClassName , customDomain )
483+ _ , gwerr := createAndCheckGateway (oc , gw , gatewayClassName , customDomain , loadBalancerSupported )
469484 o .Expect (gwerr ).NotTo (o .HaveOccurred (), "Failed to create Gateway" )
470485
471486 // make sure the DNSRecord is ready to use
472- assertDNSRecordStatus (oc , gw )
487+ if managedDNS {
488+ assertDNSRecordStatus (oc , gw )
489+ }
473490
474491 g .By ("Create the http route using the custom gateway" )
475492 defaultRoutename := "test-hostname." + customDomain
@@ -479,7 +496,9 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat
479496 assertHttpRouteSuccessful (oc , gw , "test-httproute" )
480497
481498 g .By ("Validating the http connectivity to the backend application" )
482- assertHttpRouteConnection (defaultRoutename )
499+ if loadBalancerSupported && managedDNS {
500+ assertHttpRouteConnection (defaultRoutename )
501+ }
483502 })
484503
485504 g .It ("Ensure GIE is enabled after creating an inferencePool CRD" , func () {
@@ -565,6 +584,10 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat
565584 })
566585
567586 g .It ("Ensure gateway loadbalancer service and dnsrecords could be deleted and then get recreated [Serial]" , func () {
587+ if ! loadBalancerSupported || ! managedDNS {
588+ g .Skip ("Skipping LoadBalancer and DNS deletion test - platform does not support these features" )
589+ }
590+
568591 g .By ("Getting the default domain for creating a custom Gateway" )
569592 defaultIngressDomain , err := getDefaultIngressClusterDomainName (oc , time .Minute )
570593 o .Expect (err ).NotTo (o .HaveOccurred (), "Failed to find default domain name" )
@@ -573,7 +596,7 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat
573596 g .By ("Create a custom Gateway" )
574597 gw := names .SimpleNameGenerator .GenerateName ("gateway-" )
575598 gateways = append (gateways , gw )
576- _ , gwerr := createAndCheckGateway (oc , gw , gatewayClassName , customDomain )
599+ _ , gwerr := createAndCheckGateway (oc , gw , gatewayClassName , customDomain , loadBalancerSupported )
577600 o .Expect (gwerr ).NotTo (o .HaveOccurred (), "Failed to create Gateway" )
578601
579602 // verify the gateway's LoadBalancer service
@@ -610,10 +633,9 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat
610633 })
611634})
612635
613- // skipGatewayForUnsupportedPlatform skips gateway API tests on non-cloud
614- // platforms (gateway needs LB service) and on dual-stack clusters (dual-stack
615- // support is not yet declared).
616- func skipGatewayForUnsupportedPlatform (oc * exutil.CLI ) {
636+ // checkPlatformSupportAndGetCapabilities verifies the platform is supported and returns
637+ // platform capabilities for LoadBalancer services and managed DNS.
638+ func checkPlatformSupportAndGetCapabilities (oc * exutil.CLI ) (loadBalancerSupported bool , managedDNS bool ) {
617639 infra , err := oc .AdminConfigClient ().ConfigV1 ().Infrastructures ().Get (context .Background (), "cluster" , metav1.GetOptions {})
618640 o .Expect (err ).NotTo (o .HaveOccurred ())
619641 o .Expect (infra ).NotTo (o .BeNil ())
@@ -623,17 +645,47 @@ func skipGatewayForUnsupportedPlatform(oc *exutil.CLI) {
623645 o .Expect (platformType ).NotTo (o .BeEmpty ())
624646 switch platformType {
625647 case configv1 .AWSPlatformType , configv1 .AzurePlatformType , configv1 .GCPPlatformType , configv1 .IBMCloudPlatformType :
626- // supported
648+ // Cloud platforms with native LoadBalancer support
649+ loadBalancerSupported = true
650+ case configv1 .VSpherePlatformType , configv1 .BareMetalPlatformType , configv1 .EquinixMetalPlatformType :
651+ // Platforms without native LoadBalancer support (may have MetalLB or similar)
652+ loadBalancerSupported = false
627653 default :
628- g .Skip (fmt .Sprintf ("Skipping on non cloud platform type %q" , platformType ))
654+ g .Skip (fmt .Sprintf ("Skipping on unsupported platform type %q" , platformType ))
629655 }
630656
631- if infra .Status .PlatformStatus .AWS != nil {
632- ipFamily := infra .Status .PlatformStatus .AWS .IPFamily
633- if ipFamily == configv1 .DualStackIPv4Primary || ipFamily == configv1 .DualStackIPv6Primary {
634- g .Skip ("Skipping Gateway API tests on dual-stack cluster" )
657+ // Check if DNS is managed (has public or private zones configured)
658+ managedDNS = isDNSManaged (oc )
659+
660+ // Skip Gateway API tests on IPv6 or dual-stack clusters (any platform)
661+ if isIPv6OrDualStack (oc ) {
662+ g .Skip ("Skipping Gateway API tests on IPv6/dual-stack cluster" )
663+ }
664+
665+ e2e .Logf ("Platform: %s, LoadBalancer supported: %t, DNS managed: %t" , platformType , loadBalancerSupported , managedDNS )
666+ return loadBalancerSupported , managedDNS
667+ }
668+
669+ // isDNSManaged checks if the cluster has DNS zones configured (public or private).
670+ // On platforms like vSphere without external DNS, DNS records cannot be managed.
671+ func isDNSManaged (oc * exutil.CLI ) bool {
672+ dnsConfig , err := oc .AdminConfigClient ().ConfigV1 ().DNSes ().Get (context .Background (), "cluster" , metav1.GetOptions {})
673+ o .Expect (err ).NotTo (o .HaveOccurred (), "Failed to get DNS config" )
674+ return dnsConfig .Spec .PrivateZone != nil || dnsConfig .Spec .PublicZone != nil
675+ }
676+
677+ // isIPv6OrDualStack checks if the cluster is using IPv6 or dual-stack networking.
678+ // Returns true if any ServiceNetwork CIDR is IPv6 (indicates IPv6-only or dual-stack).
679+ func isIPv6OrDualStack (oc * exutil.CLI ) bool {
680+ networkConfig , err := oc .AdminOperatorClient ().OperatorV1 ().Networks ().Get (context .Background (), "cluster" , metav1.GetOptions {})
681+ o .Expect (err ).NotTo (o .HaveOccurred (), "Failed to get network config" )
682+
683+ for _ , cidr := range networkConfig .Spec .ServiceNetwork {
684+ if utilnet .IsIPv6CIDRString (cidr ) {
685+ return true
635686 }
636687 }
688+ return false
637689}
638690
639691func isNoOLMFeatureGateEnabled (oc * exutil.CLI ) bool {
@@ -679,7 +731,7 @@ func buildGatewayClass(name, controllerName string) *gatewayapiv1.GatewayClass {
679731}
680732
681733// createAndCheckGateway build and creates the Gateway.
682- func createAndCheckGateway (oc * exutil.CLI , gwname , gwclassname , domain string ) (* gatewayapiv1.Gateway , error ) {
734+ func createAndCheckGateway (oc * exutil.CLI , gwname , gwclassname , domain string , loadBalancerSupported bool ) (* gatewayapiv1.Gateway , error ) {
683735 // Build the gateway object
684736 gatewaybuild := buildGateway (gwname , ingressNamespace , gwclassname , "All" , domain )
685737
@@ -690,10 +742,19 @@ func createAndCheckGateway(oc *exutil.CLI, gwname, gwclassname, domain string) (
690742 }
691743
692744 // Confirm the gateway is up and running
693- return checkGatewayStatus (oc , gwname , ingressNamespace )
745+ return checkGatewayStatus (oc , gwname , ingressNamespace , loadBalancerSupported )
694746}
695747
696- func checkGatewayStatus (oc * exutil.CLI , gwname , ingressNameSpace string ) (* gatewayapiv1.Gateway , error ) {
748+ func checkGatewayStatus (oc * exutil.CLI , gwname , ingressNameSpace string , loadBalancerSupported bool ) (* gatewayapiv1.Gateway , error ) {
749+ // Determine which condition to wait for based on platform capabilities
750+ // Without LoadBalancer support, Gateway reaches Accepted but not Programmed (reason: AddressNotAssigned)
751+ var expectedCondition gatewayapiv1.GatewayConditionType
752+ if loadBalancerSupported {
753+ expectedCondition = gatewayapiv1 .GatewayConditionProgrammed
754+ } else {
755+ expectedCondition = gatewayapiv1 .GatewayConditionAccepted
756+ }
757+
697758 programmedGateway := & gatewayapiv1.Gateway {}
698759 timeout := 20 * time .Minute
699760 if err := wait .PollUntilContextTimeout (context .Background (), 10 * time .Second , timeout , false , func (context context.Context ) (bool , error ) {
@@ -702,22 +763,21 @@ func checkGatewayStatus(oc *exutil.CLI, gwname, ingressNameSpace string) (*gatew
702763 e2e .Logf ("Failed to get gateway %q: %v, retrying..." , gwname , err )
703764 return false , nil
704765 }
705- // Checking the gateway controller status
706766 for _ , condition := range gateway .Status .Conditions {
707- if condition .Type == string (gatewayapiv1 . GatewayConditionProgrammed ) {
767+ if condition .Type == string (expectedCondition ) {
708768 if condition .Status == metav1 .ConditionTrue {
709- e2e .Logf ("The gateway controller for gateway %q is programmed " , gwname )
769+ e2e .Logf ("Gateway %q has condition %s=True " , gwname , expectedCondition )
710770 programmedGateway = gateway
711771 return true , nil
712772 }
713773 }
714774 }
715- e2e .Logf ("Found gateway %q but the controller is still not programmed , retrying..." , gwname )
775+ e2e .Logf ("Found gateway %q but condition %s is not yet True , retrying..." , gwname , expectedCondition )
716776 return false , nil
717777 }); err != nil {
718- return nil , fmt .Errorf ("timed out after %v waiting for gateway %q to become programmed : %w" , timeout , gwname , err )
778+ return nil , fmt .Errorf ("timed out after %v waiting for gateway %q to have condition %s=True : %w" , timeout , gwname , expectedCondition , err )
719779 }
720- e2e .Logf ("Gateway %q successfully programmed! " , gwname )
780+ e2e .Logf ("Gateway %q successfully has condition %s=True " , gwname , expectedCondition )
721781 return programmedGateway , nil
722782}
723783
@@ -843,7 +903,10 @@ func createHttpRoute(oc *exutil.CLI, gwName, routeName, hostname, backendRefname
843903 // Confirm the HTTPRoute is up
844904 waitErr := wait .PollUntilContextTimeout (context .Background (), 1 * time .Second , 4 * time .Minute , false , func (context context.Context ) (bool , error ) {
845905 checkHttpRoute , err := oc .GatewayApiClient ().GatewayV1 ().HTTPRoutes (namespace ).Get (context , httpRoute .Name , metav1.GetOptions {})
846- o .Expect (err ).NotTo (o .HaveOccurred ())
906+ if err != nil {
907+ e2e .Logf ("Failed to get HTTPRoute %q: %v; retrying..." , httpRoute .Name , err )
908+ return false , nil
909+ }
847910 if len (checkHttpRoute .Status .Parents ) > 0 {
848911 for _ , condition := range checkHttpRoute .Status .Parents [0 ].Conditions {
849912 if condition .Type == string (gatewayapiv1 .RouteConditionAccepted ) {
@@ -965,7 +1028,10 @@ func assertHttpRouteSuccessful(oc *exutil.CLI, gwName, name string) (*gatewayapi
9651028 // Wait up to 4 minutes for parent(s) to update.
9661029 err := wait .PollUntilContextTimeout (context .Background (), 2 * time .Second , 4 * time .Minute , false , func (context context.Context ) (bool , error ) {
9671030 checkHttpRoute , err := oc .GatewayApiClient ().GatewayV1 ().HTTPRoutes (namespace ).Get (context , name , metav1.GetOptions {})
968- o .Expect (err ).NotTo (o .HaveOccurred ())
1031+ if err != nil {
1032+ e2e .Logf ("Failed to get HTTPRoute %s/%s: %v; retrying..." , namespace , name , err )
1033+ return false , nil
1034+ }
9691035
9701036 numParents := len (checkHttpRoute .Status .Parents )
9711037 if numParents == 0 {
0 commit comments