@@ -13,14 +13,17 @@ import (
1313 mg "github.com/openshift/origin/test/extended/machine_config"
1414 exutil "github.com/openshift/origin/test/extended/util"
1515 "golang.org/x/sync/errgroup"
16+ appsv1 "k8s.io/api/apps/v1"
1617 corev1 "k8s.io/api/core/v1"
1718 apierrors "k8s.io/apimachinery/pkg/api/errors"
1819 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20+ "k8s.io/apimachinery/pkg/labels"
1921 "k8s.io/apimachinery/pkg/runtime/schema"
2022 "k8s.io/apimachinery/pkg/util/wait"
2123 "k8s.io/kubernetes/test/e2e/framework"
2224 e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
2325 e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
26+ e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
2427 "k8s.io/kubernetes/test/e2e/framework/statefulset"
2528 admissionapi "k8s.io/pod-security-admission/api"
2629
@@ -34,7 +37,9 @@ const (
3437 // tcpdumpGeneveFilter can be used to filter out Geneve encapsulated packets destined to target node.
3538 tcpdumpGeneveFilter = "udp port 6081 and src %s and dst %s"
3639 // tcpdumpICMPFilter can be used to filter out icmp packets destined to target node.
37- tcpdumpICMPFilter = "icmp and src %s and dst %s"
40+ tcpdumpICMPFilter = "icmp and src %s and dst %s"
41+ // tcpdumpNATTFilter can be used to filter out NAT-T encapsulated packets destined to target node.
42+ tcpdumpNATTFilter = "udp port 4500 and src %s and dst %s"
3843 masterIPsecMachineConfigName = "80-ipsec-master-extensions"
3944 workerIPSecMachineConfigName = "80-ipsec-worker-extensions"
4045 ipsecRolloutWaitDuration = 40 * time .Minute
@@ -91,31 +96,56 @@ var (
9196 rightServerCertName = "right_server"
9297 // Expiration date for certificates.
9398 certExpirationDate = time .Date (2034 , time .April , 10 , 0 , 0 , 0 , 0 , time .UTC )
99+ // http endpoint port for the pod traffic test
100+ port uint16 = 8080
94101)
95102
96103type trafficType string
97104
105+ type ipsecConfig struct {
106+ mode v1.IPsecMode
107+ encap v1.Encapsulation
108+ }
109+
98110const (
99111 esp trafficType = "esp"
100112 geneve trafficType = "geneve"
101113 icmp trafficType = "icmp"
114+ natt trafficType = "natt"
102115)
103116
104- func getIPsecMode (oc * exutil.CLI ) (v1. IPsecMode , error ) {
117+ func getIPsecConfig (oc * exutil.CLI ) (* ipsecConfig , error ) {
105118 network , err := oc .AdminOperatorClient ().OperatorV1 ().Networks ().Get (context .Background (), "cluster" , metav1.GetOptions {})
106119 if err != nil {
107- return v1 . IPsecModeDisabled , err
120+ return nil , err
108121 }
109122 conf := network .Spec .DefaultNetwork .OVNKubernetesConfig
123+ mode := getIPsecMode (conf )
124+ encap := getIPsecEncap (conf )
125+ return & ipsecConfig {mode : mode ,
126+ encap : encap }, nil
127+ }
128+
129+ func getIPsecMode (ovnkCfg * v1.OVNKubernetesConfig ) v1.IPsecMode {
110130 mode := v1 .IPsecModeDisabled
111- if conf .IPsecConfig != nil {
112- if conf .IPsecConfig .Mode != "" {
113- mode = conf .IPsecConfig .Mode
131+ if ovnkCfg .IPsecConfig != nil {
132+ if ovnkCfg .IPsecConfig .Mode != "" {
133+ mode = ovnkCfg .IPsecConfig .Mode
114134 } else {
115135 mode = v1 .IPsecModeFull // Backward compatibility with existing configs
116136 }
117137 }
118- return mode , nil
138+ return mode
139+ }
140+
141+ func getIPsecEncap (ovnkCfg * v1.OVNKubernetesConfig ) v1.Encapsulation {
142+ encapType := v1 .Encapsulation (v1 .EncapsulationAuto )
143+ if ovnkCfg .IPsecConfig != nil &&
144+ ovnkCfg .IPsecConfig .Mode == v1 .IPsecModeFull &&
145+ ovnkCfg .IPsecConfig .Full != nil {
146+ encapType = ovnkCfg .IPsecConfig .Full .Encapsulation
147+ }
148+ return encapType
119149}
120150
121151// ensureIPsecFullEnabled this function ensure IPsec is enabled by making sure ovn-ipsec-host daemonset
@@ -237,7 +267,7 @@ var _ = g.Describe("[sig-network][Feature:IPsec]", g.Ordered, func() {
237267 nodeIP string
238268 }
239269 type testConfig struct {
240- ipsecMode v1. IPsecMode
270+ ipsecCfg * ipsecConfig
241271 srcNodeConfig * testNodeConfig
242272 dstNodeConfig * testNodeConfig
243273 }
@@ -261,6 +291,9 @@ var _ = g.Describe("[sig-network][Feature:IPsec]", g.Ordered, func() {
261291 case icmp :
262292 srcNodeTrafficFilter = fmt .Sprintf (tcpdumpICMPFilter , src .tcpdumpPod .Status .PodIP , dst .tcpdumpPod .Status .PodIP )
263293 dstNodeTrafficFilter = fmt .Sprintf (tcpdumpICMPFilter , dst .tcpdumpPod .Status .PodIP , src .tcpdumpPod .Status .PodIP )
294+ case natt :
295+ srcNodeTrafficFilter = fmt .Sprintf (tcpdumpNATTFilter , src .tcpdumpPod .Status .PodIP , dst .tcpdumpPod .Status .PodIP )
296+ dstNodeTrafficFilter = fmt .Sprintf (tcpdumpNATTFilter , dst .tcpdumpPod .Status .PodIP , src .tcpdumpPod .Status .PodIP )
264297 }
265298 checkSrcNodeTraffic := func (src * testNodeConfig ) error {
266299 _ , err := oc .AsAdmin ().Run ("exec" ).Args (src .tcpdumpPod .Name , "-n" , src .tcpdumpPod .Namespace , "--" ,
@@ -370,6 +403,8 @@ var _ = g.Describe("[sig-network][Feature:IPsec]", g.Ordered, func() {
370403 o .Expect (err ).To (o .HaveOccurred ())
371404 err = pingAndCheckNodeTraffic (config .srcNodeConfig , config .dstNodeConfig , icmp )
372405 o .Expect (err ).To (o .HaveOccurred ())
406+ err = pingAndCheckNodeTraffic (config .srcNodeConfig , config .dstNodeConfig , natt )
407+ o .Expect (err ).To (o .HaveOccurred ())
373408 err = nil
374409 }
375410
@@ -388,13 +423,37 @@ var _ = g.Describe("[sig-network][Feature:IPsec]", g.Ordered, func() {
388423 o .Expect (err ).NotTo (o .HaveOccurred ())
389424 err = pingAndCheckNodeTraffic (config .srcNodeConfig , config .dstNodeConfig , geneve )
390425 o .Expect (err ).To (o .HaveOccurred ())
426+ err = pingAndCheckNodeTraffic (config .srcNodeConfig , config .dstNodeConfig , natt )
427+ o .Expect (err ).To (o .HaveOccurred ())
391428 err = nil
392429 }
393430
394- checkPodTraffic := func (mode v1. IPsecMode ) {
431+ checkForNATTOnlyPodTraffic := func (config * testConfig ) {
395432 g .GinkgoHelper ()
396- if mode == v1 .IPsecModeFull {
433+ err := setupTestPods (config , false )
434+ o .Expect (err ).NotTo (o .HaveOccurred ())
435+ defer func () {
436+ // Don't cleanup test pods in error scenario.
437+ if err != nil && ! framework .TestContext .DeleteNamespaceOnFailure {
438+ return
439+ }
440+ cleanupTestPods (config )
441+ }()
442+ err = pingAndCheckNodeTraffic (config .srcNodeConfig , config .dstNodeConfig , natt )
443+ o .Expect (err ).NotTo (o .HaveOccurred ())
444+ err = pingAndCheckNodeTraffic (config .srcNodeConfig , config .dstNodeConfig , geneve )
445+ o .Expect (err ).To (o .HaveOccurred ())
446+ err = pingAndCheckNodeTraffic (config .srcNodeConfig , config .dstNodeConfig , esp )
447+ o .Expect (err ).To (o .HaveOccurred ())
448+ err = nil
449+ }
450+
451+ checkPodTraffic := func (ipsecCfg * ipsecConfig ) {
452+ g .GinkgoHelper ()
453+ if ipsecCfg .mode == v1 .IPsecModeFull && ipsecCfg .encap == v1 .EncapsulationAuto {
397454 checkForESPOnlyPodTraffic (config )
455+ } else if ipsecCfg .mode == v1 .IPsecModeFull && ipsecCfg .encap == v1 .EncapsulationAlways {
456+ checkForNATTOnlyPodTraffic (config )
398457 } else {
399458 checkForGeneveOnlyPodTraffic (config )
400459 }
@@ -427,12 +486,12 @@ var _ = g.Describe("[sig-network][Feature:IPsec]", g.Ordered, func() {
427486 g .BeforeAll (func () {
428487 // Set up the config object with existing IPsecConfig, setup testing config on
429488 // the selected nodes.
430- ipsecMode , err := getIPsecMode (oc )
489+ ipsecConfig , err := getIPsecConfig (oc )
431490 o .Expect (err ).NotTo (o .HaveOccurred ())
432- o .Expect (ipsecMode ).NotTo (o .Equal (v1 .IPsecModeDisabled ))
491+ o .Expect (ipsecConfig . mode ).NotTo (o .Equal (v1 .IPsecModeDisabled ))
433492
434493 srcNode , dstNode := & testNodeConfig {}, & testNodeConfig {}
435- config = & testConfig {ipsecMode : ipsecMode , srcNodeConfig : srcNode ,
494+ config = & testConfig {ipsecCfg : ipsecConfig , srcNodeConfig : srcNode ,
436495 dstNodeConfig : dstNode }
437496
438497 // Deploy nmstate handler which is used for rolling out IPsec config
@@ -472,7 +531,7 @@ var _ = g.Describe("[sig-network][Feature:IPsec]", g.Ordered, func() {
472531 return areMachineConfigPoolsReadyWithMachineConfig (pools , nsCertMachineConfigName )
473532 }, ipsecRolloutWaitDuration , ipsecRolloutWaitInterval ).Should (o .BeTrue ())
474533 // Ensure IPsec mode is still correctly configured.
475- waitForIPsecConfigToComplete (oc , config . ipsecMode )
534+ waitForIPsecConfigToComplete (oc , ipsecConfig . mode )
476535 })
477536
478537 g .BeforeEach (func () {
@@ -503,10 +562,10 @@ var _ = g.Describe("[sig-network][Feature:IPsec]", g.Ordered, func() {
503562 })
504563
505564 g .AfterEach (func () {
506- ipsecMode , err := getIPsecMode (oc )
565+ ipsecConfig , err := getIPsecConfig (oc )
507566 o .Expect (err ).NotTo (o .HaveOccurred ())
508567 if g .CurrentSpecReport ().Failed () {
509- if ipsecMode == v1 .IPsecModeFull {
568+ if ipsecConfig . mode == v1 .IPsecModeFull {
510569 var ipsecPods []string
511570 srcIPsecPod , err := findIPsecPodonNode (oc , config .srcNodeConfig .nodeName )
512571 o .Expect (err ).NotTo (o .HaveOccurred ())
@@ -554,15 +613,16 @@ var _ = g.Describe("[sig-network][Feature:IPsec]", g.Ordered, func() {
554613
555614 g .It ("check traffic with IPsec [apigroup:config.openshift.io] [Suite:openshift/network/ipsec]" , func () {
556615 o .Expect (config ).NotTo (o .BeNil ())
616+ o .Expect (config .ipsecCfg ).NotTo (o .BeNil ())
557617
558618 g .By ("validate traffic before changing IPsec configuration" )
559- checkPodTraffic (config .ipsecMode )
619+ checkPodTraffic (config .ipsecCfg )
560620 // N/S ipsec config is not in effect yet, so node traffic behaves as it were disabled
561621 checkNodeTraffic (v1 .IPsecModeDisabled )
562622
563623 // TODO: remove this block when https://issues.redhat.com/browse/RHEL-67307 is fixed.
564- if config .ipsecMode == v1 .IPsecModeFull {
565- g .By (fmt .Sprintf ("skip testing IPsec NS configuration with %s mode due to nmstate bug RHEL-67307" , config .ipsecMode ))
624+ if config .ipsecCfg . mode == v1 .IPsecModeFull {
625+ g .By (fmt .Sprintf ("skip testing IPsec NS configuration with %s mode due to nmstate bug RHEL-67307" , config .ipsecCfg . mode ))
566626 return
567627 }
568628
@@ -590,12 +650,137 @@ var _ = g.Describe("[sig-network][Feature:IPsec]", g.Ordered, func() {
590650
591651 g .By ("validate IPsec traffic between nodes" )
592652 // Pod traffic will be encrypted as a result N/S encryption being enabled between this two nodes
593- checkPodTraffic (v1 .IPsecModeFull )
653+ checkPodTraffic (& ipsecConfig {mode : v1 .IPsecModeFull ,
654+ encap : v1 .Encapsulation (v1 .EncapsulationAuto )})
594655 checkNodeTraffic (v1 .IPsecModeExternal )
595656 })
596657 })
597658})
598659
660+ var _ = g .Describe ("[sig-network][Feature:IPsec] IPsec resilience" , g .Ordered , func () {
661+ oc := exutil .NewCLIWithPodSecurityLevel ("ipsec" , admissionapi .LevelPrivileged )
662+ f := oc .KubeFramework ()
663+ var ipsecMode v1.IPsecMode
664+
665+ InOVNKubernetesContext (func () {
666+ g .BeforeAll (func () {
667+ var err error
668+ ipsecConfig , err := getIPsecConfig (oc )
669+ o .Expect (err ).NotTo (o .HaveOccurred ())
670+ ipsecMode = ipsecConfig .mode
671+ })
672+
673+ g .It ("check pod traffic is working across nodes [apigroup:config.openshift.io] [Suite:openshift/network/ipsec]" , func () {
674+ g .By ("creating test pods" )
675+ pods := createWebServerPods (oc , f .Namespace .Name )
676+ g .By ("checking crossing connectivity over the pods" )
677+ checkPodCrossConnectivity (pods )
678+ })
679+
680+ g .It ("check pod traffic is working across nodes after ipsec daemonset restart [apigroup:config.openshift.io] [Suite:openshift/network/ipsec]" , func () {
681+ // The IPsec daemonset manages IPsec connections between nodes for pod's east-west traffic.
682+ // The IPsec daemonset exists only in IPsec full mode, so skip this test for other IPsec modes.
683+ if ipsecMode != v1 .IPsecModeFull {
684+ e2eskipper .Skipf ("cluster is configured with IPsec %s mode, so skipping the test" , ipsecMode )
685+ }
686+ g .By ("creating test pods" )
687+ pods := createWebServerPods (oc , f .Namespace .Name )
688+ g .By ("checking crossing connectivity over the pods" )
689+ checkPodCrossConnectivity (pods )
690+ // Restart IPsec daemonset few times and check pod traffic is not impacted.
691+ for i := 1 ; i <= 5 ; i ++ {
692+ g .By (fmt .Sprintf ("attempt#%d restarting IPsec pods" , i ))
693+ restartIPsecDaemonSet (oc )
694+ g .By ("checking crossing connectivity over the pods" )
695+ checkPodCrossConnectivity (pods )
696+ }
697+ })
698+ })
699+ })
700+
701+ func restartIPsecDaemonSet (oc * exutil.CLI ) {
702+ g .GinkgoHelper ()
703+ ds , err := getDaemonSet (oc , ovnNamespace , ovnIPsecDsName )
704+ o .Expect (err ).NotTo (o .HaveOccurred ())
705+ o .Expect (ds ).NotTo (o .BeNil ())
706+ err = deleteDaemonSet (oc .AdminKubeClient (), ovnNamespace , ovnIPsecDsName )
707+ o .Expect (err ).NotTo (o .HaveOccurred ())
708+ // wait until CNO reconciles IPsec daemonset.
709+ err = ensureIPsecFullEnabled (oc )
710+ o .Expect (err ).NotTo (o .HaveOccurred ())
711+ }
712+
713+ func createWebServerPods (oc * exutil.CLI , namespace string ) []corev1.Pod {
714+ g .GinkgoHelper ()
715+ immediate := int64 (0 )
716+ ds := & appsv1.DaemonSet {
717+ ObjectMeta : metav1.ObjectMeta {
718+ Name : "ipsec-webserver" ,
719+ Namespace : namespace ,
720+ },
721+ Spec : appsv1.DaemonSetSpec {
722+ Selector : & metav1.LabelSelector {
723+ MatchLabels : map [string ]string {
724+ "apps" : "ipsec-webserver" ,
725+ },
726+ },
727+ Template : corev1.PodTemplateSpec {
728+ ObjectMeta : metav1.ObjectMeta {
729+ Labels : map [string ]string {
730+ "apps" : "ipsec-webserver" ,
731+ },
732+ },
733+ Spec : corev1.PodSpec {
734+ Tolerations : []corev1.Toleration {
735+ {
736+ Key : "node-role.kubernetes.io/master" ,
737+ Operator : corev1 .TolerationOpExists ,
738+ Effect : corev1 .TaintEffectNoSchedule ,
739+ },
740+ },
741+ TerminationGracePeriodSeconds : & immediate ,
742+ Containers : []corev1.Container {e2epod .NewAgnhostContainer ("agnhost-container" , nil , nil , httpServerContainerCmd (port )... )},
743+ },
744+ },
745+ },
746+ }
747+ ds , err := oc .AdminKubeClient ().AppsV1 ().DaemonSets (namespace ).Create (context .Background (), ds , metav1.CreateOptions {})
748+ o .Expect (err ).NotTo (o .HaveOccurred ())
749+ err = wait .PollUntilContextTimeout (context .Background (), 1 * time .Second ,
750+ 180 * time .Second , true , func (ctx context.Context ) (bool , error ) {
751+ return isDaemonSetRunning (oc , namespace , ds .Name )
752+ })
753+ o .Expect (err ).NotTo (o .HaveOccurred ())
754+
755+ pods , err := oc .AdminKubeClient ().CoreV1 ().Pods (namespace ).List (context .Background (),
756+ metav1.ListOptions {LabelSelector : labels .Set (ds .Spec .Selector .MatchLabels ).String ()})
757+ o .Expect (err ).NotTo (o .HaveOccurred ())
758+ ds , err = getDaemonSet (oc , namespace , ds .Name )
759+ o .Expect (err ).NotTo (o .HaveOccurred ())
760+ o .Expect (len (pods .Items )).To (o .Equal (int (ds .Status .NumberAvailable )), fmt .Sprintf ("%#v" , pods .Items ))
761+ return pods .Items
762+ }
763+
764+ func checkPodCrossConnectivity (pods []corev1.Pod ) {
765+ g .GinkgoHelper ()
766+ var testFns []func () error
767+ for _ , sourcePod := range pods {
768+ for _ , targetPod := range pods {
769+ if sourcePod .Name == targetPod .Name {
770+ // Skip if source and target pod are same, pod connectivity check is not required
771+ // for this case.
772+ continue
773+ }
774+ testFns = append (testFns , func () error {
775+ framework .Logf ("Checking pod connectivity from node %s to node %s" , sourcePod .Spec .NodeName , targetPod .Spec .NodeName )
776+ return connectToServer (podConfiguration {namespace : sourcePod .Namespace , name : sourcePod .Name }, targetPod .Status .PodIP , int (port ))
777+ })
778+ }
779+ }
780+ errs := ParallelTest (6 , testFns )
781+ o .Expect (errs ).To (o .Equal ([]error (nil )))
782+ }
783+
599784func waitForIPsecConfigToComplete (oc * exutil.CLI , ipsecMode v1.IPsecMode ) {
600785 g .GinkgoHelper ()
601786 switch ipsecMode {
0 commit comments