Skip to content
Merged
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
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ require (
github.com/onsi/ginkgo/v2 v2.21.0
github.com/onsi/gomega v1.35.1
github.com/opencontainers/go-digest v1.0.0
github.com/openshift/api v0.0.0-20250425163235-9b80d67473bc
github.com/openshift/api v0.0.0-20250513132935-9052dea86694
github.com/openshift/apiserver-library-go v0.0.0-20250127121756-dc9a973f14ce
github.com/openshift/build-machinery-go v0.0.0-20250102153059-e85a1a7ecb5c
github.com/openshift/client-go v0.0.0-20250425165505-5f55ff6979a1
github.com/openshift/client-go v0.0.0-20250513150353-9ea84fa6431b
github.com/openshift/cluster-network-operator v0.0.0-20240708200319-1cd8678b38fb
github.com/openshift/library-go v0.0.0-20250129210218-fe56c2cf5d70
github.com/ovn-org/ovn-kubernetes/go-controller v0.0.0-20250118001652-a8b9c3c31417
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -603,14 +603,14 @@ github.com/opencontainers/selinux v1.11.1 h1:nHFvthhM0qY8/m+vfhJylliSshm8G1jJ2jD
github.com/opencontainers/selinux v1.11.1/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
github.com/openshift-eng/openshift-tests-extension v0.0.0-20250220212757-b9c4d98a0c45 h1:hXpbYtP3iTh8oy/RKwKkcMziwchY3fIk95ciczf7cOA=
github.com/openshift-eng/openshift-tests-extension v0.0.0-20250220212757-b9c4d98a0c45/go.mod h1:6gkP5f2HL0meusT0Aim8icAspcD1cG055xxBZ9yC68M=
github.com/openshift/api v0.0.0-20250425163235-9b80d67473bc h1:BGKjHtYzBweOSu1UwTnNqtPbJZ4VzOTqVFlUDpP+6U8=
github.com/openshift/api v0.0.0-20250425163235-9b80d67473bc/go.mod h1:yk60tHAmHhtVpJQo3TwVYq2zpuP70iJIFDCmeKMIzPw=
github.com/openshift/api v0.0.0-20250513132935-9052dea86694 h1:kPnk1+m89LJHexYsTP+MVM9OgJLxcpUR3vRdMQNF66s=
github.com/openshift/api v0.0.0-20250513132935-9052dea86694/go.mod h1:yk60tHAmHhtVpJQo3TwVYq2zpuP70iJIFDCmeKMIzPw=
github.com/openshift/apiserver-library-go v0.0.0-20250127121756-dc9a973f14ce h1:w0Up6YV1APcn20v/1h5IfuToz96o2pVqZyjzbw0yotU=
github.com/openshift/apiserver-library-go v0.0.0-20250127121756-dc9a973f14ce/go.mod h1:kkSwH4osgejnRIyHfsfkv0V0xfmgH4Yk/VDObaJukHU=
github.com/openshift/build-machinery-go v0.0.0-20250102153059-e85a1a7ecb5c h1:6XcszPFZpan4qll5XbdLll7n1So3IsPn28aw2j1obMo=
github.com/openshift/build-machinery-go v0.0.0-20250102153059-e85a1a7ecb5c/go.mod h1:8jcm8UPtg2mCAsxfqKil1xrmRMI3a+XU2TZ9fF8A7TE=
github.com/openshift/client-go v0.0.0-20250425165505-5f55ff6979a1 h1:2HPG58V07TrrSGBviNPd0PY42vYHPPCIEwj/pb9nUlY=
github.com/openshift/client-go v0.0.0-20250425165505-5f55ff6979a1/go.mod h1:kH5mjMfcHCF0tEnxwvNJTLMnlbrEt3Ua+vMVGvBOK5w=
github.com/openshift/client-go v0.0.0-20250513150353-9ea84fa6431b h1:ffS7iyeXP5PdiiVVssk2GCP6PqrVR4LdCdrKRcI+ogM=
github.com/openshift/client-go v0.0.0-20250513150353-9ea84fa6431b/go.mod h1:ic71V3s+SXPaFMRd80eZhNvjEWom7s61GXE2wmYCfOI=
github.com/openshift/cluster-network-operator v0.0.0-20240708200319-1cd8678b38fb h1:Dr0dbSQTAU9UaoAvimGjR+fsvwx2twJ5KR0s/jyAz88=
github.com/openshift/cluster-network-operator v0.0.0-20240708200319-1cd8678b38fb/go.mod h1:LnhqxbWhAnhPwilJ4yX1/ly7wCMCYJKkaiSJQSh+Wjg=
github.com/openshift/kubernetes v1.30.1-0.20250509031521-18f7f4cbd6ea h1:HcjGXaqrP+cnCMw6Ppqvqt1ryxCzTDJI2Hh+N9Ujxls=
Expand Down
196 changes: 156 additions & 40 deletions test/extended/router/gatewayapicontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ package router

import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"strings"
"time"

g "github.com/onsi/ginkgo/v2"
o "github.com/onsi/gomega"

configv1 "github.com/openshift/api/config/v1"
operatoringressv1 "github.com/openshift/api/operatoringress/v1"

exutil "github.com/openshift/origin/test/extended/util"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -32,6 +37,13 @@ var (
}
)

const (
// Max time duration for the DNS resolution
dnsResolutionTimeout = 10 * time.Minute
// Max time duration for the Load balancer address
loadBalancerReadyTimeout = 10 * time.Minute
)

var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io]", g.Ordered, g.Serial, func() {
defer g.GinkgoRecover()
var (
Expand All @@ -40,6 +52,7 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat
err error
gateways []string
)

const (
// The expected OSSM subscription name.
expectedSubscriptionName = "servicemeshoperator3"
Expand Down Expand Up @@ -145,15 +158,15 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat

g.By("Confirm that Istio CR is created and in healthy state")
waitForIstioHealthy(oc)

})

g.It("Ensure default gatewayclass is accepted", func() {

g.By("Check if default GatewayClass is accepted after OLM resources are successful")
errCheck := checkGatewayClass(oc, gatewayClassName)
o.Expect(errCheck).NotTo(o.HaveOccurred(), "GatewayClass %q was not installed and accepted", gatewayClassName)

})

g.It("Ensure custom gatewayclass can be accepted", func() {
customGatewayClassName := "custom-gatewayclass"

Expand Down Expand Up @@ -182,7 +195,6 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat
})

g.It("Ensure LB, service, and dnsRecord are created for a Gateway object", func() {
var lbAddress string
g.By("Ensure default GatewayClass is accepted")
errCheck := checkGatewayClass(oc, gatewayClassName)
o.Expect(errCheck).NotTo(o.HaveOccurred(), "GatewayClass %q was not installed and accepted", gatewayClassName)
Expand All @@ -199,30 +211,10 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat
o.Expect(gwerr).NotTo(o.HaveOccurred(), "failed to create Gateway")

g.By("Verify the gateway's LoadBalancer service and DNSRecords")
// check gateway LB service, note that External-IP might be hostname (AWS) or IP (Azure/GCP)
lbService, err := oc.AdminKubeClient().CoreV1().Services("openshift-ingress").Get(context.Background(), gw+"-openshift-default", metav1.GetOptions{})
o.Expect(err).NotTo(o.HaveOccurred())
if lbService.Status.LoadBalancer.Ingress[0].Hostname != "" {
lbAddress = lbService.Status.LoadBalancer.Ingress[0].Hostname
} else {
lbAddress = lbService.Status.LoadBalancer.Ingress[0].IP
}
e2e.Logf("The load balancer External-IP is: %v", lbAddress)
assertGatewayLoadbalancerReady(oc, gw, gw+"-openshift-default")

gwlist, haerr := oc.AdminGatewayApiClient().GatewayV1().Gateways("openshift-ingress").Get(context.Background(), gw, metav1.GetOptions{})
e2e.Logf("The gateway hostname address is %v ", gwlist.Status.Addresses[0].Value)
o.Expect(haerr).NotTo(o.HaveOccurred())
o.Expect(lbAddress).To(o.Equal(gwlist.Status.Addresses[0].Value))

// get the dnsrecord name
dnsRecordName, err := oc.AsAdmin().WithoutNamespace().Run("get").Args("-n", "openshift-ingress", "dnsrecord", "-l", "gateway.networking.k8s.io/gateway-name="+gw, "-o=jsonpath={.items[0].metadata.name}").Output()
o.Expect(err).NotTo(o.HaveOccurred())
e2e.Logf("The gateway API dnsrecord name is: %v", dnsRecordName)
// check status of published dnsrecord of the gateway, all zones should be True (not contains False)
dnsRecordStatus, err := oc.AsAdmin().WithoutNamespace().Run("get").Args("-n", "openshift-ingress", "dnsrecord", dnsRecordName, `-o=jsonpath={.status.zones[*].conditions[0].status}`).Output()
o.Expect(err).NotTo(o.HaveOccurred())
e2e.Logf("The dnsrecords status of all zones: %v", dnsRecordStatus)
o.Expect(dnsRecordStatus).NotTo(o.ContainSubstring("False"))
// check the dns record is created and status of the published dnsrecord of all zones are True
assertDNSRecordStatus(oc, gw)
})

g.It("Ensure HTTPRoute object is created", func() {
Expand All @@ -241,12 +233,18 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat
_, gwerr := createAndCheckGateway(oc, gw, gatewayClassName, customDomain)
o.Expect(gwerr).NotTo(o.HaveOccurred(), "Failed to create Gateway")

// make sure the DNSRecord is ready to use.
assertDNSRecordStatus(oc, gw)

g.By("Create the http route using the custom gateway")
defaultRoutename := "test-hostname." + customDomain
createHttpRoute(oc, gw, "test-httproute", defaultRoutename, "echo-pod-"+gw)

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")
assertHttpRouteConnection(defaultRoutename)
})
})

Expand Down Expand Up @@ -331,33 +329,30 @@ func createAndCheckGateway(oc *exutil.CLI, gwname, gwclassname, domain string) (
}

func checkGatewayStatus(oc *exutil.CLI, gwname, ingressNameSpace string) (*gatewayapiv1.Gateway, error) {
var err error
gateway := &gatewayapiv1.Gateway{}

waitErr := wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 10*time.Minute, false, func(context context.Context) (bool, error) {
gateway, err = oc.AdminGatewayApiClient().GatewayV1().Gateways(ingressNameSpace).Get(context, gwname, metav1.GetOptions{})
programmedGateway := &gatewayapiv1.Gateway{}
if err := wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 10*time.Minute, false, func(context context.Context) (bool, error) {
gateway, err := oc.AdminGatewayApiClient().GatewayV1().Gateways(ingressNameSpace).Get(context, gwname, metav1.GetOptions{})
if err != nil {
e2e.Logf("Failed to get gateway object, retrying...")
e2e.Logf("Failed to get gateway %q: %v, retrying...", gwname, err)
return false, nil
}
// Checking the gateway controller status
for _, condition := range gateway.Status.Conditions {
if condition.Type == string(gatewayapiv1.GatewayConditionProgrammed) {
if condition.Status == metav1.ConditionTrue {
e2e.Logf("The gateway controller is up and running")
e2e.Logf("The gateway controller for gateway %q is programmed", gwname)
programmedGateway = gateway
return true, nil
}
}
}
e2e.Logf("Found gateway %q but the controller is still not programmed, retrying...", gateway.Name)
e2e.Logf("Found gateway %q but the controller is still not programmed, retrying...", gwname)
return false, nil
})

if waitErr != nil {
return nil, fmt.Errorf("Timed out waiting for gateway %q to become programmed: %w", gateway.Name, waitErr)
}); err != nil {
return nil, fmt.Errorf("timed out waiting for gateway %q to become programmed: %w", gwname, err)
}
e2e.Logf("Gateway %q successfully programmed!", gateway.Name)
return gateway, nil
e2e.Logf("Gateway %q successfully programmed!", gwname)
return programmedGateway, nil
}

// buildGateway initializes the Gateway and returns its address.
Expand All @@ -377,6 +372,77 @@ 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)
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("openshift-ingress").Get(context, gwServiceName, metav1.GetOptions{})
if err != nil {
e2e.Logf("Failed to get service %q: %v, retrying...", gwServiceName, err)
return false, nil
}
if lbService.Status.LoadBalancer.Ingress[0].Hostname != "" {
lbAddress = lbService.Status.LoadBalancer.Ingress[0].Hostname
} else {
lbAddress = lbService.Status.LoadBalancer.Ingress[0].IP
}
if lbAddress == "" {
e2e.Logf("No load balancer address for service %q, retrying", gwServiceName)
return false, nil
}
e2e.Logf("Got load balancer address for service %q: %v", gwServiceName, lbAddress)

gw, err := oc.AdminGatewayApiClient().GatewayV1().Gateways("openshift-ingress").Get(context, gwName, metav1.GetOptions{})
if err != nil {
e2e.Logf("Failed to get gateway %q, retrying...", gwName)
return false, nil
}
for _, gwAddr := range gw.Status.Addresses {
if gwAddr.Value == lbAddress {
return true, nil
}
}

e2e.Logf("Gateway %q does not have service load balancer address, retrying...", gwName)
return false, nil
})
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.
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) {
gatewayDNSRecord := &operatoringressv1.DNSRecord{}
gatewayDNSRecords, err := oc.AdminIngressClient().IngressV1().DNSRecords("openshift-ingress").List(context, metav1.ListOptions{})
if err != nil {
e2e.Logf("Failed to list DNS records for gateway %q: %v, retrying...", gatewayName, err)
return false, nil
}

// get the desired DNS records of the given gateway
for _, record := range gatewayDNSRecords.Items {
if record.Labels["gateway.networking.k8s.io/gateway-name"] == gatewayName {
gatewayDNSRecord = &record
break
}
}

// checking the gateway DNS record status
for _, zone := range gatewayDNSRecord.Status.Zones {
for _, condition := range zone.Conditions {
if condition.Type == "Published" && condition.Status == "True" {
return true, nil
}
}
}
e2e.Logf("DNS record %q is not ready, retrying...", gatewayDNSRecord.Name)
return false, nil
})
o.Expect(err).NotTo(o.HaveOccurred(), "Timed out waiting for gateway %q DNSRecord to become ready", gatewayName)
}

// createHttpRoute checks if the HTTPRoute can be created.
// If it can't an error is returned.
func createHttpRoute(oc *exutil.CLI, gwName, routeName, hostname, backendRefname string) (*gatewayapiv1.HTTPRoute, error) {
Expand Down Expand Up @@ -546,6 +612,56 @@ func assertHttpRouteSuccessful(oc *exutil.CLI, gwName, name string) (*gatewayapi
return checkHttpRoute, nil
}

// 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},
},
}

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)

// 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) {
statusCode, err := getHttpResponse(client, hostname)
if err != nil {
e2e.Logf("HTTP GET request to %q failed: %v, retrying...", hostname, err)
return false, nil
}
if statusCode != http.StatusOK {
e2e.Logf("Unexpected status code for HTTP GET request to %q: %v, retrying...", hostname, statusCode)
return false, nil // retry on 503 as pod/service may not be ready
}
return true, nil
})
o.Expect(err).NotTo(o.HaveOccurred(), "Timed out waiting for successful HTTP GET response from %q: %v", hostname, err)
}

func getHttpResponse(client *http.Client, hostname string) (int, error) {
// Send the HTTP request.
response, err := client.Get("http://" + hostname)
if err != nil {
return 0, err
}

// Close response body.
defer response.Body.Close()

return response.StatusCode, nil
}

// Check for the existence of the okd-scos string in the version name to determine if it is OKD
func isOKD(oc *exutil.CLI) (bool, error) {
current, err := exutil.GetCurrentVersion(context.TODO(), oc.AdminConfig())
Expand Down
5 changes: 5 additions & 0 deletions test/extended/util/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import (
mcv1client "github.com/openshift/client-go/machineconfiguration/clientset/versioned"
oauthv1client "github.com/openshift/client-go/oauth/clientset/versioned"
operatorv1client "github.com/openshift/client-go/operator/clientset/versioned"
ingressv1client "github.com/openshift/client-go/operatoringress/clientset/versioned"
projectv1client "github.com/openshift/client-go/project/clientset/versioned"
quotav1client "github.com/openshift/client-go/quota/clientset/versioned"
routev1client "github.com/openshift/client-go/route/clientset/versioned"
Expand Down Expand Up @@ -732,6 +733,10 @@ func (c *CLI) AdminImageClient() imagev1client.Interface {
return imagev1client.NewForConfigOrDie(c.AdminConfig())
}

func (c *CLI) AdminIngressClient() ingressv1client.Interface {
return ingressv1client.NewForConfigOrDie(c.AdminConfig())
}

func (c *CLI) AdminOAuthClient() oauthv1client.Interface {
return oauthv1client.NewForConfigOrDie(c.AdminConfig())
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading