Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions test/extended/router/gatewayapi_upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
e2e.Logf("HTTPRoute connectivity verified before upgrade")
}
}
Expand Down Expand Up @@ -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)
}

if migrationOccurred {
Expand Down
145 changes: 95 additions & 50 deletions test/extended/router/gatewayapicontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package router

import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
Expand Down Expand Up @@ -367,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() {
Expand All @@ -391,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
Expand All @@ -403,9 +400,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)
}
})

Expand Down Expand Up @@ -612,18 +609,24 @@ func getPlatformCapabilities(oc *exutil.CLI) (loadBalancerSupported bool, manage
loadBalancerSupported = false
}

managedDNS = isDNSManaged(oc)
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
}

// isDNSManaged checks if the cluster has DNS zones configured (public or private).
// isDNSZoneConfigured 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 {
// 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{})
o.Expect(err).NotTo(o.HaveOccurred(), "Failed to get DNS config")
return dnsConfig.Spec.PrivateZone != nil || dnsConfig.Spec.PublicZone != nil
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.
Expand Down Expand Up @@ -753,18 +756,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 != "" {
Expand All @@ -773,11 +775,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)
Expand All @@ -795,10 +806,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 := isDNSZoneConfigured(oc)
if err != nil {
e2e.Failf("Failed to check if DNS zone is configured: %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 {
Expand All @@ -810,20 +828,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
}
Comment on lines +836 to +839
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we are now enabling assertDNSRecordStatus to check DNSRecords for unmanaged clusters in this PR - however, this logic will never pass because (see comment above) Gateway API will be unmanaged only when there are no zones. So this will get stuck.

I think we have two options:

  1. Keep it simple - don't call assertDNSRecordStatus for unsupported DNS clusters (and remove all changes to assertDNSRecordStatus)
  2. Or, you need to check dnsManaged here, and return early - there are no zones to check

Gateway API DNS logic is simpler than IngressController: the DNSRecord is always created, and whether it actually publishes depends only on whether zones exist and the domain matches baseDomain. There's no per-Gateway opt-out mechanism like the IngressController's dnsManagementPolicy or endpointPublishingStrategy fields.


// 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
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not knowing the Gateway API code, would you be OK ignoring this condition and recording as a debug/info message instead of reporting this as an error?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I'm following: this code can never be reached in any Gateway API test scenario today. If DNS isn't configured, there are no zones to check against. Gateway API DNS is strictly on or off, there's no partial state.

I think I understand some of the confusion - when #30131 was written before I added isDNSManaged in #30946. The rebase doesn't really make sense like it did before.

For DNS concerns, the previously merged #30946 already handles the unmanaged DNS case for Gateway API tests. The changes to assertDNSRecordStatus in this PR aren't needed. Consider reverting all DNS-related changes to the Gateway API tests, except for:

  • The isDNSManaged rename (good idea)
  • The load balancer address changes that you need in assertHttpRouteConnection

Let me know if I'm missing something, but I'd rather not change anything unnecessarily.

}
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)
}
Expand Down Expand Up @@ -1040,25 +1081,29 @@ 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},
},
// 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 check if DNS zone is configured: %v", err)
}
lbAddress := ""
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 {
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 {
lbAddress = getLoadBalancerAddress(oc, gwServiceName)
}

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) {
Expand Down
16 changes: 13 additions & 3 deletions test/extended/router/grpc-interop.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify symbol usage in this file/package.
rg -n --type=go '\bdefaultDomain\b' test/extended/router/grpc-interop.go
rg -n --type=go '\bbaseDomain\b' test/extended/router/grpc-interop.go
rg -n --type=go '\bgetClusterBaseDomainName\s*\(' test/extended/router

Repository: openshift/origin

Length of output: 511


Fix undefined baseDomain variable to resolve compile-time failure.

Line 199 uses baseDomain, but line 56 assigns to defaultDomain instead. This leaves baseDomain undefined and causes a compilation error.

Suggested fix
-			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 cluster base domain name")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/extended/router/grpc-interop.go` at line 199, The code uses an undefined
variable baseDomain when building shardFQDN; replace baseDomain with the
existing defaultDomain variable (the one assigned earlier) so shardFQDN :=
oc.Namespace() + "." + defaultDomain (i.e., use defaultDomain instead of
baseDomain) to fix the compile error involving shardFQDN, oc.Namespace(),
baseDomain and defaultDomain.


g.By("Creating routes to test for gRPC interoperability")
routeType := oc.Namespace()
Expand Down Expand Up @@ -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",
Expand All @@ -352,15 +361,15 @@ 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())
}
})
})
})

// 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
Expand All @@ -371,6 +380,7 @@ func grpcExecTestCases(oc *exutil.CLI, routeType routev1.TLSTerminationType, tim
Port: 443,
UseTLS: true,
Insecure: true,
Target: lbAddress,
}

if routeType == "h2c" {
Expand Down
17 changes: 17 additions & 0 deletions test/extended/router/grpc-interop/clientconn.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package grpc_interop

import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net"
"strconv"

Expand All @@ -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) {
Expand Down Expand Up @@ -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())...)
}
8 changes: 4 additions & 4 deletions test/extended/router/h2spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
Loading