Skip to content

Commit 902316f

Browse files
committed
add e2e
Signed-off-by: zbb88888 <jmdxjsjgcxy@gmail.com>
1 parent 16472bc commit 902316f

File tree

2 files changed

+88
-4
lines changed

2 files changed

+88
-4
lines changed

dist/images/vpcnatgateway/nat-gateway.sh

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,15 +350,17 @@ function del_snat() {
350350
function add_hairpin_snat() {
351351
# make sure inited
352352
check_inited
353+
local all_hairpin_rules
354+
all_hairpin_rules=$($iptables_save_cmd -t nat | grep HAIRPIN_SNAT)
353355
for rule in $@
354356
do
355357
arr=(${rule//,/ })
356358
eip=(${arr[0]//\// })
357359
internalCIDR=${arr[1]}
358360

359-
# Cache iptables-save output to avoid redundant calls (performance optimization)
361+
# Filter from cached rules for this specific CIDR
360362
local existing_rules
361-
existing_rules=$($iptables_save_cmd -t nat | grep HAIRPIN_SNAT | grep -w -- "-s $internalCIDR" | grep -w -- "-d $internalCIDR")
363+
existing_rules=$(echo "$all_hairpin_rules" | grep -w -- "-s $internalCIDR" | grep -w -- "-d $internalCIDR")
362364

363365
# Check if this exact rule already exists (idempotent)
364366
if echo "$existing_rules" | grep -qE -- "--to-source $eip(\$| )"; then
@@ -380,13 +382,15 @@ function add_hairpin_snat() {
380382
function del_hairpin_snat() {
381383
# make sure inited
382384
check_inited
385+
local all_hairpin_rules
386+
all_hairpin_rules=$($iptables_save_cmd -t nat | grep HAIRPIN_SNAT)
383387
for rule in $@
384388
do
385389
arr=(${rule//,/ })
386390
eip=(${arr[0]//\// })
387391
internalCIDR=${arr[1]}
388392
# check if rule exists (idempotent - skip if not found)
389-
if $iptables_save_cmd -t nat | grep HAIRPIN_SNAT | grep -w -- "-s $internalCIDR" | grep -w -- "-d $internalCIDR" | grep -E -- "--to-source $eip(\$| )" > /dev/null; then
393+
if echo "$all_hairpin_rules" | grep -w -- "-s $internalCIDR" | grep -w -- "-d $internalCIDR" | grep -qE -- "--to-source $eip(\$| )"; then
390394
exec_cmd "$iptables_cmd -t nat -D HAIRPIN_SNAT -s $internalCIDR -d $internalCIDR -j SNAT --to-source $eip"
391395
echo "Hairpin SNAT rule deleted: $internalCIDR -> $eip"
392396
fi

test/e2e/iptables-vpc-nat-gw/e2e_test.go

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,64 @@ func verifySubnetStatusAfterEIPOperation(subnetClient *framework.SubnetClient, s
248248
}
249249
}
250250

251+
// checkHairpinSnatRuleExists checks if hairpin SNAT rule exists in the NAT gateway pod
252+
// Returns true if rule exists, false otherwise (including when HAIRPIN_SNAT chain doesn't exist)
253+
// This is used with gomega.Eventually for polling-based verification
254+
func checkHairpinSnatRuleExists(natGwPodName, cidr, eip string) bool {
255+
cmd := []string{"iptables-save", "-t", "nat"}
256+
stdout, _, err := framework.KubectlExec(framework.KubeOvnNamespace, natGwPodName, cmd...)
257+
if err != nil {
258+
framework.Logf("Failed to exec iptables-save in NAT gateway pod %s: %v", natGwPodName, err)
259+
return false
260+
}
261+
262+
iptablesOutput := string(stdout)
263+
264+
// If HAIRPIN_SNAT chain doesn't exist, rule cannot exist
265+
if !strings.Contains(iptablesOutput, ":HAIRPIN_SNAT") && !strings.Contains(iptablesOutput, "-N HAIRPIN_SNAT") {
266+
return false
267+
}
268+
269+
hairpinRulePattern := fmt.Sprintf("-A HAIRPIN_SNAT -s %s -d %s -j SNAT --to-source %s", cidr, cidr, eip)
270+
return strings.Contains(iptablesOutput, hairpinRulePattern)
271+
}
272+
273+
// verifyHairpinSnatRule verifies hairpin SNAT rule exists or not in the NAT gateway pod
274+
// Hairpin SNAT enables internal VMs to access other internal VMs via their FIP/EIP
275+
// The rule format: -A HAIRPIN_SNAT -s <cidr> -d <cidr> -j SNAT --to-source <eip>
276+
// This feature was introduced in v1.15, so the function will skip verification
277+
// if HAIRPIN_SNAT chain does not exist (for backward compatibility)
278+
func verifyHairpinSnatRule(natGwPodName, cidr, eip string, shouldExist bool) {
279+
ginkgo.GinkgoHelper()
280+
281+
cmd := []string{"iptables-save", "-t", "nat"}
282+
stdout, _, err := framework.KubectlExec(framework.KubeOvnNamespace, natGwPodName, cmd...)
283+
framework.ExpectNoError(err, "failed to exec iptables-save in NAT gateway pod %s", natGwPodName)
284+
285+
iptablesOutput := string(stdout)
286+
287+
// Check if HAIRPIN_SNAT chain exists (feature introduced in v1.15)
288+
// Skip verification if the chain doesn't exist for backward compatibility
289+
if !strings.Contains(iptablesOutput, ":HAIRPIN_SNAT") && !strings.Contains(iptablesOutput, "-N HAIRPIN_SNAT") {
290+
framework.Logf("HAIRPIN_SNAT chain not found, skipping hairpin SNAT verification (feature requires v1.15+)")
291+
return
292+
}
293+
294+
// Check for hairpin SNAT rule pattern: -A HAIRPIN_SNAT -s <cidr> -d <cidr> -j SNAT --to-source <eip>
295+
hairpinRulePattern := fmt.Sprintf("-A HAIRPIN_SNAT -s %s -d %s -j SNAT --to-source %s", cidr, cidr, eip)
296+
ruleExists := strings.Contains(iptablesOutput, hairpinRulePattern)
297+
298+
if shouldExist {
299+
framework.ExpectTrue(ruleExists,
300+
"Hairpin SNAT rule should exist: %s\niptables output:\n%s", hairpinRulePattern, iptablesOutput)
301+
framework.Logf("Verified hairpin SNAT rule exists: %s", hairpinRulePattern)
302+
} else {
303+
framework.ExpectFalse(ruleExists,
304+
"Hairpin SNAT rule should NOT exist: %s\niptables output:\n%s", hairpinRulePattern, iptablesOutput)
305+
framework.Logf("Verified hairpin SNAT rule does not exist for CIDR %s", cidr)
306+
}
307+
}
308+
251309
var _ = framework.OrderedDescribe("[group:iptables-vpc-nat-gw]", func() {
252310
f := framework.NewDefaultFramework("iptables-vpc-nat-gw")
253311

@@ -456,6 +514,7 @@ var _ = framework.OrderedDescribe("[group:iptables-vpc-nat-gw]", func() {
456514
})
457515

458516
framework.ConformanceIt("[2] iptables EIP FIP SNAT DNAT", func() {
517+
f.SkipVersionPriorTo(1, 15, "This feature was introduced in v1.15")
459518
// Test-specific variables
460519
randomSuffix := framework.RandomSuffix()
461520
fipVipName := "fip-vip-" + randomSuffix
@@ -528,6 +587,12 @@ var _ = framework.OrderedDescribe("[group:iptables-vpc-nat-gw]", func() {
528587
iptablesSnatRuleClient.DeleteSync(snatName)
529588
})
530589

590+
// Verify hairpin SNAT rule is automatically created for internal CIDR
591+
ginkgo.By("Verifying hairpin SNAT rule exists in NAT gateway pod")
592+
vpcNatGwPodName := util.GenNatGwPodName(vpcNatGwName)
593+
snatEip = iptablesEIPClient.Get(snatEipName)
594+
verifyHairpinSnatRule(vpcNatGwPodName, overlaySubnetV4Cidr, snatEip.Status.IP, true)
595+
531596
ginkgo.By("Creating iptables vip for dnat")
532597
dnatVip := framework.MakeVip(f.Namespace.Name, dnatVipName, overlaySubnetName, "", "", "")
533598
_ = vipClient.CreateSync(dnatVip)
@@ -603,8 +668,13 @@ var _ = framework.OrderedDescribe("[group:iptables-vpc-nat-gw]", func() {
603668
iptablesSnatRuleClient.DeleteSync(sharedEipSnatName)
604669
})
605670

606-
ginkgo.By("Get share eip")
671+
// Verify hairpin SNAT rule is created for shared SNAT as well
672+
ginkgo.By("Getting share eip for hairpin verification")
607673
shareEip = iptablesEIPClient.Get(sharedEipName)
674+
framework.ExpectNotEmpty(shareEip.Status.IP, "shareEip.Status.IP should not be empty for hairpin verification")
675+
ginkgo.By("Verifying hairpin SNAT rule exists for shared snat")
676+
verifyHairpinSnatRule(vpcNatGwPodName, overlaySubnetV4Cidr, shareEip.Status.IP, true)
677+
608678
ginkgo.By("Get share dnat")
609679
shareDnat = iptablesDnatRuleClient.Get(sharedEipDnatName)
610680
ginkgo.By("Get share snat")
@@ -628,6 +698,16 @@ var _ = framework.OrderedDescribe("[group:iptables-vpc-nat-gw]", func() {
628698
// make sure eip is shared
629699
nats := []string{util.DnatUsingEip, util.FipUsingEip, util.SnatUsingEip}
630700
framework.ExpectEqual(shareEip.Status.Nat, strings.Join(nats, ","))
701+
702+
// Verify hairpin SNAT rule cleanup when SNAT is deleted
703+
ginkgo.By("Deleting snat to verify hairpin SNAT rule cleanup")
704+
iptablesSnatRuleClient.DeleteSync(snatName)
705+
ginkgo.By("Verifying hairpin SNAT rule is deleted after snat deletion")
706+
gomega.Eventually(func() bool {
707+
return checkHairpinSnatRuleExists(vpcNatGwPodName, overlaySubnetV4Cidr, snatEip.Status.IP)
708+
}, 30*time.Second, 2*time.Second).Should(gomega.BeFalse(),
709+
"Hairpin SNAT rule should be deleted after SNAT deletion")
710+
631711
// All cleanup is handled by DeferCleanup above, no need for manual cleanup
632712
})
633713

0 commit comments

Comments
 (0)