@@ -37,33 +37,24 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:AWSDualStackInstall][Featu
3737 ctx := context .Background ()
3838
3939 g .By ("Checking that the Infrastructure CR has a DualStack IPFamily" )
40- infra , err := oc .AdminConfigClient ().ConfigV1 ().Infrastructures ().Get (ctx , "cluster" , metav1.GetOptions {})
41- o .Expect (err ).NotTo (o .HaveOccurred (), "failed to get infrastructure CR" )
42-
43- if infra .Status .PlatformStatus == nil || infra .Status .PlatformStatus .Type != configv1 .AWSPlatformType {
44- g .Skip ("Test requires AWS platform" )
45- }
46- if infra .Status .PlatformStatus .AWS == nil {
47- g .Skip ("AWS platform status is not set" )
48- }
49- ipFamily := infra .Status .PlatformStatus .AWS .IPFamily
50- if ipFamily != configv1 .DualStackIPv4Primary && ipFamily != configv1 .DualStackIPv6Primary {
51- g .Skip (fmt .Sprintf ("Test requires DualStack IPFamily, got %q" , ipFamily ))
52- }
40+ requireAWSDualStack (ctx , oc )
5341
5442 g .By ("Getting the default ingress domain" )
5543 defaultDomain , err := getDefaultIngressClusterDomainName (oc , time .Minute )
5644 o .Expect (err ).NotTo (o .HaveOccurred (), "failed to find default domain name" )
5745
5846 ns := oc .KubeFramework ().Namespace .Name
5947 baseDomain := strings .TrimPrefix (defaultDomain , "apps." )
60- shardFQDN := "hosts ." + baseDomain
48+ shardFQDN := "nlb ." + baseDomain
6149
6250 // Deploy the shard first so DNS and LB can provision while we set up the backend.
51+ // Use 2 replicas so the NLB allocates addresses in multiple AZs, which
52+ // mitigates instability of IPv6 hairpin traffic (cluster to cluster via LB).
6353 g .By ("Deploying a new router shard with NLB" )
6454 shardIngressCtrl , err := shard .DeployNewRouterShard (oc , 10 * time .Minute , shard.Config {
65- Domain : shardFQDN ,
66- Type : oc .Namespace (),
55+ Domain : shardFQDN ,
56+ Type : oc .Namespace (),
57+ Replicas : 2 ,
6758 LoadBalancer : & operatorv1.LoadBalancerStrategy {
6859 Scope : operatorv1 .ExternalLoadBalancer ,
6960 ProviderParameters : & operatorv1.ProviderLoadBalancerParameters {
@@ -87,148 +78,229 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:AWSDualStackInstall][Featu
8778 err = oc .AsAdmin ().Run ("label" ).Args ("namespace" , oc .Namespace (), "type=" + oc .Namespace ()).Execute ()
8879 o .Expect (err ).NotTo (o .HaveOccurred ())
8980
90- g .By ("Creating backend service" )
91- service := & corev1.Service {
92- ObjectMeta : metav1.ObjectMeta {
93- Name : "dualstack-backend" ,
94- Labels : map [string ]string {
95- "app" : "dualstack-backend" ,
96- },
97- },
98- Spec : corev1.ServiceSpec {
99- Selector : map [string ]string {
100- "app" : "dualstack-backend" ,
101- },
102- IPFamilyPolicy : func () * corev1.IPFamilyPolicy {
103- p := corev1 .IPFamilyPolicyPreferDualStack
104- return & p
105- }(),
106- Ports : []corev1.ServicePort {
107- {
108- Name : "http" ,
109- Port : 8080 ,
110- Protocol : corev1 .ProtocolTCP ,
111- TargetPort : intstr .FromInt (8080 ),
81+ g .By ("Creating backend service and pod" )
82+ createBackendServiceAndPod (ctx , oc , ns , "dualstack-backend" )
83+
84+ g .By ("Creating an edge-terminated route" )
85+ routeHost := "dualstack-test." + shardFQDN
86+ createEdgeRoute (ctx , oc , ns , "dualstack-route" , routeHost , "dualstack-backend" )
87+
88+ g .By ("Waiting for the route to be admitted" )
89+ waitForRouteAdmitted (ctx , oc , ns , "dualstack-route" , routeHost )
90+
91+ g .By ("Creating exec pod for curl tests" )
92+ execPod := exutil .CreateExecPodOrFail (oc .AdminKubeClient (), ns , "execpod" )
93+ defer func () {
94+ oc .AdminKubeClient ().CoreV1 ().Pods (ns ).Delete (ctx , execPod .Name , * metav1 .NewDeleteOptions (1 ))
95+ }()
96+
97+ g .By ("Verifying route is reachable over IPv4" )
98+ err = waitForRouteResponse (ns , execPod .Name , routeHost , "-4" , 10 * time .Minute )
99+ o .Expect (err ).NotTo (o .HaveOccurred (), "route not reachable over IPv4" )
100+
101+ g .By ("Verifying route is reachable over IPv6" )
102+ err = waitForRouteResponse (ns , execPod .Name , routeHost , "-6" , 10 * time .Minute )
103+ o .Expect (err ).NotTo (o .HaveOccurred (), "route not reachable over IPv6" )
104+ })
105+
106+ g .It ("should be reachable via IPv4 through a Classic LB ingress controller on a dual-stack cluster" , func () {
107+ ctx := context .Background ()
108+
109+ g .By ("Checking that the Infrastructure CR has a DualStack IPFamily" )
110+ requireAWSDualStack (ctx , oc )
111+
112+ g .By ("Getting the default ingress domain" )
113+ defaultDomain , err := getDefaultIngressClusterDomainName (oc , time .Minute )
114+ o .Expect (err ).NotTo (o .HaveOccurred (), "failed to find default domain name" )
115+
116+ ns := oc .KubeFramework ().Namespace .Name
117+ baseDomain := strings .TrimPrefix (defaultDomain , "apps." )
118+ shardFQDN := "clb." + baseDomain
119+
120+ // Deploy the shard first so DNS and LB can provision while we set up the backend.
121+ g .By ("Deploying a new router shard with Classic LB" )
122+ shardIngressCtrl , err := shard .DeployNewRouterShard (oc , 10 * time .Minute , shard.Config {
123+ Domain : shardFQDN ,
124+ Type : oc .Namespace (),
125+ LoadBalancer : & operatorv1.LoadBalancerStrategy {
126+ Scope : operatorv1 .ExternalLoadBalancer ,
127+ ProviderParameters : & operatorv1.ProviderLoadBalancerParameters {
128+ Type : operatorv1 .AWSLoadBalancerProvider ,
129+ AWS : & operatorv1.AWSLoadBalancerParameters {
130+ Type : operatorv1 .AWSClassicLoadBalancer ,
112131 },
113132 },
114133 },
115- }
116- _ , err = oc .AdminKubeClient ().CoreV1 ().Services (ns ).Create (ctx , service , metav1.CreateOptions {})
134+ })
135+ defer func () {
136+ if shardIngressCtrl != nil {
137+ if err := oc .AdminOperatorClient ().OperatorV1 ().IngressControllers (shardIngressCtrl .Namespace ).Delete (ctx , shardIngressCtrl .Name , metav1.DeleteOptions {}); err != nil {
138+ e2e .Logf ("deleting ingress controller failed: %v\n " , err )
139+ }
140+ }
141+ }()
142+ o .Expect (err ).NotTo (o .HaveOccurred (), "new router shard did not rollout" )
143+
144+ g .By ("Labelling the namespace for the shard" )
145+ err = oc .AsAdmin ().Run ("label" ).Args ("namespace" , oc .Namespace (), "type=" + oc .Namespace ()).Execute ()
117146 o .Expect (err ).NotTo (o .HaveOccurred ())
118147
119- g .By ("Creating backend pod" )
120- backendPod := & corev1.Pod {
121- ObjectMeta : metav1.ObjectMeta {
122- Name : "dualstack-backend" ,
123- Labels : map [string ]string {
124- "app" : "dualstack-backend" ,
148+ g .By ("Creating backend service and pod" )
149+ createBackendServiceAndPod (ctx , oc , ns , "classic-backend" )
150+
151+ g .By ("Creating an edge-terminated route" )
152+ routeHost := "classic-test." + shardFQDN
153+ createEdgeRoute (ctx , oc , ns , "classic-route" , routeHost , "classic-backend" )
154+
155+ g .By ("Waiting for the route to be admitted" )
156+ waitForRouteAdmitted (ctx , oc , ns , "classic-route" , routeHost )
157+
158+ g .By ("Creating exec pod for curl tests" )
159+ execPod := exutil .CreateExecPodOrFail (oc .AdminKubeClient (), ns , "execpod" )
160+ defer func () {
161+ oc .AdminKubeClient ().CoreV1 ().Pods (ns ).Delete (ctx , execPod .Name , * metav1 .NewDeleteOptions (1 ))
162+ }()
163+
164+ g .By ("Verifying route is reachable over IPv4" )
165+ err = waitForRouteResponse (ns , execPod .Name , routeHost , "-4" , 10 * time .Minute )
166+ o .Expect (err ).NotTo (o .HaveOccurred (), "route not reachable over IPv4" )
167+ })
168+ })
169+
170+ func requireAWSDualStack (ctx context.Context , oc * exutil.CLI ) {
171+ infra , err := oc .AdminConfigClient ().ConfigV1 ().Infrastructures ().Get (ctx , "cluster" , metav1.GetOptions {})
172+ o .Expect (err ).NotTo (o .HaveOccurred (), "failed to get infrastructure CR" )
173+
174+ if infra .Status .PlatformStatus == nil || infra .Status .PlatformStatus .Type != configv1 .AWSPlatformType {
175+ g .Skip ("Test requires AWS platform" )
176+ }
177+ if infra .Status .PlatformStatus .AWS == nil {
178+ g .Skip ("AWS platform status is not set" )
179+ }
180+ ipFamily := infra .Status .PlatformStatus .AWS .IPFamily
181+ if ipFamily != configv1 .DualStackIPv4Primary && ipFamily != configv1 .DualStackIPv6Primary {
182+ g .Skip (fmt .Sprintf ("Test requires DualStack IPFamily, got %q" , ipFamily ))
183+ }
184+ }
185+
186+ func createBackendServiceAndPod (ctx context.Context , oc * exutil.CLI , ns , name string ) {
187+ service := & corev1.Service {
188+ ObjectMeta : metav1.ObjectMeta {
189+ Name : name ,
190+ Labels : map [string ]string {"app" : name },
191+ },
192+ Spec : corev1.ServiceSpec {
193+ Selector : map [string ]string {"app" : name },
194+ IPFamilyPolicy : func () * corev1.IPFamilyPolicy {
195+ p := corev1 .IPFamilyPolicyPreferDualStack
196+ return & p
197+ }(),
198+ Ports : []corev1.ServicePort {
199+ {
200+ Name : "http" ,
201+ Port : 8080 ,
202+ Protocol : corev1 .ProtocolTCP ,
203+ TargetPort : intstr .FromInt (8080 ),
125204 },
126205 },
127- Spec : corev1.PodSpec {
128- TerminationGracePeriodSeconds : utilpointer .Int64 (1 ),
129- Containers : []corev1.Container {
130- {
131- Name : "server" ,
132- Image : image .ShellImage (),
133- ImagePullPolicy : corev1 .PullIfNotPresent ,
134- Command : []string {"/bin/bash" , "-c" , `while true; do
206+ },
207+ }
208+ _ , err := oc .AdminKubeClient ().CoreV1 ().Services (ns ).Create (ctx , service , metav1.CreateOptions {})
209+ o .Expect (err ).NotTo (o .HaveOccurred ())
210+
211+ pod := & corev1.Pod {
212+ ObjectMeta : metav1.ObjectMeta {
213+ Name : name ,
214+ Labels : map [string ]string {"app" : name },
215+ },
216+ Spec : corev1.PodSpec {
217+ TerminationGracePeriodSeconds : utilpointer .Int64 (1 ),
218+ Containers : []corev1.Container {
219+ {
220+ Name : "server" ,
221+ Image : image .ShellImage (),
222+ ImagePullPolicy : corev1 .PullIfNotPresent ,
223+ Command : []string {"/bin/bash" , "-c" , `while true; do
135224printf "HTTP/1.1 200 OK\r\nContent-Length: 2\r\nContent-Type: text/plain\r\n\r\nOK" | ncat -l 8080 --send-only || true
136225done` },
137- Ports : []corev1.ContainerPort {
138- {
139- ContainerPort : 8080 ,
140- Name : "http" ,
141- Protocol : corev1 .ProtocolTCP ,
142- },
226+ Ports : []corev1.ContainerPort {
227+ {
228+ ContainerPort : 8080 ,
229+ Name : "http" ,
230+ Protocol : corev1 .ProtocolTCP ,
143231 },
144232 },
145233 },
146234 },
147- }
148- _ , err = oc .AdminKubeClient ().CoreV1 ().Pods (ns ).Create (ctx , backendPod , metav1.CreateOptions {})
149- o .Expect (err ).NotTo (o .HaveOccurred ())
235+ },
236+ }
237+ _ , err = oc .AdminKubeClient ().CoreV1 ().Pods (ns ).Create (ctx , pod , metav1.CreateOptions {})
238+ o .Expect (err ).NotTo (o .HaveOccurred ())
150239
151- g . By ( "Waiting for backend pod to be running" )
152- e2e . ExpectNoError ( e2epod . WaitForPodRunningInNamespaceSlow ( ctx , oc . KubeClient (), "dualstack-backend" , ns ), "backend pod not running" )
240+ e2e . ExpectNoError ( e2epod . WaitForPodRunningInNamespaceSlow ( ctx , oc . KubeClient (), name , ns ), " backend pod not running" )
241+ }
153242
154- g .By ("Creating an edge-terminated route" )
155- routeType := oc .Namespace ()
156- route := routev1.Route {
157- ObjectMeta : metav1.ObjectMeta {
158- Name : "dualstack-route" ,
159- Labels : map [string ]string {
160- "type" : routeType ,
161- },
243+ func createEdgeRoute (ctx context.Context , oc * exutil.CLI , ns , name , host , serviceName string ) {
244+ route := routev1.Route {
245+ ObjectMeta : metav1.ObjectMeta {
246+ Name : name ,
247+ Labels : map [string ]string {
248+ "type" : oc .Namespace (),
162249 },
163- Spec : routev1.RouteSpec {
164- Host : "dualstack-test." + shardFQDN ,
165- Port : & routev1.RoutePort {
166- TargetPort : intstr .FromInt (8080 ),
167- },
168- TLS : & routev1.TLSConfig {
169- Termination : routev1 .TLSTerminationEdge ,
170- InsecureEdgeTerminationPolicy : routev1 .InsecureEdgeTerminationPolicyRedirect ,
171- },
172- To : routev1.RouteTargetReference {
173- Kind : "Service" ,
174- Name : "dualstack-backend" ,
175- Weight : utilpointer .Int32 (100 ),
176- },
177- WildcardPolicy : routev1 .WildcardPolicyNone ,
250+ },
251+ Spec : routev1.RouteSpec {
252+ Host : host ,
253+ Port : & routev1.RoutePort {
254+ TargetPort : intstr .FromInt (8080 ),
178255 },
179- }
180- _ , err = oc .RouteClient ().RouteV1 ().Routes (ns ).Create (ctx , & route , metav1.CreateOptions {})
181- o .Expect (err ).NotTo (o .HaveOccurred ())
256+ TLS : & routev1.TLSConfig {
257+ Termination : routev1 .TLSTerminationEdge ,
258+ InsecureEdgeTerminationPolicy : routev1 .InsecureEdgeTerminationPolicyRedirect ,
259+ },
260+ To : routev1.RouteTargetReference {
261+ Kind : "Service" ,
262+ Name : serviceName ,
263+ Weight : utilpointer .Int32 (100 ),
264+ },
265+ WildcardPolicy : routev1 .WildcardPolicyNone ,
266+ },
267+ }
268+ _ , err := oc .RouteClient ().RouteV1 ().Routes (ns ).Create (ctx , & route , metav1.CreateOptions {})
269+ o .Expect (err ).NotTo (o .HaveOccurred ())
270+ }
182271
183- g .By ("Waiting for the route to be admitted" )
184- routeHost := "dualstack-test." + shardFQDN
185- err = wait .PollImmediate (5 * time .Second , 5 * time .Minute , func () (bool , error ) {
186- r , err := oc .RouteClient ().RouteV1 ().Routes (ns ).Get (ctx , "dualstack-route" , metav1.GetOptions {})
187- if err != nil {
188- e2e .Logf ("failed to get route: %v, retrying..." , err )
189- return false , nil
190- }
191- for _ , ingress := range r .Status .Ingress {
192- if ingress .Host == routeHost {
193- for _ , condition := range ingress .Conditions {
194- if condition .Type == routev1 .RouteAdmitted && condition .Status == corev1 .ConditionTrue {
195- return true , nil
196- }
272+ func waitForRouteAdmitted (ctx context.Context , oc * exutil.CLI , ns , name , host string ) {
273+ err := wait .PollImmediate (5 * time .Second , 5 * time .Minute , func () (bool , error ) {
274+ r , err := oc .RouteClient ().RouteV1 ().Routes (ns ).Get (ctx , name , metav1.GetOptions {})
275+ if err != nil {
276+ e2e .Logf ("failed to get route: %v, retrying..." , err )
277+ return false , nil
278+ }
279+ for _ , ingress := range r .Status .Ingress {
280+ if ingress .Host == host {
281+ for _ , condition := range ingress .Conditions {
282+ if condition .Type == routev1 .RouteAdmitted && condition .Status == corev1 .ConditionTrue {
283+ return true , nil
197284 }
198285 }
199286 }
200- return false , nil
201- })
202- o .Expect (err ).NotTo (o .HaveOccurred (), "route was not admitted" )
203-
204- g .By ("Creating exec pod for curl tests" )
205- execPod := exutil .CreateExecPodOrFail (oc .AdminKubeClient (), ns , "execpod" )
206- defer func () {
207- oc .AdminKubeClient ().CoreV1 ().Pods (ns ).Delete (ctx , execPod .Name , * metav1 .NewDeleteOptions (1 ))
208- }()
209-
210- g .By ("Verifying route is reachable over IPv4" )
211- err = waitForDualStackRouteResponse (ns , execPod .Name , routeHost , "-4" , 10 * time .Minute )
212- o .Expect (err ).NotTo (o .HaveOccurred (), "route not reachable over IPv4" )
213-
214- g .By ("Verifying route is reachable over IPv6" )
215- err = waitForDualStackRouteResponse (ns , execPod .Name , routeHost , "-6" , 10 * time .Minute )
216- o .Expect (err ).NotTo (o .HaveOccurred (), "route not reachable over IPv6" )
287+ }
288+ return false , nil
217289 })
218- })
290+ o .Expect (err ).NotTo (o .HaveOccurred (), "route was not admitted" )
291+ }
219292
220- func waitForDualStackRouteResponse (ns , execPodName , host , ipFlag string , timeout time.Duration ) error {
293+ func waitForRouteResponse (ns , execPodName , host , ipFlag string , timeout time.Duration ) error {
221294 curlCmd := fmt .Sprintf ("curl %s -k -v -m 10 --connect-timeout 5 -o /dev/null https://%s 2>&1" , ipFlag , host )
222295 var lastOutput string
223296 err := wait .PollImmediate (5 * time .Second , timeout , func () (bool , error ) {
224297 output , err := e2eoutput .RunHostCmd (ns , execPodName , curlCmd )
225298 lastOutput = output
226- e2e .Logf ("curl %s %s:\n %s" , ipFlag , host , output )
227299 if err != nil {
228- e2e .Logf ("curl %s error: %v" , ipFlag , err )
229300 return false , nil
230301 }
231302 if strings .Contains (output , "< HTTP/1.1 200" ) || strings .Contains (output , "< HTTP/2 200" ) {
303+ e2e .Logf ("curl %s %s:\n %s" , ipFlag , host , output )
232304 return true , nil
233305 }
234306 return false , nil
0 commit comments