Skip to content

Commit 2969e75

Browse files
Merge pull request #29563 from pperiyasamy/nat-t-e2e
CORENET-5668: Update IPsec e2e test to validate NAT-T encapsulation option
2 parents dd7a2a8 + 6c4a40a commit 2969e75

File tree

6 files changed

+223
-33
lines changed

6 files changed

+223
-33
lines changed

pkg/testsuites/standard_suites.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ var staticSuites = []ginkgo.TestSuite{
260260
return strings.Contains(name, "[Suite:openshift/network/ipsec")
261261
},
262262
Parallelism: 1,
263-
TestTimeout: 60 * time.Minute,
263+
TestTimeout: 20 * time.Minute,
264264
},
265265
{
266266
Name: "openshift/network/stress",

test/extended/networking/egressip_helpers.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1382,15 +1382,6 @@ func (p *PortAllocator) allocatePort(port int) error {
13821382
return nil
13831383
}
13841384

1385-
// deleteDaemonSet deletes the Daemonset <namespace>/<dsName>.
1386-
func deleteDaemonSet(clientset kubernetes.Interface, namespace, dsName string) error {
1387-
deleteOptions := metav1.DeleteOptions{}
1388-
if err := clientset.AppsV1().DaemonSets(namespace).Delete(context.TODO(), dsName, deleteOptions); err != nil {
1389-
return fmt.Errorf("Failed to delete DaemonSet %s/%s: %v", namespace, dsName, err)
1390-
}
1391-
return nil
1392-
}
1393-
13941385
// createHostNetworkedDaemonSetAndProbe creates a host networked pod in namespace <namespace> on
13951386
// node <nodeName>. It will allocate a port to listen on and it will return
13961387
// the DaemonSet or an error.

test/extended/networking/internal_ports.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,14 +166,14 @@ var _ = ginkgo.Describe("[sig-network] Internal connectivity", func() {
166166
}
167167
}
168168
}
169-
errs := parallelTest(6, testFns)
169+
errs := ParallelTest(6, testFns)
170170
o.Expect(errs).To(o.Equal([]error(nil)))
171171
})
172172
})
173173

174-
// parallelTest runs the provided fns in parallel with at most workers and returns an array of all
174+
// ParallelTest runs the provided fns in parallel with at most workers and returns an array of all
175175
// non nil errors.
176-
func parallelTest(workers int, fns []func() error) []error {
176+
func ParallelTest(workers int, fns []func() error) []error {
177177
var wg sync.WaitGroup
178178
work := make(chan func() error, workers)
179179
results := make(chan error, workers)

test/extended/networking/ipsec.go

Lines changed: 205 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -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

96103
type trafficType string
97104

105+
type ipsecConfig struct {
106+
mode v1.IPsecMode
107+
encap v1.Encapsulation
108+
}
109+
98110
const (
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+
599784
func waitForIPsecConfigToComplete(oc *exutil.CLI, ipsecMode v1.IPsecMode) {
600785
g.GinkgoHelper()
601786
switch ipsecMode {

0 commit comments

Comments
 (0)