From 40a8a2c5cb31b1f9fe6150fee4761e862bece369 Mon Sep 17 00:00:00 2001 From: microness Date: Thu, 22 Jan 2026 02:06:51 +0900 Subject: [PATCH] test: refactor container_kill_linux_test.go to use Tigron Signed-off-by: microness --- .../container/container_kill_linux_test.go | 162 +++++++++++++----- pkg/testutil/iptables/iptables_linux.go | 23 +++ 2 files changed, 146 insertions(+), 39 deletions(-) diff --git a/cmd/nerdctl/container/container_kill_linux_test.go b/cmd/nerdctl/container/container_kill_linux_test.go index 6372d80ee33..90deb115a5a 100644 --- a/cmd/nerdctl/container/container_kill_linux_test.go +++ b/cmd/nerdctl/container/container_kill_linux_test.go @@ -18,63 +18,147 @@ package container import ( "fmt" + "strconv" "strings" "testing" "github.com/coreos/go-iptables/iptables" "gotest.tools/v3/assert" - "github.com/containerd/nerdctl/v2/pkg/rootlessutil" + "github.com/containerd/nerdctl/mod/tigron/expect" + "github.com/containerd/nerdctl/mod/tigron/require" + "github.com/containerd/nerdctl/mod/tigron/test" + "github.com/containerd/nerdctl/mod/tigron/tig" + "github.com/containerd/nerdctl/v2/pkg/testutil" iptablesutil "github.com/containerd/nerdctl/v2/pkg/testutil/iptables" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" + "github.com/containerd/nerdctl/v2/pkg/testutil/portlock" ) // TestKillCleanupForwards runs a container that exposes a port and then kill it. // The test checks that the kill command effectively clean up -// the iptables forwards creted from the run. +// the iptables forwards created from the run. func TestKillCleanupForwards(t *testing.T) { - const ( - hostPort = 9999 - testContainerName = "ngx" - ) - base := testutil.NewBase(t) - defer func() { - base.Cmd("rm", "-f", testContainerName).Run() - }() - - // skip if rootless - if rootlessutil.IsRootless() { - t.Skip("pkg/testutil/iptables does not support rootless") - } ipt, err := iptables.New() assert.NilError(t, err) - containerID := base.Cmd("run", "-d", - "--restart=no", - "--name", testContainerName, - "-p", fmt.Sprintf("127.0.0.1:%d:80", hostPort), - testutil.NginxAlpineImage).Run().Stdout() - containerID = strings.TrimSuffix(containerID, "\n") - - containerIP := base.Cmd("inspect", - "-f", - "'{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}'", - testContainerName).Run().Stdout() - containerIP = strings.ReplaceAll(containerIP, "'", "") - containerIP = strings.TrimSuffix(containerIP, "\n") - - // define iptables chain name depending on the target (docker/nerdctl) - var chain string - if nerdtest.IsDocker() { - chain = "DOCKER" - } else { - redirectChain := "CNI-HOSTPORT-DNAT" - chain = iptablesutil.GetRedirectedChain(t, ipt, redirectChain, testutil.Namespace, containerID) + testCase := nerdtest.Setup() + + testCase.Require = require.Not(nerdtest.Rootless) + + testCase.Setup = func(data test.Data, helpers test.Helpers) { + hostPort, err := portlock.Acquire(0) + if err != nil { + t.Logf("Failed to acquire port: %v", err) + t.FailNow() + } + + containerName := data.Identifier() + + helpers.Ensure( + "run", "-d", + "--restart=no", + "--name", containerName, + "-p", fmt.Sprintf("127.0.0.1:%d:80", hostPort), + testutil.NginxAlpineImage, + ) + nerdtest.EnsureContainerStarted(helpers, containerName) + + containerID := strings.TrimSpace( + helpers.Capture("inspect", "-f", "{{.Id}}", containerName), + ) + + containerIP := strings.TrimSpace( + helpers.Capture( + "inspect", + "-f", "{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}", + containerName, + ), + ) + + // define iptables chain name depending on the target (docker/nerdctl) + var chain string + if nerdtest.IsDocker() { + chain = "DOCKER" + } else { + chain = iptablesutil.GetRedirectedChain( + t, + ipt, + "CNI-HOSTPORT-DNAT", + testutil.Namespace, + containerID, + ) + } + + data.Labels().Set("containerName", containerName) + data.Labels().Set("containerIP", containerIP) + data.Labels().Set("hostPort", strconv.Itoa(hostPort)) + data.Labels().Set("chain", chain) + } + + testCase.SubTests = []*test.Case{ + { + Description: "iptables forwarding rule should exist before container is killed", + NoParallel: true, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Custom("iptables", "-t", "nat", "-S", data.Labels().Get("chain")) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, t tig.T) { + rules := strings.Split(stdout, "\n") + containerIP := data.Labels().Get("containerIP") + hostPort, err := strconv.Atoi(data.Labels().Get("hostPort")) + assert.NilError(t, err) + found, err := iptablesutil.ForwardExistsFromRules(rules, containerIP, hostPort) + assert.NilError(t, err) + assert.Assert(t, found) + }, + } + }, + }, + { + Description: "kill container", + NoParallel: true, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("kill", data.Labels().Get("containerName")) + }, + Expected: test.Expects(expect.ExitCodeSuccess, nil, nil), + }, + { + Description: "iptables forwarding rule should be removed after container is killed", + NoParallel: true, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Custom("iptables", "-t", "nat", "-S", data.Labels().Get("chain")) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: expect.ExitCodeGenericFail, + Output: func(stdout string, t tig.T) { + rules := strings.Split(stdout, "\n") + containerIP := data.Labels().Get("containerIP") + hostPort, err := strconv.Atoi(data.Labels().Get("hostPort")) + assert.NilError(t, err) + found, err := iptablesutil.ForwardExistsFromRules(rules, containerIP, hostPort) + assert.NilError(t, err) + assert.Assert(t, !found) + }, + } + }, + }, + } + + testCase.Cleanup = func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + + if p := data.Labels().Get("hostPort"); p != "" { + if port, err := strconv.Atoi(p); err == nil { + _ = portlock.Release(port) + } + } } - assert.Equal(t, iptablesutil.ForwardExists(t, ipt, chain, containerIP, hostPort), true) - base.Cmd("kill", testContainerName).AssertOK() - assert.Equal(t, iptablesutil.ForwardExists(t, ipt, chain, containerIP, hostPort), false) + testCase.Run(t) } diff --git a/pkg/testutil/iptables/iptables_linux.go b/pkg/testutil/iptables/iptables_linux.go index c0dcea4d42d..196531d60a5 100644 --- a/pkg/testutil/iptables/iptables_linux.go +++ b/pkg/testutil/iptables/iptables_linux.go @@ -97,3 +97,26 @@ func GetRedirectedChain(t *testing.T, ipt *iptables.IPTables, chain, namespace, } return redirectedChain } + +// ForwardExistsFromRules checks whether any rule in the given slice +// matches the expected DNAT forwarding pattern. +func ForwardExistsFromRules(rules []string, containerIP string, port int) (bool, error) { + if len(rules) < 1 { + return false, fmt.Errorf("not enough rules: %d", len(rules)) + } + + // here we check if at least one of the rules in the chain + // matches the required string to identify that the rule was applied + found := false + matchRule := `--dport ` + fmt.Sprintf("%d", port) + ` .+ --to-destination ` + containerIP + for _, rule := range rules { + foundInRule, err := regexp.MatchString(matchRule, rule) + if err != nil { + return false, fmt.Errorf("error in match string: %q", err) + } + if foundInRule { + found = foundInRule + } + } + return found, nil +}