This is an automated email from the ASF dual-hosted git repository.

github-actions[bot] pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo-kubernetes.git


The following commit(s) were added to refs/heads/master by this push:
     new 5d5160fb fix: preserve legacy CNI values in upgrade e2e (#1011)
5d5160fb is described below

commit 5d5160fbd7193e6549409e634aecc98103714cdf
Author: mfordjody <[email protected]>
AuthorDate: Tue Aug 11 08:50:22 2026 +0800

    fix: preserve legacy CNI values in upgrade e2e (#1011)
    
    * fix: preserve legacy CNI values in upgrade e2e
    
    * test: collect pod events on e2e failure
    
    * fix: fall back to direct iptables rules without ipset
    
    * fix: preserve established CNI connections
---
 cni/pkg/nodeagent/iptables.go      | 153 ++++++++++++++++++++++++++++++++++++-
 cni/pkg/nodeagent/iptables_test.go |  98 +++++++++++++++++++++++-
 tests/e2e/run.sh                   |  14 +++-
 3 files changed, 258 insertions(+), 7 deletions(-)

diff --git a/cni/pkg/nodeagent/iptables.go b/cni/pkg/nodeagent/iptables.go
index 41830e7b..200c3885 100644
--- a/cni/pkg/nodeagent/iptables.go
+++ b/cni/pkg/nodeagent/iptables.go
@@ -18,8 +18,10 @@ package nodeagent
 import (
        "bytes"
        "context"
+       "errors"
        "fmt"
        "net"
+       "os"
        "os/exec"
 )
 
@@ -53,6 +55,10 @@ type IPTablesRuleManager struct {
        grpcInboundPort int
        dryRun          bool
        runner          CommandRunner
+       // directRules is used only when the node does not provide ipset. It 
keeps
+       // the same inbound fence with destination-IP rules instead of failing 
every
+       // managed Pod's sandbox creation.
+       directRules bool
 }
 
 func NewIPTablesRuleManager(conf NetConf) *IPTablesRuleManager {
@@ -76,7 +82,13 @@ func (m *IPTablesRuleManager) AddPodRules(ctx 
context.Context, podIP string, exc
        if err != nil {
                return err
        }
+       if m.directRules {
+               return m.addDirectPodRules(ctx, ip, excludedPorts)
+       }
        if err := m.ensureBase(ctx); err != nil {
+               if m.useDirectRules(err) {
+                       return m.addDirectPodRules(ctx, ip, excludedPorts)
+               }
                return err
        }
        if err := m.runIPSet(ctx, "add", meshPodIPSet, ip, "-exist"); err != 
nil {
@@ -90,12 +102,24 @@ func (m *IPTablesRuleManager) DeletePodRules(ctx 
context.Context, podIP string,
        if err != nil {
                return err
        }
+       if m.directRules {
+               return m.deleteDirectPodRules(ctx, ip, excludedPorts)
+       }
        for _, port := range excludedPorts {
                if err := m.runIPSet(ctx, "del", meshExcludeIPSet, 
excludeEntry(ip, port), "-exist"); err != nil {
+                       if m.useDirectRules(err) {
+                               return m.deleteDirectPodRules(ctx, ip, 
excludedPorts)
+                       }
                        return err
                }
        }
-       return m.runIPSet(ctx, "del", meshPodIPSet, ip, "-exist")
+       if err := m.runIPSet(ctx, "del", meshPodIPSet, ip, "-exist"); err != 
nil {
+               if m.useDirectRules(err) {
+                       return m.deleteDirectPodRules(ctx, ip, excludedPorts)
+               }
+               return err
+       }
+       return nil
 }
 
 // Reconcile rebuilds the whole fence from the supplied pod states.
@@ -105,7 +129,13 @@ func (m *IPTablesRuleManager) DeletePodRules(ctx 
context.Context, podIP string,
 // Without this the fence silently disappears for every already-running pod,
 // so the node agent replays it from its own persisted state.
 func (m *IPTablesRuleManager) Reconcile(ctx context.Context, states 
[]PodState) error {
+       if m.directRules {
+               return m.reconcileDirectRules(ctx, states)
+       }
        if err := m.ensureBase(ctx); err != nil {
+               if m.useDirectRules(err) {
+                       return m.reconcileDirectRules(ctx, states)
+               }
                return err
        }
        if err := m.runIPSet(ctx, "flush", meshPodIPSet); err != nil {
@@ -130,6 +160,123 @@ func (m *IPTablesRuleManager) Reconcile(ctx 
context.Context, states []PodState)
        return nil
 }
 
+func (m *IPTablesRuleManager) useDirectRules(err error) bool {
+       if !errors.Is(err, exec.ErrNotFound) {
+               return false
+       }
+       m.directRules = true
+       fmt.Fprintln(os.Stderr, "dubbo-cni: ipset is unavailable; using direct 
iptables inbound rules")
+       return true
+}
+
+func (m *IPTablesRuleManager) addDirectPodRules(ctx context.Context, ip 
string, excludedPorts []int) error {
+       if err := m.ensureDirectBase(ctx); err != nil {
+               return err
+       }
+       rules, err := m.directPodRules(ip, excludedPorts)
+       if err != nil {
+               return err
+       }
+       m.deleteDirectRules(ctx, rules)
+       for _, rule := range rules {
+               if err := m.appendRule(ctx, rule...); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+func (m *IPTablesRuleManager) deleteDirectPodRules(ctx context.Context, ip 
string, excludedPorts []int) error {
+       rules, err := m.directPodRules(ip, excludedPorts)
+       if err != nil {
+               return err
+       }
+       m.deleteDirectRules(ctx, rules)
+       return nil
+}
+
+func (m *IPTablesRuleManager) reconcileDirectRules(ctx context.Context, states 
[]PodState) error {
+       if err := m.ensureDirectBase(ctx); err != nil {
+               return err
+       }
+       if err := m.run(ctx, "-w", "-t", "filter", "-F", meshInboundChain); err 
!= nil {
+               return err
+       }
+       for _, state := range states {
+               ip, err := normalizePodIP(state.IP)
+               if err != nil {
+                       // A malformed entry must not stop the remaining pods 
from being restored.
+                       continue
+               }
+               rules, err := m.directPodRules(ip, state.ExcludedPorts)
+               if err != nil {
+                       return err
+               }
+               for _, rule := range rules {
+                       if err := m.appendRule(ctx, rule...); err != nil {
+                               return err
+                       }
+               }
+       }
+       return nil
+}
+
+func (m *IPTablesRuleManager) ensureDirectBase(ctx context.Context) error {
+       if err := m.runIgnoreExists(ctx, "-w", "-t", "filter", "-N", 
meshInboundChain); err != nil {
+               return err
+       }
+       for _, chain := range []string{"FORWARD", "OUTPUT"} {
+               if err := m.run(ctx, "-w", "-t", "filter", "-C", chain, "-j", 
meshInboundChain); err == nil {
+                       continue
+               }
+               if err := m.run(ctx, "-w", "-t", "filter", "-I", chain, "1", 
"-j", meshInboundChain); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+func (m *IPTablesRuleManager) directPodRules(ip string, excludedPorts []int) 
([][]string, error) {
+       ports := make([]int, 0, len(excludedPorts)+3)
+       seen := make(map[int]struct{}, len(excludedPorts)+3)
+       addPort := func(port int) error {
+               if port < 1 || port > 65535 {
+                       return fmt.Errorf("excluded port %d is out of range", 
port)
+               }
+               if _, found := seen[port]; found {
+                       return nil
+               }
+               seen[port] = struct{}{}
+               ports = append(ports, port)
+               return nil
+       }
+       for _, port := range excludedPorts {
+               if err := addPort(port); err != nil {
+                       return nil, err
+               }
+       }
+       for _, port := range []int{m.grpcInboundPort, dxgateAdminPort, 
dxproxyAdminPort} {
+               if err := addPort(port); err != nil {
+                       return nil, err
+               }
+       }
+       rules := make([][]string, 0, len(ports)+1)
+       for _, port := range ports {
+               rules = append(rules, []string{"-d", ip, "-p", "tcp", 
"--dport", fmt.Sprint(port), "-j", "RETURN"})
+       }
+       // Fence only connection attempts. A reply to an outbound xDS/activation
+       // connection also has the Pod as its destination, but is ESTABLISHED 
and
+       // must pass; rejecting it would leave injected gateways permanently
+       // unready.
+       return append(rules, []string{"-d", ip, "-p", "tcp", "-m", "conntrack", 
"--ctstate", "NEW", "-j", "REJECT"}), nil
+}
+
+func (m *IPTablesRuleManager) deleteDirectRules(ctx context.Context, rules 
[][]string) {
+       for _, rule := range rules {
+               m.deleteRepeated(ctx, rule...)
+       }
+}
+
 func (m *IPTablesRuleManager) addExcludedPorts(ctx context.Context, ip string, 
ports []int) error {
        for _, port := range ports {
                if port < 1 || port > 65535 {
@@ -170,7 +317,9 @@ func (m *IPTablesRuleManager) ensureBase(ctx 
context.Context) error {
        allowGRPCInbound := []string{"-m", "set", "--match-set", meshPodIPSet, 
"dst", "-p", "tcp", "--dport", fmt.Sprint(m.grpcInboundPort), "-j", "RETURN"}
        allowDxgateAdmin := []string{"-m", "set", "--match-set", meshPodIPSet, 
"dst", "-p", "tcp", "--dport", fmt.Sprint(dxgateAdminPort), "-j", "RETURN"}
        allowDxproxyAdmin := []string{"-m", "set", "--match-set", meshPodIPSet, 
"dst", "-p", "tcp", "--dport", fmt.Sprint(dxproxyAdminPort), "-j", "RETURN"}
-       rejectOtherTCP := []string{"-m", "set", "--match-set", meshPodIPSet, 
"dst", "-p", "tcp", "-j", "REJECT"}
+       // Reject only new inbound connections. Established replies to outbound
+       // control-plane traffic still target a managed Pod and must not be 
fenced.
+       rejectOtherTCP := []string{"-m", "set", "--match-set", meshPodIPSet, 
"dst", "-p", "tcp", "-m", "conntrack", "--ctstate", "NEW", "-j", "REJECT"}
        m.deleteRepeated(ctx, allowExcluded...)
        m.deleteRepeated(ctx, allowGRPCInbound...)
        m.deleteRepeated(ctx, allowDxgateAdmin...)
diff --git a/cni/pkg/nodeagent/iptables_test.go 
b/cni/pkg/nodeagent/iptables_test.go
index e1121391..db002649 100644
--- a/cni/pkg/nodeagent/iptables_test.go
+++ b/cni/pkg/nodeagent/iptables_test.go
@@ -17,6 +17,7 @@ package nodeagent
 
 import (
        "context"
+       "os/exec"
        "strings"
        "testing"
 )
@@ -44,7 +45,7 @@ func TestIPTablesRuleManagerAddsGRPCInboundBoundaryRules(t 
*testing.T) {
                "-A DUBBO-GRPC-INBOUND -m set --match-set 
DUBBO-GRPC-INBOUND-PODS dst -p tcp --dport 15080 -j RETURN",
                "-A DUBBO-GRPC-INBOUND -m set --match-set 
DUBBO-GRPC-INBOUND-PODS dst -p tcp --dport 26021 -j RETURN",
                "-A DUBBO-GRPC-INBOUND -m set --match-set 
DUBBO-GRPC-INBOUND-PODS dst -p tcp --dport 15020 -j RETURN",
-               "-A DUBBO-GRPC-INBOUND -m set --match-set 
DUBBO-GRPC-INBOUND-PODS dst -p tcp -j REJECT",
+               "-A DUBBO-GRPC-INBOUND -m set --match-set 
DUBBO-GRPC-INBOUND-PODS dst -p tcp -m conntrack --ctstate NEW -j REJECT",
                "ipset add DUBBO-GRPC-INBOUND-PODS 10.244.0.12 -exist",
                "ipset add DUBBO-GRPC-INBOUND-EXCLUDE 10.244.0.12,tcp:9090 
-exist",
        } {
@@ -55,7 +56,7 @@ func TestIPTablesRuleManagerAddsGRPCInboundBoundaryRules(t 
*testing.T) {
 
        // The exclusion must be evaluated before the catch-all REJECT.
        excludeAt := strings.Index(joined, "--match-set 
DUBBO-GRPC-INBOUND-EXCLUDE dst,dst -p tcp -j RETURN")
-       rejectAt := strings.Index(joined, "DUBBO-GRPC-INBOUND-PODS dst -p tcp 
-j REJECT")
+       rejectAt := strings.Index(joined, "DUBBO-GRPC-INBOUND-PODS dst -p tcp 
-m conntrack --ctstate NEW -j REJECT")
        if excludeAt < 0 || rejectAt < 0 || excludeAt > rejectAt {
                t.Fatalf("exclusion rule is not appended before the REJECT 
rule:\n%s", joined)
        }
@@ -95,6 +96,87 @@ func TestIPTablesRuleManagerReconcileRebuildsFence(t 
*testing.T) {
        }
 }
 
+func TestIPTablesRuleManagerFallsBackToDirectRulesWithoutIPSet(t *testing.T) {
+       runner := &ipsetMissingRunner{}
+       conf, err := ParseNetConf([]byte(`{"grpcInboundPort":15080}`))
+       if err != nil {
+               t.Fatalf("ParseNetConf() failed: %v", err)
+       }
+       manager := NewIPTablesRuleManagerWithRunner(conf, runner)
+
+       if err := manager.AddPodRules(context.Background(), "10.244.0.12", 
[]int{9090}); err != nil {
+               t.Fatalf("AddPodRules() failed: %v", err)
+       }
+
+       joined := strings.Join(runner.commands, "\n")
+       for _, want := range []string{
+               "ipset create DUBBO-GRPC-INBOUND-PODS hash:ip -exist",
+               "-A DUBBO-GRPC-INBOUND -d 10.244.0.12 -p tcp --dport 9090 -j 
RETURN",
+               "-A DUBBO-GRPC-INBOUND -d 10.244.0.12 -p tcp --dport 15080 -j 
RETURN",
+               "-A DUBBO-GRPC-INBOUND -d 10.244.0.12 -p tcp --dport 26021 -j 
RETURN",
+               "-A DUBBO-GRPC-INBOUND -d 10.244.0.12 -p tcp --dport 15020 -j 
RETURN",
+               "-A DUBBO-GRPC-INBOUND -d 10.244.0.12 -p tcp -m conntrack 
--ctstate NEW -j REJECT",
+       } {
+               if !strings.Contains(joined, want) {
+                       t.Fatalf("commands missing %q:\n%s", want, joined)
+               }
+       }
+       if strings.Contains(joined, "--match-set") {
+               t.Fatalf("ipset rule leaked into direct fallback:\n%s", joined)
+       }
+}
+
+func TestIPTablesRuleManagerReconcilesDirectRulesWithoutIPSet(t *testing.T) {
+       runner := &ipsetMissingRunner{}
+       conf, err := ParseNetConf([]byte(`{"grpcInboundPort":15080}`))
+       if err != nil {
+               t.Fatalf("ParseNetConf() failed: %v", err)
+       }
+       manager := NewIPTablesRuleManagerWithRunner(conf, runner)
+
+       if err := manager.Reconcile(context.Background(), []PodState{{
+               IP:            "10.244.0.12",
+               ExcludedPorts: []int{9090},
+       }}); err != nil {
+               t.Fatalf("Reconcile() failed: %v", err)
+       }
+
+       joined := strings.Join(runner.commands, "\n")
+       for _, want := range []string{
+               "-F DUBBO-GRPC-INBOUND",
+               "-A DUBBO-GRPC-INBOUND -d 10.244.0.12 -p tcp --dport 9090 -j 
RETURN",
+               "-A DUBBO-GRPC-INBOUND -d 10.244.0.12 -p tcp -m conntrack 
--ctstate NEW -j REJECT",
+       } {
+               if !strings.Contains(joined, want) {
+                       t.Fatalf("commands missing %q:\n%s", want, joined)
+               }
+       }
+}
+
+func TestIPTablesRuleManagerDeletesDirectRulesWithoutIPSet(t *testing.T) {
+       runner := &ipsetMissingRunner{}
+       conf, err := ParseNetConf([]byte(`{"grpcInboundPort":15080}`))
+       if err != nil {
+               t.Fatalf("ParseNetConf() failed: %v", err)
+       }
+       manager := NewIPTablesRuleManagerWithRunner(conf, runner)
+
+       if err := manager.DeletePodRules(context.Background(), "10.244.0.12", 
[]int{9090}); err != nil {
+               t.Fatalf("DeletePodRules() failed: %v", err)
+       }
+
+       joined := strings.Join(runner.commands, "\n")
+       for _, want := range []string{
+               "-D DUBBO-GRPC-INBOUND -d 10.244.0.12 -p tcp --dport 9090 -j 
RETURN",
+               "-D DUBBO-GRPC-INBOUND -d 10.244.0.12 -p tcp --dport 15080 -j 
RETURN",
+               "-D DUBBO-GRPC-INBOUND -d 10.244.0.12 -p tcp -m conntrack 
--ctstate NEW -j REJECT",
+       } {
+               if !strings.Contains(joined, want) {
+                       t.Fatalf("commands missing %q:\n%s", want, joined)
+               }
+       }
+}
+
 type recordingRunner struct {
        commands []string
 }
@@ -109,6 +191,18 @@ func (r *recordingRunner) Run(_ context.Context, name 
string, args ...string) ([
        return nil, nil
 }
 
+type ipsetMissingRunner struct {
+       recordingRunner
+}
+
+func (r *ipsetMissingRunner) Run(ctx context.Context, name string, args 
...string) ([]byte, error) {
+       if name == "ipset" {
+               r.commands = append(r.commands, name+" "+strings.Join(args, " 
"))
+               return nil, &exec.Error{Name: name, Err: exec.ErrNotFound}
+       }
+       return r.recordingRunner.Run(ctx, name, args...)
+}
+
 var errCommandFailed = commandFailedError{}
 
 type commandFailedError struct{}
diff --git a/tests/e2e/run.sh b/tests/e2e/run.sh
index 6c6ed42f..0deaf702 100755
--- a/tests/e2e/run.sh
+++ b/tests/e2e/run.sh
@@ -66,6 +66,13 @@ fail() {
   echo "FAIL: $*" >&2
   echo "--- diagnostics: pods ---" >&2
   "${KUBECTL[@]}" get pods -A -o wide >&2 || true
+  # ContainerCreating failures have no container logs. Events and describe
+  # expose the kubelet/CNI/image error before cleanup deletes the cluster.
+  echo "--- diagnostics: events ---" >&2
+  "${KUBECTL[@]}" get events -A --sort-by=.lastTimestamp >&2 || true
+  echo "--- diagnostics: managed gateway pod descriptions ---" >&2
+  "${KUBECTL[@]}" -n "${APP_NS}" describe pods \
+    -l gateway.networking.k8s.io/gateway-name >&2 || true
   echo "--- diagnostics: dubbod logs ---" >&2
   "${KUBECTL[@]}" -n "${SYSTEM_NS}" logs deploy/dubbod --tail=100 >&2 || true
   echo "--- diagnostics: managed gateway logs ---" >&2
@@ -234,10 +241,11 @@ install_dubbod() {
       --set-string "gateway.image=${DXGATE_IMAGE}"
     )
   else
-    # The previous chart still exposes its legacy CNI configuration.
+    # 0.4.3 predates Inherent. These values target its immutable legacy
+    # chart schema; renaming them leaves its CNI enabled during the upgrade.
     chart_values=(
-      --set "global.inherent.cni.enabled=false"
-      --set-string "global.inherent.cni.image=${image}"
+      --set "global.proxyless.cni.enabled=false"
+      --set-string "global.proxyless.cni.image=${image}"
       --set-string "global.gateway.image=${DXGATE_IMAGE}"
     )
   fi

Reply via email to