Skip to content

Commit e0e704f

Browse files
Merge pull request #30946 from gcs278/enable-gateway-api-vsphere
NE-2520: Enable Gateway API tests on vSphere and baremetal
2 parents b578433 + 974a80c commit e0e704f

1 file changed

Lines changed: 102 additions & 36 deletions

File tree

test/extended/router/gatewayapicontroller.go

Lines changed: 102 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -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 (
104105
var _ = 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

639691
func 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

Comments
 (0)