@@ -2,14 +2,19 @@ package router
22
33import (
44 "context"
5+ "crypto/tls"
56 "fmt"
7+ "net"
8+ "net/http"
69 "strings"
710 "time"
811
912 g "github.com/onsi/ginkgo/v2"
1013 o "github.com/onsi/gomega"
1114
1215 configv1 "github.com/openshift/api/config/v1"
16+ operatoringressv1 "github.com/openshift/api/operatoringress/v1"
17+
1318 exutil "github.com/openshift/origin/test/extended/util"
1419 corev1 "k8s.io/api/core/v1"
1520 apierrors "k8s.io/apimachinery/pkg/api/errors"
3237 }
3338)
3439
40+ const (
41+ // Max time duration for the DNS resolution
42+ dnsResolutionTimeout = 10 * time .Minute
43+ // Max time duration for the Load balancer address
44+ loadBalancerReadyTimeout = 10 * time .Minute
45+ )
46+
3547var _ = g .Describe ("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io]" , g .Ordered , g .Serial , func () {
3648 defer g .GinkgoRecover ()
3749 var (
@@ -40,6 +52,7 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat
4052 err error
4153 gateways []string
4254 )
55+
4356 const (
4457 // The expected OSSM subscription name.
4558 expectedSubscriptionName = "servicemeshoperator3"
@@ -145,15 +158,15 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat
145158
146159 g .By ("Confirm that Istio CR is created and in healthy state" )
147160 waitForIstioHealthy (oc )
148-
149161 })
162+
150163 g .It ("Ensure default gatewayclass is accepted" , func () {
151164
152165 g .By ("Check if default GatewayClass is accepted after OLM resources are successful" )
153166 errCheck := checkGatewayClass (oc , gatewayClassName )
154167 o .Expect (errCheck ).NotTo (o .HaveOccurred (), "GatewayClass %q was not installed and accepted" , gatewayClassName )
155-
156168 })
169+
157170 g .It ("Ensure custom gatewayclass can be accepted" , func () {
158171 customGatewayClassName := "custom-gatewayclass"
159172
@@ -182,7 +195,6 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat
182195 })
183196
184197 g .It ("Ensure LB, service, and dnsRecord are created for a Gateway object" , func () {
185- var lbAddress string
186198 g .By ("Ensure default GatewayClass is accepted" )
187199 errCheck := checkGatewayClass (oc , gatewayClassName )
188200 o .Expect (errCheck ).NotTo (o .HaveOccurred (), "GatewayClass %q was not installed and accepted" , gatewayClassName )
@@ -199,30 +211,10 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat
199211 o .Expect (gwerr ).NotTo (o .HaveOccurred (), "failed to create Gateway" )
200212
201213 g .By ("Verify the gateway's LoadBalancer service and DNSRecords" )
202- // check gateway LB service, note that External-IP might be hostname (AWS) or IP (Azure/GCP)
203- lbService , err := oc .AdminKubeClient ().CoreV1 ().Services ("openshift-ingress" ).Get (context .Background (), gw + "-openshift-default" , metav1.GetOptions {})
204- o .Expect (err ).NotTo (o .HaveOccurred ())
205- if lbService .Status .LoadBalancer .Ingress [0 ].Hostname != "" {
206- lbAddress = lbService .Status .LoadBalancer .Ingress [0 ].Hostname
207- } else {
208- lbAddress = lbService .Status .LoadBalancer .Ingress [0 ].IP
209- }
210- e2e .Logf ("The load balancer External-IP is: %v" , lbAddress )
214+ assertGatewayLoadbalancerReady (oc , gw , gw + "-openshift-default" )
211215
212- gwlist , haerr := oc .AdminGatewayApiClient ().GatewayV1 ().Gateways ("openshift-ingress" ).Get (context .Background (), gw , metav1.GetOptions {})
213- e2e .Logf ("The gateway hostname address is %v " , gwlist .Status .Addresses [0 ].Value )
214- o .Expect (haerr ).NotTo (o .HaveOccurred ())
215- o .Expect (lbAddress ).To (o .Equal (gwlist .Status .Addresses [0 ].Value ))
216-
217- // get the dnsrecord name
218- 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 ()
219- o .Expect (err ).NotTo (o .HaveOccurred ())
220- e2e .Logf ("The gateway API dnsrecord name is: %v" , dnsRecordName )
221- // check status of published dnsrecord of the gateway, all zones should be True (not contains False)
222- dnsRecordStatus , err := oc .AsAdmin ().WithoutNamespace ().Run ("get" ).Args ("-n" , "openshift-ingress" , "dnsrecord" , dnsRecordName , `-o=jsonpath={.status.zones[*].conditions[0].status}` ).Output ()
223- o .Expect (err ).NotTo (o .HaveOccurred ())
224- e2e .Logf ("The dnsrecords status of all zones: %v" , dnsRecordStatus )
225- o .Expect (dnsRecordStatus ).NotTo (o .ContainSubstring ("False" ))
216+ // check the dns record is created and status of the published dnsrecord of all zones are True
217+ assertDNSRecordStatus (oc , gw )
226218 })
227219
228220 g .It ("Ensure HTTPRoute object is created" , func () {
@@ -241,12 +233,18 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat
241233 _ , gwerr := createAndCheckGateway (oc , gw , gatewayClassName , customDomain )
242234 o .Expect (gwerr ).NotTo (o .HaveOccurred (), "Failed to create Gateway" )
243235
236+ // make sure the DNSRecord is ready to use.
237+ assertDNSRecordStatus (oc , gw )
238+
244239 g .By ("Create the http route using the custom gateway" )
245240 defaultRoutename := "test-hostname." + customDomain
246241 createHttpRoute (oc , gw , "test-httproute" , defaultRoutename , "echo-pod-" + gw )
247242
248243 g .By ("Checking the http route using the default gateway is accepted" )
249244 assertHttpRouteSuccessful (oc , gw , "test-httproute" )
245+
246+ g .By ("Validating the http connectivity to the backend application" )
247+ assertHttpRouteConnection (defaultRoutename )
250248 })
251249})
252250
@@ -331,33 +329,30 @@ func createAndCheckGateway(oc *exutil.CLI, gwname, gwclassname, domain string) (
331329}
332330
333331func checkGatewayStatus (oc * exutil.CLI , gwname , ingressNameSpace string ) (* gatewayapiv1.Gateway , error ) {
334- var err error
335- gateway := & gatewayapiv1.Gateway {}
336-
337- waitErr := wait .PollUntilContextTimeout (context .Background (), 2 * time .Second , 10 * time .Minute , false , func (context context.Context ) (bool , error ) {
338- gateway , err = oc .AdminGatewayApiClient ().GatewayV1 ().Gateways (ingressNameSpace ).Get (context , gwname , metav1.GetOptions {})
332+ programmedGateway := & gatewayapiv1.Gateway {}
333+ if err := wait .PollUntilContextTimeout (context .Background (), 2 * time .Second , 10 * time .Minute , false , func (context context.Context ) (bool , error ) {
334+ gateway , err := oc .AdminGatewayApiClient ().GatewayV1 ().Gateways (ingressNameSpace ).Get (context , gwname , metav1.GetOptions {})
339335 if err != nil {
340- e2e .Logf ("Failed to get gateway object , retrying..." )
336+ e2e .Logf ("Failed to get gateway %q: %v , retrying..." , gwname , err )
341337 return false , nil
342338 }
343339 // Checking the gateway controller status
344340 for _ , condition := range gateway .Status .Conditions {
345341 if condition .Type == string (gatewayapiv1 .GatewayConditionProgrammed ) {
346342 if condition .Status == metav1 .ConditionTrue {
347- e2e .Logf ("The gateway controller is up and running" )
343+ e2e .Logf ("The gateway controller for gateway %q is programmed" , gwname )
344+ programmedGateway = gateway
348345 return true , nil
349346 }
350347 }
351348 }
352- e2e .Logf ("Found gateway %q but the controller is still not programmed, retrying..." , gateway . Name )
349+ e2e .Logf ("Found gateway %q but the controller is still not programmed, retrying..." , gwname )
353350 return false , nil
354- })
355-
356- if waitErr != nil {
357- return nil , fmt .Errorf ("Timed out waiting for gateway %q to become programmed: %w" , gateway .Name , waitErr )
351+ }); err != nil {
352+ return nil , fmt .Errorf ("timed out waiting for gateway %q to become programmed: %w" , gwname , err )
358353 }
359- e2e .Logf ("Gateway %q successfully programmed!" , gateway . Name )
360- return gateway , nil
354+ e2e .Logf ("Gateway %q successfully programmed!" , gwname )
355+ return programmedGateway , nil
361356}
362357
363358// buildGateway initializes the Gateway and returns its address.
@@ -377,6 +372,77 @@ func buildGateway(name, namespace, gcname, fromNs, domain string) *gatewayapiv1.
377372 }
378373}
379374
375+ // assertGatewayLoadbalancerReady verifies that the given gateway has the service's load balancer address assigned.
376+ func assertGatewayLoadbalancerReady (oc * exutil.CLI , gwName , gwServiceName string ) {
377+ // check gateway LB service, note that External-IP might be hostname (AWS) or IP (Azure/GCP)
378+ var lbAddress string
379+ err := wait .PollUntilContextTimeout (context .Background (), 1 * time .Second , loadBalancerReadyTimeout , false , func (context context.Context ) (bool , error ) {
380+ lbService , err := oc .AdminKubeClient ().CoreV1 ().Services ("openshift-ingress" ).Get (context , gwServiceName , metav1.GetOptions {})
381+ if err != nil {
382+ e2e .Logf ("Failed to get service %q: %v, retrying..." , gwServiceName , err )
383+ return false , nil
384+ }
385+ if lbService .Status .LoadBalancer .Ingress [0 ].Hostname != "" {
386+ lbAddress = lbService .Status .LoadBalancer .Ingress [0 ].Hostname
387+ } else {
388+ lbAddress = lbService .Status .LoadBalancer .Ingress [0 ].IP
389+ }
390+ if lbAddress == "" {
391+ e2e .Logf ("No load balancer address for service %q, retrying" , gwServiceName )
392+ return false , nil
393+ }
394+ e2e .Logf ("Got load balancer address for service %q: %v" , gwServiceName , lbAddress )
395+
396+ gw , err := oc .AdminGatewayApiClient ().GatewayV1 ().Gateways ("openshift-ingress" ).Get (context , gwName , metav1.GetOptions {})
397+ if err != nil {
398+ e2e .Logf ("Failed to get gateway %q, retrying..." , gwName )
399+ return false , nil
400+ }
401+ for _ , gwAddr := range gw .Status .Addresses {
402+ if gwAddr .Value == lbAddress {
403+ return true , nil
404+ }
405+ }
406+
407+ e2e .Logf ("Gateway %q does not have service load balancer address, retrying..." , gwName )
408+ return false , nil
409+ })
410+ o .Expect (err ).NotTo (o .HaveOccurred (), "Timed out waiting for gateway %q to get load balancer address of service %q" , gwName , gwServiceName )
411+ }
412+
413+ // assertDNSRecordStatus polls until the DNSRecord's status in the default operand namespace is True.
414+ func assertDNSRecordStatus (oc * exutil.CLI , gatewayName string ) {
415+ // find the DNS Record and confirm its zone status is True
416+ err := wait .PollUntilContextTimeout (context .Background (), 2 * time .Second , 10 * time .Minute , false , func (context context.Context ) (bool , error ) {
417+ gatewayDNSRecord := & operatoringressv1.DNSRecord {}
418+ gatewayDNSRecords , err := oc .AdminIngressClient ().IngressV1 ().DNSRecords ("openshift-ingress" ).List (context , metav1.ListOptions {})
419+ if err != nil {
420+ e2e .Logf ("Failed to list DNS records for gateway %q: %v, retrying..." , gatewayName , err )
421+ return false , nil
422+ }
423+
424+ // get the desired DNS records of the given gateway
425+ for _ , record := range gatewayDNSRecords .Items {
426+ if record .Labels ["gateway.networking.k8s.io/gateway-name" ] == gatewayName {
427+ gatewayDNSRecord = & record
428+ break
429+ }
430+ }
431+
432+ // checking the gateway DNS record status
433+ for _ , zone := range gatewayDNSRecord .Status .Zones {
434+ for _ , condition := range zone .Conditions {
435+ if condition .Type == "Published" && condition .Status == "True" {
436+ return true , nil
437+ }
438+ }
439+ }
440+ e2e .Logf ("DNS record %q is not ready, retrying..." , gatewayDNSRecord .Name )
441+ return false , nil
442+ })
443+ o .Expect (err ).NotTo (o .HaveOccurred (), "Timed out waiting for gateway %q DNSRecord to become ready" , gatewayName )
444+ }
445+
380446// createHttpRoute checks if the HTTPRoute can be created.
381447// If it can't an error is returned.
382448func createHttpRoute (oc * exutil.CLI , gwName , routeName , hostname , backendRefname string ) (* gatewayapiv1.HTTPRoute , error ) {
@@ -546,6 +612,56 @@ func assertHttpRouteSuccessful(oc *exutil.CLI, gwName, name string) (*gatewayapi
546612 return checkHttpRoute , nil
547613}
548614
615+ // assertHttpRouteConnection checks if the http route of the given name replies successfully,
616+ // and returns an error if not
617+ func assertHttpRouteConnection (hostname string ) {
618+ // Create the http client to check the response status code.
619+ client := & http.Client {
620+ Timeout : 10 * time .Second ,
621+ Transport : & http.Transport {
622+ TLSClientConfig : & tls.Config {InsecureSkipVerify : true },
623+ },
624+ }
625+
626+ err := wait .PollUntilContextTimeout (context .Background (), 20 * time .Second , dnsResolutionTimeout , false , func (context context.Context ) (bool , error ) {
627+ _ , err := net .LookupHost (hostname )
628+ if err != nil {
629+ e2e .Logf ("[%v] Failed to resolve HTTP route's hostname %q: %v, retrying..." , time .Now (), hostname , err )
630+ return false , nil
631+ }
632+ return true , nil
633+ })
634+ o .Expect (err ).NotTo (o .HaveOccurred (), "Timed out waiting for HTTP route's hostname %q to be resolved: %v" , hostname , err )
635+
636+ // Wait for http route to respond, and when it does, check for the status code.
637+ err = wait .PollUntilContextTimeout (context .Background (), 5 * time .Second , 5 * time .Minute , false , func (context context.Context ) (bool , error ) {
638+ statusCode , err := getHttpResponse (client , hostname )
639+ if err != nil {
640+ e2e .Logf ("HTTP GET request to %q failed: %v, retrying..." , hostname , err )
641+ return false , nil
642+ }
643+ if statusCode != http .StatusOK {
644+ e2e .Logf ("Unexpected status code for HTTP GET request to %q: %v, retrying..." , hostname , statusCode )
645+ return false , nil // retry on 503 as pod/service may not be ready
646+ }
647+ return true , nil
648+ })
649+ o .Expect (err ).NotTo (o .HaveOccurred (), "Timed out waiting for successful HTTP GET response from %q: %v" , hostname , err )
650+ }
651+
652+ func getHttpResponse (client * http.Client , hostname string ) (int , error ) {
653+ // Send the HTTP request.
654+ response , err := client .Get ("http://" + hostname )
655+ if err != nil {
656+ return 0 , err
657+ }
658+
659+ // Close response body.
660+ defer response .Body .Close ()
661+
662+ return response .StatusCode , nil
663+ }
664+
549665// Check for the existence of the okd-scos string in the version name to determine if it is OKD
550666func isOKD (oc * exutil.CLI ) (bool , error ) {
551667 current , err := exutil .GetCurrentVersion (context .TODO (), oc .AdminConfig ())
0 commit comments