From b2da473c4a35bd92cc209911b132c4a93bb9fcc1 Mon Sep 17 00:00:00 2001 From: Sandhya Dasu Date: Tue, 5 May 2026 10:16:11 -0400 Subject: [PATCH 1/2] OCPBUGS-59176: fix several failing tests in custom-dns jobs Some e2e tests are failing with the job "gcp-custom-dns" for featuregate "GCPClusterHostedDNSInstall" which is promoted to GA in 4.20. In the "custom-dns" cluster OpenShift will start static CoreDNS pods to provide DNS resolution for API, Internal API and Ingress services that are essential for cluster creation. After cluster deployment is completed, the customer will update their external DNS solution with the same assigned LB IP addresses used for the configuration of the internal CoreDNS instance. The failing tests like http2 and grpc tests use dedicated ingresscontrollers, and gateway also has separated LB and dnsrecord, so the default wildcard created by the new static CoreDNS won't work for those tests. Make below changes to fix the failing tests: - Update makeHTTPClient() DialContext to target LoadBalancer IP address directly when DNS is unmanaged, fixing both http2 and gatewayapi httproute tests. - Update grpc Dial() to target LoadBalancer IP address directly when DNS is unmanaged, fixing the grpc test. - Extract getLoadBalancerAddress() as a reusable helper from assertGatewayLoadbalancerReady() for use across http2, grpc and gatewayapi tests. - Update gatewayapi assertDNSRecordStatus() to check the Published condition per-zone based on whether DNS is managed or unmanaged. - Add isDNSManaged() helper that checks the DNSManaged condition on the default ingresscontroller, defaulting to managed=true when the condition is absent. - Add getClusterBaseDomainName() and update http2/grpc/h2spec shard ingresscontroller to generate domain based on cluster baseDomain instead of ingress domain (e.g. "e2e-test-xxx.apps.baseDomain"), avoiding custom ingresscontroller DNS overlapping with default wildcard "*.apps.baseDomain". --- test/extended/router/gatewayapi_upgrade.go | 8 +- test/extended/router/gatewayapicontroller.go | 126 +++++++++++------- test/extended/router/grpc-interop.go | 20 ++- .../router/grpc-interop/clientconn.go | 17 +++ test/extended/router/h2spec.go | 8 +- test/extended/router/http2.go | 81 ++++++++++- test/extended/router/idle.go | 2 +- 7 files changed, 198 insertions(+), 64 deletions(-) diff --git a/test/extended/router/gatewayapi_upgrade.go b/test/extended/router/gatewayapi_upgrade.go index 47a499f396e6..b3f37b30d691 100644 --- a/test/extended/router/gatewayapi_upgrade.go +++ b/test/extended/router/gatewayapi_upgrade.go @@ -138,9 +138,9 @@ func (t *GatewayAPIUpgradeTest) Setup(ctx context.Context, f *e2e.Framework) { _, err = assertHttpRouteSuccessful(t.oc, t.gatewayName, t.routeName) o.Expect(err).NotTo(o.HaveOccurred()) - if t.loadBalancerSupported && t.managedDNS { + if t.loadBalancerSupported { g.By("Verifying HTTP connectivity before upgrade") - assertHttpRouteConnection(t.hostname) + assertHttpRouteConnection(t.oc, t.gatewayName+"-openshift-default", t.hostname, t.loadBalancerSupported) e2e.Logf("HTTPRoute connectivity verified before upgrade") } } @@ -182,9 +182,9 @@ func (t *GatewayAPIUpgradeTest) Test(ctx context.Context, f *e2e.Framework, done _, err = assertHttpRouteSuccessful(t.oc, t.gatewayName, t.routeName) o.Expect(err).NotTo(o.HaveOccurred()) - if t.loadBalancerSupported && t.managedDNS { + if t.loadBalancerSupported { g.By("Verifying HTTP connectivity after upgrade") - assertHttpRouteConnection(t.hostname) + assertHttpRouteConnection(t.oc, t.gatewayName+"-openshift-default", t.hostname, t.loadBalancerSupported) } if migrationOccurred { diff --git a/test/extended/router/gatewayapicontroller.go b/test/extended/router/gatewayapicontroller.go index 7406afba767e..d1c2a055da87 100644 --- a/test/extended/router/gatewayapicontroller.go +++ b/test/extended/router/gatewayapicontroller.go @@ -2,7 +2,6 @@ package router import ( "context" - "crypto/tls" "errors" "fmt" "net" @@ -403,9 +402,9 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat g.By("Checking the http route using the default gateway is accepted") assertHttpRouteSuccessful(oc, gw, "test-httproute") - g.By("Validating the http connectivity to the backend application") - if loadBalancerSupported && managedDNS { - assertHttpRouteConnection(defaultRoutename) + if loadBalancerSupported { + g.By("Validating the http connectivity to the backend application") + assertHttpRouteConnection(oc, gw+"-openshift-default", defaultRoutename, loadBalancerSupported) } }) @@ -612,20 +611,13 @@ func getPlatformCapabilities(oc *exutil.CLI) (loadBalancerSupported bool, manage loadBalancerSupported = false } - managedDNS = isDNSManaged(oc) + managedDNS, err = isDNSManaged(oc, time.Minute) + o.Expect(err).NotTo(o.HaveOccurred(), "Failed to check if DNS is managed") e2e.Logf("Platform: %s, LoadBalancer supported: %t, DNS managed: %t", infra.Status.PlatformStatus.Type, loadBalancerSupported, managedDNS) return loadBalancerSupported, managedDNS } -// isDNSManaged checks if the cluster has DNS zones configured (public or private). -// On platforms like vSphere without external DNS, DNS records cannot be managed. -func isDNSManaged(oc *exutil.CLI) bool { - dnsConfig, err := oc.AdminConfigClient().ConfigV1().DNSes().Get(context.Background(), "cluster", metav1.GetOptions{}) - o.Expect(err).NotTo(o.HaveOccurred(), "Failed to get DNS config") - return dnsConfig.Spec.PrivateZone != nil || dnsConfig.Spec.PublicZone != nil -} - // isIPv6OrDualStack checks if the cluster is using IPv6 or dual-stack networking. // Returns true if any ServiceNetwork CIDR is IPv6 (indicates IPv6-only or dual-stack). func isIPv6OrDualStack(oc *exutil.CLI) (bool, error) { @@ -753,18 +745,17 @@ func buildGateway(name, namespace, gcname, fromNs, domain string) *gatewayapiv1. } } -// assertGatewayLoadbalancerReady verifies that the given gateway has the service's load balancer address assigned. -func assertGatewayLoadbalancerReady(oc *exutil.CLI, gwName, gwServiceName string) { - // check gateway LB service, note that External-IP might be hostname (AWS) or IP (Azure/GCP) +// return LoadBalancer service address, note that External-IP might be hostname (AWS) or IP (Azure/GCP) +func getLoadBalancerAddress(oc *exutil.CLI, serviceName string) string { var lbAddress string err := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, loadBalancerReadyTimeout, false, func(context context.Context) (bool, error) { - lbService, err := oc.AdminKubeClient().CoreV1().Services(ingressNamespace).Get(context, gwServiceName, metav1.GetOptions{}) + lbService, err := oc.AdminKubeClient().CoreV1().Services(ingressNamespace).Get(context, serviceName, metav1.GetOptions{}) if err != nil { - e2e.Logf("Failed to get service %q: %v, retrying...", gwServiceName, err) + e2e.Logf("Failed to get service %q: %v, retrying...", serviceName, err) return false, nil } if len(lbService.Status.LoadBalancer.Ingress) == 0 { - e2e.Logf("Service %q has no load balancer; retrying...", gwServiceName) + e2e.Logf("Service %q has no load balancer; retrying...", serviceName) return false, nil } if lbService.Status.LoadBalancer.Ingress[0].Hostname != "" { @@ -773,11 +764,20 @@ func assertGatewayLoadbalancerReady(oc *exutil.CLI, gwName, gwServiceName string lbAddress = lbService.Status.LoadBalancer.Ingress[0].IP } if lbAddress == "" { - e2e.Logf("No load balancer address for service %q, retrying", gwServiceName) + e2e.Logf("No load balancer address for service %q, retrying", serviceName) return false, nil } - e2e.Logf("Got load balancer address for service %q: %v", gwServiceName, lbAddress) + return true, nil + }) + o.Expect(err).NotTo(o.HaveOccurred(), "Timed out to get load balancer address of service %q", serviceName) + e2e.Logf("Got load balancer address for service %q: %v", serviceName, lbAddress) + return lbAddress +} +// assertGatewayLoadbalancerReady verifies that the given gateway has the service's load balancer address assigned. +func assertGatewayLoadbalancerReady(oc *exutil.CLI, gwName, gwServiceName string) { + lbAddress := getLoadBalancerAddress(oc, gwServiceName) + err := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, loadBalancerReadyTimeout, false, func(context context.Context) (bool, error) { gw, err := oc.AdminGatewayApiClient().GatewayV1().Gateways(ingressNamespace).Get(context, gwName, metav1.GetOptions{}) if err != nil { e2e.Logf("Failed to get gateway %q: %v; retrying...", err, gwName) @@ -795,10 +795,17 @@ func assertGatewayLoadbalancerReady(oc *exutil.CLI, gwName, gwServiceName string o.Expect(err).NotTo(o.HaveOccurred(), "Timed out waiting for gateway %q to get load balancer address of service %q", gwName, gwServiceName) } -// assertDNSRecordStatus polls until the DNSRecord's status in the default operand namespace is True. +// assertDNSRecordStatus polls until the DNSRecord's status in the default operand namespace is ready. +// When DNS is managed, it waits for all zones to have Published=True. +// When DNS is unmanaged, it waits for all zones to have Published!=True (expected in custom-dns clusters). func assertDNSRecordStatus(oc *exutil.CLI, gatewayName string) { - // find the DNS Record and confirm its zone status is True - err := wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 10*time.Minute, false, func(context context.Context) (bool, error) { + dnsManaged, err := isDNSManaged(oc, time.Minute) + if err != nil { + e2e.Failf("Failed to get default ingresscontroller DNSManaged status: %v", err) + } + + // find the DNS Record and confirm its zone status + err = wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 10*time.Minute, false, func(context context.Context) (bool, error) { gatewayDNSRecord := &operatoringressv1.DNSRecord{} gatewayDNSRecords, err := oc.AdminIngressClient().IngressV1().DNSRecords(ingressNamespace).List(context, metav1.ListOptions{}) if err != nil { @@ -810,20 +817,43 @@ func assertDNSRecordStatus(oc *exutil.CLI, gatewayName string) { for _, record := range gatewayDNSRecords.Items { if record.Labels["gateway.networking.k8s.io/gateway-name"] == gatewayName { gatewayDNSRecord = &record + e2e.Logf("Found the desired dnsrecord and spec is: %v", gatewayDNSRecord.Spec) break } } - // checking the gateway DNS record status + if len(gatewayDNSRecord.Status.Zones) == 0 { + e2e.Logf("DNS record %q has no zones yet, retrying...", gatewayDNSRecord.Name) + return false, nil + } + + // Check the Published condition for each zone for _, zone := range gatewayDNSRecord.Status.Zones { + published := false for _, condition := range zone.Conditions { - if condition.Type == "Published" && condition.Status == "True" { - return true, nil + if condition.Type != "Published" { + continue + } + e2e.Logf("The published status is %v for zone %v", condition.Status, zone.DNSZone) + if dnsManaged && condition.Status != "True" { + e2e.Logf("DNS record %q zone %v is not published yet, retrying...", gatewayDNSRecord.Name, zone.DNSZone) + return false, nil + } + if !dnsManaged && condition.Status == "True" { + e2e.Logf("DNS record %q zone %v is unexpectedly published for unmanaged DNS, retrying...", gatewayDNSRecord.Name, zone.DNSZone) + return false, nil } + published = true + break + } + if !published { + e2e.Logf("DNS record %q zone %v has no Published condition, retrying...", gatewayDNSRecord.Name, zone.DNSZone) + return false, nil } } - e2e.Logf("DNS record %q is not ready, retrying...", gatewayDNSRecord.Name) - return false, nil + + e2e.Logf("All zones are checked and DNS record %q is ready", gatewayDNSRecord.Name) + return true, nil }) o.Expect(err).NotTo(o.HaveOccurred(), "Timed out waiting for gateway %q DNSRecord to become ready", gatewayName) } @@ -1041,24 +1071,30 @@ func assertHttpRouteSuccessful(oc *exutil.CLI, gwName, name string) (*gatewayapi // assertHttpRouteConnection checks if the http route of the given name replies successfully, // and returns an error if not -func assertHttpRouteConnection(hostname string) { - // Create the http client to check the response status code. - client := &http.Client{ - Timeout: 10 * time.Second, - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }, +func assertHttpRouteConnection(oc *exutil.CLI, gwServiceName, hostname string, loadBalancerSupported bool) { + isDNSManaged, err := isDNSManaged(oc, time.Minute) + if err != nil { + e2e.Failf("Failed to get default ingresscontroller DNSManaged status: %v", err) + } + lbAddress := "" + if isDNSManaged { + err := wait.PollUntilContextTimeout(context.Background(), 20*time.Second, dnsResolutionTimeout, false, func(context context.Context) (bool, error) { + _, err := net.LookupHost(hostname) + if err != nil { + e2e.Logf("[%v] Failed to resolve HTTP route's hostname %q: %v, retrying...", time.Now(), hostname, err) + return false, nil + } + return true, nil + }) + o.Expect(err).NotTo(o.HaveOccurred(), "Timed out waiting for HTTP route's hostname %q to be resolved: %v", hostname, err) + } else if loadBalancerSupported { + lbAddress = getLoadBalancerAddress(oc, gwServiceName) + } else { + e2e.Failf("Platform does not support load balancers and DNS is unmanaged - cannot verify HTTP route connectivity") } - err := wait.PollUntilContextTimeout(context.Background(), 20*time.Second, dnsResolutionTimeout, false, func(context context.Context) (bool, error) { - _, err := net.LookupHost(hostname) - if err != nil { - e2e.Logf("[%v] Failed to resolve HTTP route's hostname %q: %v, retrying...", time.Now(), hostname, err) - return false, nil - } - return true, nil - }) - o.Expect(err).NotTo(o.HaveOccurred(), "Timed out waiting for HTTP route's hostname %q to be resolved: %v", hostname, err) + // Create the http client to check the response status code. + client := makeHTTPClient(false, 10*time.Second, lbAddress) // Wait for http route to respond, and when it does, check for the status code. err = wait.PollUntilContextTimeout(context.Background(), 5*time.Second, 5*time.Minute, false, func(context context.Context) (bool, error) { diff --git a/test/extended/router/grpc-interop.go b/test/extended/router/grpc-interop.go index b6e96b86e0b5..817443c172dd 100644 --- a/test/extended/router/grpc-interop.go +++ b/test/extended/router/grpc-interop.go @@ -53,8 +53,8 @@ var _ = g.Describe("[sig-network-edge][Conformance][Area:Networking][Feature:Rou g.Skip("Skip on platforms where the default router is not exposed by a load balancer service.") } - defaultDomain, err := getDefaultIngressClusterDomainName(oc, time.Minute) - o.Expect(err).NotTo(o.HaveOccurred(), "failed to find default domain name") + baseDomain, err := getClusterBaseDomainName(oc, time.Minute) + o.Expect(err).NotTo(o.HaveOccurred(), "failed to find base domain name") g.By("Locating the canary image reference") image, err := getCanaryImage(oc) @@ -196,7 +196,7 @@ var _ = g.Describe("[sig-network-edge][Conformance][Area:Networking][Feature:Rou pemCrt2, err := certgen.MarshalCertToPEMString(tlsCrt2Data) o.Expect(err).NotTo(o.HaveOccurred()) - shardFQDN := oc.Namespace() + "." + defaultDomain + shardFQDN := oc.Namespace() + "." + baseDomain g.By("Creating routes to test for gRPC interoperability") routeType := oc.Namespace() @@ -330,6 +330,15 @@ var _ = g.Describe("[sig-network-edge][Conformance][Area:Networking][Feature:Rou o.Expect(shardService).NotTo(o.BeNil()) o.Expect(shardService.Status.LoadBalancer.Ingress).To(o.Not(o.BeEmpty())) + isDNSManaged, err := isDNSManaged(oc, time.Minute) + if err != nil { + e2e.Failf("Failed to get default ingresscontroller DNSManaged status: %v", err) + } + lbAddress := "" + if !isDNSManaged { + lbAddress = getLoadBalancerAddress(oc, "router-"+oc.Namespace()) + } + testCases := []string{ "cancel_after_begin", "cancel_after_first_response", @@ -352,7 +361,7 @@ var _ = g.Describe("[sig-network-edge][Conformance][Area:Networking][Feature:Rou routev1.TLSTerminationReencrypt, routev1.TLSTerminationPassthrough, } { - err := grpcExecTestCases(oc, routeType, 5*time.Minute, testCases...) + err := grpcExecTestCases(oc, routeType, 5*time.Minute, lbAddress, testCases...) o.Expect(err).NotTo(o.HaveOccurred()) } }) @@ -360,7 +369,7 @@ var _ = g.Describe("[sig-network-edge][Conformance][Area:Networking][Feature:Rou }) // grpcExecTestCases run gRPC interop test cases. -func grpcExecTestCases(oc *exutil.CLI, routeType routev1.TLSTerminationType, timeout time.Duration, testCases ...string) error { +func grpcExecTestCases(oc *exutil.CLI, routeType routev1.TLSTerminationType, timeout time.Duration, lbAddress string, testCases ...string) error { host, err := getHostnameForRoute(oc, fmt.Sprintf("grpc-interop-%s", routeType)) if err != nil { return err @@ -371,6 +380,7 @@ func grpcExecTestCases(oc *exutil.CLI, routeType routev1.TLSTerminationType, tim Port: 443, UseTLS: true, Insecure: true, + Target: lbAddress, } if routeType == "h2c" { diff --git a/test/extended/router/grpc-interop/clientconn.go b/test/extended/router/grpc-interop/clientconn.go index 5d5792732c96..567701185d98 100644 --- a/test/extended/router/grpc-interop/clientconn.go +++ b/test/extended/router/grpc-interop/clientconn.go @@ -1,9 +1,11 @@ package grpc_interop import ( + "context" "crypto/tls" "crypto/x509" "errors" + "fmt" "net" "strconv" @@ -17,6 +19,8 @@ type DialParams struct { Host string Port int Insecure bool + // Target is the actual IP you want to dial instead of resolving hostname + Target string } func Dial(cfg DialParams) (*grpc.ClientConn, error) { @@ -44,5 +48,18 @@ func Dial(cfg DialParams) (*grpc.ClientConn, error) { opts = append(opts, grpc.WithInsecure()) } + if cfg.Target != "" { + dialer := func(ctx context.Context, addr string) (net.Conn, error) { + _, port, err := net.SplitHostPort(addr) + if err != nil { + return nil, fmt.Errorf("failed to split host:port from %q: %w", addr, err) + } + // Connect to targetIP:port regardless of hostname + dest := net.JoinHostPort(cfg.Target, port) + return (&net.Dialer{}).DialContext(ctx, "tcp", dest) + } + opts = append(opts, grpc.WithContextDialer(dialer)) + } + return grpc.Dial(net.JoinHostPort(cfg.Host, strconv.Itoa(cfg.Port)), append(opts, grpc.WithBlock())...) } diff --git a/test/extended/router/h2spec.go b/test/extended/router/h2spec.go index f7edf4abb641..0c8bb0adf6f1 100644 --- a/test/extended/router/h2spec.go +++ b/test/extended/router/h2spec.go @@ -80,9 +80,9 @@ var _ = g.Describe("[sig-network-edge][Conformance][Area:Networking][Feature:Rou } } - g.By("Getting the default domain") - defaultDomain, err := getDefaultIngressClusterDomainName(oc, time.Minute) - o.Expect(err).NotTo(o.HaveOccurred(), "failed to find default domain name") + g.By("Getting the base domain") + baseDomain, err := getClusterBaseDomainName(oc, time.Minute) + o.Expect(err).NotTo(o.HaveOccurred(), "failed to find base domain name") g.By("Locating the router image reference") routerImage, err := exutil.FindRouterImage(oc) @@ -409,7 +409,7 @@ BFNBRELPe53ZdLKWpf2Sr96vRPRNw e2e.ExpectNoError(e2epod.WaitForPodNameRunningInNamespace(context.TODO(), oc.KubeClient(), "h2spec-haproxy", oc.KubeFramework().Namespace.Name)) e2e.ExpectNoError(e2epod.WaitForPodNameRunningInNamespace(context.TODO(), oc.KubeClient(), "h2spec", oc.KubeFramework().Namespace.Name)) - shardFQDN := oc.Namespace() + "." + defaultDomain + shardFQDN := oc.Namespace() + "." + baseDomain // The new router shard is using a namespace // selector so label this test namespace to diff --git a/test/extended/router/http2.go b/test/extended/router/http2.go index d9bfed9f40e0..042edbe8b7d6 100644 --- a/test/extended/router/http2.go +++ b/test/extended/router/http2.go @@ -24,6 +24,7 @@ import ( utilpointer "k8s.io/utils/pointer" configv1 "github.com/openshift/api/config/v1" + operatorv1 "github.com/openshift/api/operator/v1" routev1 "github.com/openshift/api/route/v1" routeclientset "github.com/openshift/client-go/route/clientset/versioned" @@ -52,14 +53,29 @@ const ( // certificate verification (InsecureSkipVerify). // // The function returns a pointer to the configured http.Client. -func makeHTTPClient(useHTTP2Transport bool, timeout time.Duration) *http.Client { +func makeHTTPClient(useHTTP2Transport bool, timeout time.Duration, lbAddress string) *http.Client { tlsConfig := tls.Config{ InsecureSkipVerify: true, } + dialer := &net.Dialer{} + // modify target address of DialContext if lbAddress present + dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { + if lbAddress != "" { + _, port, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + // Connect to lbAddress:port regardless of hostname + addr = net.JoinHostPort(lbAddress, port) + } + return dialer.DialContext(ctx, network, addr) + } + transport := &http.Transport{ TLSClientConfig: &tlsConfig, Proxy: http.ProxyFromEnvironment, + DialContext: dialContext, } c := &http.Client{ @@ -109,8 +125,8 @@ var _ = g.Describe("[sig-network-edge][Conformance][Area:Networking][Feature:Rou g.Skip("Skip on platforms where the default router is not exposed by a load balancer service.") } - defaultDomain, err := getDefaultIngressClusterDomainName(oc, time.Minute) - o.Expect(err).NotTo(o.HaveOccurred(), "failed to find default domain name") + baseDomain, err := getClusterBaseDomainName(oc, time.Minute) + o.Expect(err).NotTo(o.HaveOccurred(), "failed to find base domain name") g.By("Locating the canary image reference") image, err := getCanaryImage(oc) @@ -254,7 +270,7 @@ var _ = g.Describe("[sig-network-edge][Conformance][Area:Networking][Feature:Rou pemCrt2, err := certgen.MarshalCertToPEMString(tlsCrt2Data) o.Expect(err).NotTo(o.HaveOccurred()) - shardFQDN := oc.Namespace() + "." + defaultDomain + shardFQDN := oc.Namespace() + "." + baseDomain // The new router shard is using a namespace // selector so label this test namespace to @@ -485,10 +501,19 @@ var _ = g.Describe("[sig-network-edge][Conformance][Area:Networking][Feature:Rou o.Expect(shardService).NotTo(o.BeNil()) o.Expect(shardService.Status.LoadBalancer.Ingress).To(o.Not(o.BeEmpty())) + isDNSManaged, err := isDNSManaged(oc, time.Minute) + if err != nil { + e2e.Failf("Failed to get default ingresscontroller DNSManaged status: %v", err) + } + lbAddress := "" + if !isDNSManaged { + lbAddress = getLoadBalancerAddress(oc, "router-"+oc.Namespace()) + } + for i, tc := range testCases { testConfig := fmt.Sprintf("%+v", tc) var resp *http.Response - client := makeHTTPClient(tc.useHTTP2Transport, http2ClientTimeout) + client := makeHTTPClient(tc.useHTTP2Transport, http2ClientTimeout, lbAddress) o.Expect(wait.Poll(time.Second, 5*time.Minute, func() (bool, error) { host := tc.route + "." + shardFQDN @@ -520,6 +545,23 @@ var _ = g.Describe("[sig-network-edge][Conformance][Area:Networking][Feature:Rou }) }) +func getClusterBaseDomainName(oc *exutil.CLI, timeout time.Duration) (string, error) { + var domain string + + if err := wait.PollUntilContextTimeout(context.Background(), time.Second, timeout, true, func(ctx context.Context) (bool, error) { + dns, err := oc.AdminConfigClient().ConfigV1().DNSes().Get(ctx, "cluster", metav1.GetOptions{}) + if err != nil { + e2e.Logf("Get dnses.config/cluster failed: %v, retrying...", err) + return false, nil + } + domain = dns.Spec.BaseDomain + return true, nil + }); err != nil { + return "", err + } + return domain, nil +} + func getDefaultIngressClusterDomainName(oc *exutil.CLI, timeout time.Duration) (string, error) { var domain string @@ -598,3 +640,32 @@ func platformHasHTTP2LoadBalancerService(platformType configv1.PlatformType) boo return false } } + +func isDNSManaged(oc *exutil.CLI, timeout time.Duration) (bool, error) { + var status operatorv1.ConditionStatus + conditionFound := false + + if err := wait.PollUntilContextTimeout(context.Background(), time.Second, timeout, true, func(ctx context.Context) (bool, error) { + ic, err := oc.AdminOperatorClient().OperatorV1().IngressControllers("openshift-ingress-operator").Get(ctx, "default", metav1.GetOptions{}) + if err != nil { + e2e.Logf("Failed to get default ingresscontroller: %v, retrying...", err) + return false, nil + } + for _, condition := range ic.Status.Conditions { + if condition.Type == string(operatorv1.DNSManagedIngressConditionType) { + status = condition.Status + conditionFound = true + return true, nil + } + } + // DNSManaged condition not present, assume DNS is managed (default behavior) + e2e.Logf("DNSManaged condition not found on default ingresscontroller, assuming DNS is managed") + return true, nil + }); err != nil { + return false, err + } + if !conditionFound { + return true, nil + } + return status != operatorv1.ConditionFalse, nil +} diff --git a/test/extended/router/idle.go b/test/extended/router/idle.go index e89a402d10b6..0c7ad33ede44 100644 --- a/test/extended/router/idle.go +++ b/test/extended/router/idle.go @@ -306,7 +306,7 @@ func waitForRunningPods(oc *exutil.CLI, podCount int, podLabels labels.Selector, // and will return an error if the conditions are not met after the // specified timeout. func waitHTTPGetStatus(hostname string, statusCode int, timeout time.Duration) error { - client := makeHTTPClient(false, 30*time.Second) + client := makeHTTPClient(false, 30*time.Second, "") var attempt int url := "http://" + hostname From edfb16630f2634ceed81d79ffa1b3351c602d23c Mon Sep 17 00:00:00 2001 From: Sandhya Dasu Date: Thu, 14 May 2026 17:50:43 -0400 Subject: [PATCH 2/2] Updated tests to have 2 ways of determining if DNS is managed In Gateway API tests, we determine if the DNS is managed if zones are configured in the cluster's DNS config resource. In other tests, it is determined by looking at the DNSManagedIngressConditionType of the default IngressController. --- test/extended/router/gatewayapi_upgrade.go | 4 +- test/extended/router/gatewayapicontroller.go | 51 ++++++++++++-------- test/extended/router/grpc-interop.go | 4 +- test/extended/router/http2.go | 4 ++ 4 files changed, 38 insertions(+), 25 deletions(-) diff --git a/test/extended/router/gatewayapi_upgrade.go b/test/extended/router/gatewayapi_upgrade.go index b3f37b30d691..2419e95bf199 100644 --- a/test/extended/router/gatewayapi_upgrade.go +++ b/test/extended/router/gatewayapi_upgrade.go @@ -140,7 +140,7 @@ func (t *GatewayAPIUpgradeTest) Setup(ctx context.Context, f *e2e.Framework) { if t.loadBalancerSupported { g.By("Verifying HTTP connectivity before upgrade") - assertHttpRouteConnection(t.oc, t.gatewayName+"-openshift-default", t.hostname, t.loadBalancerSupported) + assertHttpRouteConnection(t.oc, t.gatewayName+"-openshift-default", t.hostname) e2e.Logf("HTTPRoute connectivity verified before upgrade") } } @@ -184,7 +184,7 @@ func (t *GatewayAPIUpgradeTest) Test(ctx context.Context, f *e2e.Framework, done if t.loadBalancerSupported { g.By("Verifying HTTP connectivity after upgrade") - assertHttpRouteConnection(t.oc, t.gatewayName+"-openshift-default", t.hostname, t.loadBalancerSupported) + assertHttpRouteConnection(t.oc, t.gatewayName+"-openshift-default", t.hostname) } if migrationOccurred { diff --git a/test/extended/router/gatewayapicontroller.go b/test/extended/router/gatewayapicontroller.go index d1c2a055da87..54841cac6303 100644 --- a/test/extended/router/gatewayapicontroller.go +++ b/test/extended/router/gatewayapicontroller.go @@ -366,10 +366,9 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat assertGatewayLoadbalancerReady(oc, gw, gw+"-openshift-default") } - // check the dns record is created and status of the published dnsrecord of all zones are True - if managedDNS { - assertDNSRecordStatus(oc, gw) - } + // Check the DNSRecord status - when DNS zones are configured, expects Published=True; + // when DNS zones are not configured (custom-dns), expects Published!=True + assertDNSRecordStatus(oc, gw) }) g.It("Ensure HTTPRoute object is created", func() { @@ -390,10 +389,9 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat _, gwerr := createAndCheckGateway(oc, gw, gatewayClassName, customDomain, loadBalancerSupported) o.Expect(gwerr).NotTo(o.HaveOccurred(), "Failed to create Gateway") - // make sure the DNSRecord is ready to use - if managedDNS { - assertDNSRecordStatus(oc, gw) - } + // Verify DNSRecord is ready - expects Published=True when zones configured, + // Published!=True when zones not configured (custom-dns) + assertDNSRecordStatus(oc, gw) g.By("Create the http route using the custom gateway") defaultRoutename := "test-hostname." + customDomain @@ -404,7 +402,7 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat if loadBalancerSupported { g.By("Validating the http connectivity to the backend application") - assertHttpRouteConnection(oc, gw+"-openshift-default", defaultRoutename, loadBalancerSupported) + assertHttpRouteConnection(oc, gw+"-openshift-default", defaultRoutename) } }) @@ -611,13 +609,26 @@ func getPlatformCapabilities(oc *exutil.CLI) (loadBalancerSupported bool, manage loadBalancerSupported = false } - managedDNS, err = isDNSManaged(oc, time.Minute) - o.Expect(err).NotTo(o.HaveOccurred(), "Failed to check if DNS is managed") + managedDNS, err = isDNSZoneConfigured(oc) + o.Expect(err).NotTo(o.HaveOccurred(), "Failed to check if DNS zone is configured") e2e.Logf("Platform: %s, LoadBalancer supported: %t, DNS managed: %t", infra.Status.PlatformStatus.Type, loadBalancerSupported, managedDNS) return loadBalancerSupported, managedDNS } +// isDNSZoneConfigured checks if the cluster has DNS zones configured (public or private). +// On platforms like vSphere without external DNS, DNS records cannot be managed. +// This checks dns.config.openshift.io/cluster PublicZone/PrivateZone, and is named +// isDNSZoneConfigured (not isDNSManaged) to avoid confusion with the DNSManaged +// IngressController condition. +func isDNSZoneConfigured(oc *exutil.CLI) (bool, error) { + dnsConfig, err := oc.AdminConfigClient().ConfigV1().DNSes().Get(context.Background(), "cluster", metav1.GetOptions{}) + if err != nil { + return false, err + } + return dnsConfig.Spec.PrivateZone != nil || dnsConfig.Spec.PublicZone != nil, nil +} + // isIPv6OrDualStack checks if the cluster is using IPv6 or dual-stack networking. // Returns true if any ServiceNetwork CIDR is IPv6 (indicates IPv6-only or dual-stack). func isIPv6OrDualStack(oc *exutil.CLI) (bool, error) { @@ -799,9 +810,9 @@ func assertGatewayLoadbalancerReady(oc *exutil.CLI, gwName, gwServiceName string // When DNS is managed, it waits for all zones to have Published=True. // When DNS is unmanaged, it waits for all zones to have Published!=True (expected in custom-dns clusters). func assertDNSRecordStatus(oc *exutil.CLI, gatewayName string) { - dnsManaged, err := isDNSManaged(oc, time.Minute) + dnsManaged, err := isDNSZoneConfigured(oc) if err != nil { - e2e.Failf("Failed to get default ingresscontroller DNSManaged status: %v", err) + e2e.Failf("Failed to check if DNS zone is configured: %v", err) } // find the DNS Record and confirm its zone status @@ -1070,14 +1081,14 @@ func assertHttpRouteSuccessful(oc *exutil.CLI, gwName, name string) (*gatewayapi } // assertHttpRouteConnection checks if the http route of the given name replies successfully, -// and returns an error if not -func assertHttpRouteConnection(oc *exutil.CLI, gwServiceName, hostname string, loadBalancerSupported bool) { - isDNSManaged, err := isDNSManaged(oc, time.Minute) +// and returns an error if not. This function should only be called when loadBalancerSupported is true. +func assertHttpRouteConnection(oc *exutil.CLI, gwServiceName, hostname string) { + isDNSZoneConfigured, err := isDNSZoneConfigured(oc) if err != nil { - e2e.Failf("Failed to get default ingresscontroller DNSManaged status: %v", err) + e2e.Failf("Failed to check if DNS zone is configured: %v", err) } lbAddress := "" - if isDNSManaged { + if isDNSZoneConfigured { err := wait.PollUntilContextTimeout(context.Background(), 20*time.Second, dnsResolutionTimeout, false, func(context context.Context) (bool, error) { _, err := net.LookupHost(hostname) if err != nil { @@ -1087,10 +1098,8 @@ func assertHttpRouteConnection(oc *exutil.CLI, gwServiceName, hostname string, l return true, nil }) o.Expect(err).NotTo(o.HaveOccurred(), "Timed out waiting for HTTP route's hostname %q to be resolved: %v", hostname, err) - } else if loadBalancerSupported { - lbAddress = getLoadBalancerAddress(oc, gwServiceName) } else { - e2e.Failf("Platform does not support load balancers and DNS is unmanaged - cannot verify HTTP route connectivity") + lbAddress = getLoadBalancerAddress(oc, gwServiceName) } // Create the http client to check the response status code. diff --git a/test/extended/router/grpc-interop.go b/test/extended/router/grpc-interop.go index 817443c172dd..8901856305d4 100644 --- a/test/extended/router/grpc-interop.go +++ b/test/extended/router/grpc-interop.go @@ -53,8 +53,8 @@ var _ = g.Describe("[sig-network-edge][Conformance][Area:Networking][Feature:Rou g.Skip("Skip on platforms where the default router is not exposed by a load balancer service.") } - baseDomain, err := getClusterBaseDomainName(oc, time.Minute) - o.Expect(err).NotTo(o.HaveOccurred(), "failed to find base domain name") + defaultDomain, err := getDefaultIngressClusterDomainName(oc, time.Minute) + o.Expect(err).NotTo(o.HaveOccurred(), "failed to find default domain name") g.By("Locating the canary image reference") image, err := getCanaryImage(oc) diff --git a/test/extended/router/http2.go b/test/extended/router/http2.go index 042edbe8b7d6..710a1a2ca53c 100644 --- a/test/extended/router/http2.go +++ b/test/extended/router/http2.go @@ -641,6 +641,10 @@ func platformHasHTTP2LoadBalancerService(platformType configv1.PlatformType) boo } } +// isDNSManaged checks the DNSManaged condition on the default IngressController. +// Returns true if the DNSManaged condition is True or not present (default behavior). +// Returns false if the DNSManaged condition is explicitly False (custom-dns clusters). +// This checks the IngressController condition, not dns.config.openshift.io/cluster zones. func isDNSManaged(oc *exutil.CLI, timeout time.Duration) (bool, error) { var status operatorv1.ConditionStatus conditionFound := false