From 5d69faa6d66f26603791efddbf19544bae05d3ad Mon Sep 17 00:00:00 2001 From: Haney Maxwell Date: Mon, 10 Nov 2014 16:53:26 -0800 Subject: [PATCH] Allow proxy to run on systems with iptables <1.4.11 --- pkg/util/iptables/iptables.go | 116 ++++++++++++- pkg/util/iptables/iptables_test.go | 257 +++++++++++++++++++++++++---- 2 files changed, 342 insertions(+), 31 deletions(-) diff --git a/pkg/util/iptables/iptables.go b/pkg/util/iptables/iptables.go index 5253c2d200b..5e57bf99d65 100644 --- a/pkg/util/iptables/iptables.go +++ b/pkg/util/iptables/iptables.go @@ -18,8 +18,12 @@ package iptables import ( "fmt" + "regexp" + "strconv" + "strings" "sync" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" utilexec "github.com/GoogleCloudPlatform/kubernetes/pkg/util/exec" "github.com/golang/glog" ) @@ -100,7 +104,7 @@ func (runner *runner) EnsureRule(table Table, chain Chain, args ...string) (bool runner.mu.Lock() defer runner.mu.Unlock() - exists, err := runner.checkRule(fullArgs) + exists, err := runner.checkRule(table, chain, args...) if err != nil { return false, err } @@ -121,7 +125,7 @@ func (runner *runner) DeleteRule(table Table, chain Chain, args ...string) error runner.mu.Lock() defer runner.mu.Unlock() - exists, err := runner.checkRule(fullArgs) + exists, err := runner.checkRule(table, chain, args...) if err != nil { return err } @@ -146,7 +150,47 @@ func (runner *runner) run(op operation, args []string) ([]byte, error) { // Returns (bool, nil) if it was able to check the existence of the rule, or // (, error) if the process of checking failed. -func (runner *runner) checkRule(args []string) (bool, error) { +func (runner *runner) checkRule(table Table, chain Chain, args ...string) (bool, error) { + checkPresent, err := getIptablesHasCheckCommand(runner.exec) + if err != nil { + glog.Warning("Error checking iptables version, assuming version at least 1.4.11: %v", err) + checkPresent = true + } + if checkPresent { + return runner.checkRuleUsingCheck(makeFullArgs(table, chain, args...)) + } else { + return runner.checkRuleWithoutCheck(table, chain, args...) + } +} + +// Executes the rule check without using the "-C" flag, instead parsing iptables-save. +// Present for compatibility with <1.4.11 versions of iptables. +func (runner *runner) checkRuleWithoutCheck(table Table, chain Chain, args ...string) (bool, error) { + out, err := runner.exec.Command("iptables-save", "-t", string(table)).CombinedOutput() + if err != nil { + return false, fmt.Errorf("error checking rule: %s", err) + } + + argset := util.NewStringSet(args...) + + for _, line := range strings.Split(string(out), "\n") { + var fields = strings.Fields(line) + + // Check that this is a rule for the correct chain, and that it has + // the correct number of argument (+2 for "-A ") + if strings.HasPrefix(line, fmt.Sprintf("-A %s", string(chain))) && len(fields) == len(args)+2 { + // TODO: This misses reorderings e.g. "-x foo ! -y bar" will match "! -x foo -y bar" + if util.NewStringSet(fields...).IsSuperset(argset) { + return true, nil + } + } + } + + return false, nil +} + +// Executes the rule check using the "-C" flag +func (runner *runner) checkRuleUsingCheck(args []string) (bool, error) { out, err := runner.run(opCheckRule, args) if err == nil { return true, nil @@ -174,3 +218,69 @@ const ( func makeFullArgs(table Table, chain Chain, args ...string) []string { return append([]string{string(chain), "-t", string(table)}, args...) } + +// Checks if iptables has the "-C" flag +func getIptablesHasCheckCommand(exec utilexec.Interface) (bool, error) { + vstring, err := getIptablesVersionString(exec) + if err != nil { + return false, err + } + + v1, v2, v3, err := extractIptablesVersion(vstring) + if err != nil { + return false, err + } + + return iptablesHasCheckCommand(v1, v2, v3), nil +} + +// getIptablesVersion returns the first three components of the iptables version. +// e.g. "iptables v1.3.66" would return (1, 3, 66, nil) +func extractIptablesVersion(str string) (int, int, int, error) { + versionMatcher := regexp.MustCompile("v([0-9]+)\\.([0-9]+)\\.([0-9]+)") + result := versionMatcher.FindStringSubmatch(str) + if result == nil { + return 0, 0, 0, fmt.Errorf("No iptables version found in string: %s", str) + } + + v1, err := strconv.Atoi(result[1]) + if err != nil { + return 0, 0, 0, err + } + + v2, err := strconv.Atoi(result[2]) + if err != nil { + return 0, 0, 0, err + } + + v3, err := strconv.Atoi(result[3]) + if err != nil { + return 0, 0, 0, err + } + + return v1, v2, v3, nil +} + +// Runs "iptables --version" to get the version string +func getIptablesVersionString(exec utilexec.Interface) (string, error) { + bytes, err := exec.Command("iptables", "--version").CombinedOutput() + if err != nil { + return "", err + } + + return string(bytes), nil +} + +// Checks if an iptables version is after 1.4.11, when --check was added +func iptablesHasCheckCommand(v1 int, v2 int, v3 int) bool { + if v1 > 1 { + return true + } + if v1 == 1 && v2 > 4 { + return true + } + if v1 == 1 && v2 == 4 && v3 >= 11 { + return true + } + return false +} diff --git a/pkg/util/iptables/iptables_test.go b/pkg/util/iptables/iptables_test.go index f027ab351b0..940409a92b5 100644 --- a/pkg/util/iptables/iptables_test.go +++ b/pkg/util/iptables/iptables_test.go @@ -108,13 +108,17 @@ func TestFlushChain(t *testing.T) { func TestEnsureRuleAlreadyExists(t *testing.T) { fcmd := exec.FakeCmd{ CombinedOutputScript: []exec.FakeCombinedOutputAction{ + // iptables version check + func() ([]byte, error) { return []byte("iptables v1.9.22"), nil }, // Success. func() ([]byte, error) { return []byte{}, nil }, }, } fexec := exec.FakeExec{ CommandScript: []exec.FakeCommandAction{ - // The first Command() call is checking the rule. Success of that exec means "done". + // iptables version check + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + // The second Command() call is checking the rule. Success of that exec means "done". func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, }, } @@ -126,10 +130,10 @@ func TestEnsureRuleAlreadyExists(t *testing.T) { if !exists { t.Errorf("expected exists = true") } - if fcmd.CombinedOutputCalls != 1 { - t.Errorf("expected 1 CombinedOutput() call, got %d", fcmd.CombinedOutputCalls) + if fcmd.CombinedOutputCalls != 2 { + t.Errorf("expected 2 CombinedOutput() call, got %d", fcmd.CombinedOutputCalls) } - if !util.NewStringSet(fcmd.CombinedOutputLog[0]...).HasAll("iptables", "-t", "nat", "-C", "OUTPUT", "abc", "123") { + if !util.NewStringSet(fcmd.CombinedOutputLog[1]...).HasAll("iptables", "-t", "nat", "-C", "OUTPUT", "abc", "123") { t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0]) } } @@ -137,6 +141,8 @@ func TestEnsureRuleAlreadyExists(t *testing.T) { func TestEnsureRuleNew(t *testing.T) { fcmd := exec.FakeCmd{ CombinedOutputScript: []exec.FakeCombinedOutputAction{ + // iptables version check + func() ([]byte, error) { return []byte("iptables v1.9.22"), nil }, // Status 1 on the first call. func() ([]byte, error) { return nil, &exec.FakeExitError{1} }, // Success on the second call. @@ -145,7 +151,9 @@ func TestEnsureRuleNew(t *testing.T) { } fexec := exec.FakeExec{ CommandScript: []exec.FakeCommandAction{ - // The first Command() call is checking the rule. Failure of that means create it. + // iptables version check + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + // The second Command() call is checking the rule. Failure of that means create it. func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, }, @@ -158,10 +166,10 @@ func TestEnsureRuleNew(t *testing.T) { if exists { t.Errorf("expected exists = false") } - if fcmd.CombinedOutputCalls != 2 { - t.Errorf("expected 2 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) + if fcmd.CombinedOutputCalls != 3 { + t.Errorf("expected 3 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) } - if !util.NewStringSet(fcmd.CombinedOutputLog[1]...).HasAll("iptables", "-t", "nat", "-A", "OUTPUT", "abc", "123") { + if !util.NewStringSet(fcmd.CombinedOutputLog[2]...).HasAll("iptables", "-t", "nat", "-A", "OUTPUT", "abc", "123") { t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[1]) } } @@ -169,13 +177,17 @@ func TestEnsureRuleNew(t *testing.T) { func TestEnsureRuleErrorChecking(t *testing.T) { fcmd := exec.FakeCmd{ CombinedOutputScript: []exec.FakeCombinedOutputAction{ + // iptables version check + func() ([]byte, error) { return []byte("iptables v1.9.22"), nil }, // Status 2 on the first call. func() ([]byte, error) { return nil, &exec.FakeExitError{2} }, }, } fexec := exec.FakeExec{ CommandScript: []exec.FakeCommandAction{ - // The first Command() call is checking the rule. Failure of that means create it. + // iptables version check + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + // The second Command() call is checking the rule. Failure of that means create it. func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, }, } @@ -184,14 +196,16 @@ func TestEnsureRuleErrorChecking(t *testing.T) { if err == nil { t.Errorf("expected failure") } - if fcmd.CombinedOutputCalls != 1 { - t.Errorf("expected 1 CombinedOutput() call, got %d", fcmd.CombinedOutputCalls) + if fcmd.CombinedOutputCalls != 2 { + t.Errorf("expected 2 CombinedOutput() call, got %d", fcmd.CombinedOutputCalls) } } func TestEnsureRuleErrorCreating(t *testing.T) { fcmd := exec.FakeCmd{ CombinedOutputScript: []exec.FakeCombinedOutputAction{ + // iptables version check + func() ([]byte, error) { return []byte("iptables v1.9.22"), nil }, // Status 1 on the first call. func() ([]byte, error) { return nil, &exec.FakeExitError{1} }, // Status 1 on the second call. @@ -200,7 +214,9 @@ func TestEnsureRuleErrorCreating(t *testing.T) { } fexec := exec.FakeExec{ CommandScript: []exec.FakeCommandAction{ - // The first Command() call is checking the rule. Failure of that means create it. + // iptables version check + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + // The second Command() call is checking the rule. Failure of that means create it. func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, }, @@ -210,21 +226,25 @@ func TestEnsureRuleErrorCreating(t *testing.T) { if err == nil { t.Errorf("expected failure") } - if fcmd.CombinedOutputCalls != 2 { - t.Errorf("expected 2 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) + if fcmd.CombinedOutputCalls != 3 { + t.Errorf("expected 3 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) } } func TestDeleteRuleAlreadyExists(t *testing.T) { fcmd := exec.FakeCmd{ CombinedOutputScript: []exec.FakeCombinedOutputAction{ + // iptables version check + func() ([]byte, error) { return []byte("iptables v1.9.22"), nil }, // Status 1 on the first call. func() ([]byte, error) { return nil, &exec.FakeExitError{1} }, }, } fexec := exec.FakeExec{ CommandScript: []exec.FakeCommandAction{ - // The first Command() call is checking the rule. Failure of that exec means "does not exist". + // iptables version check + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + // The second Command() call is checking the rule. Failure of that exec means "does not exist". func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, }, } @@ -233,10 +253,10 @@ func TestDeleteRuleAlreadyExists(t *testing.T) { if err != nil { t.Errorf("expected success, got %+v", err) } - if fcmd.CombinedOutputCalls != 1 { - t.Errorf("expected 1 CombinedOutput() call, got %d", fcmd.CombinedOutputCalls) + if fcmd.CombinedOutputCalls != 2 { + t.Errorf("expected 2 CombinedOutput() call, got %d", fcmd.CombinedOutputCalls) } - if !util.NewStringSet(fcmd.CombinedOutputLog[0]...).HasAll("iptables", "-t", "nat", "-C", "OUTPUT", "abc", "123") { + if !util.NewStringSet(fcmd.CombinedOutputLog[1]...).HasAll("iptables", "-t", "nat", "-C", "OUTPUT", "abc", "123") { t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0]) } } @@ -244,6 +264,8 @@ func TestDeleteRuleAlreadyExists(t *testing.T) { func TestDeleteRuleNew(t *testing.T) { fcmd := exec.FakeCmd{ CombinedOutputScript: []exec.FakeCombinedOutputAction{ + // iptables version check + func() ([]byte, error) { return []byte("iptables v1.9.22"), nil }, // Success on the first call. func() ([]byte, error) { return []byte{}, nil }, // Success on the second call. @@ -252,7 +274,9 @@ func TestDeleteRuleNew(t *testing.T) { } fexec := exec.FakeExec{ CommandScript: []exec.FakeCommandAction{ - // The first Command() call is checking the rule. Success of that means delete it. + // iptables version check + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + // The second Command() call is checking the rule. Success of that means delete it. func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, }, @@ -262,10 +286,10 @@ func TestDeleteRuleNew(t *testing.T) { if err != nil { t.Errorf("expected success, got %+v", err) } - if fcmd.CombinedOutputCalls != 2 { - t.Errorf("expected 2 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) + if fcmd.CombinedOutputCalls != 3 { + t.Errorf("expected 3 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) } - if !util.NewStringSet(fcmd.CombinedOutputLog[1]...).HasAll("iptables", "-t", "nat", "-D", "OUTPUT", "abc", "123") { + if !util.NewStringSet(fcmd.CombinedOutputLog[2]...).HasAll("iptables", "-t", "nat", "-D", "OUTPUT", "abc", "123") { t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[1]) } } @@ -273,13 +297,17 @@ func TestDeleteRuleNew(t *testing.T) { func TestDeleteRuleErrorChecking(t *testing.T) { fcmd := exec.FakeCmd{ CombinedOutputScript: []exec.FakeCombinedOutputAction{ + // iptables version check + func() ([]byte, error) { return []byte("iptables v1.9.22"), nil }, // Status 2 on the first call. func() ([]byte, error) { return nil, &exec.FakeExitError{2} }, }, } fexec := exec.FakeExec{ CommandScript: []exec.FakeCommandAction{ - // The first Command() call is checking the rule. Failure of that means create it. + // iptables version check + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + // The second Command() call is checking the rule. Failure of that means create it. func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, }, } @@ -288,14 +316,16 @@ func TestDeleteRuleErrorChecking(t *testing.T) { if err == nil { t.Errorf("expected failure") } - if fcmd.CombinedOutputCalls != 1 { - t.Errorf("expected 1 CombinedOutput() call, got %d", fcmd.CombinedOutputCalls) + if fcmd.CombinedOutputCalls != 2 { + t.Errorf("expected 2 CombinedOutput() call, got %d", fcmd.CombinedOutputCalls) } } func TestDeleteRuleErrorCreating(t *testing.T) { fcmd := exec.FakeCmd{ CombinedOutputScript: []exec.FakeCombinedOutputAction{ + // iptables version check + func() ([]byte, error) { return []byte("iptables v1.9.22"), nil }, // Success on the first call. func() ([]byte, error) { return []byte{}, nil }, // Status 1 on the second call. @@ -304,7 +334,9 @@ func TestDeleteRuleErrorCreating(t *testing.T) { } fexec := exec.FakeExec{ CommandScript: []exec.FakeCommandAction{ - // The first Command() call is checking the rule. Success of that means delete it. + // iptables version check + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + // The second Command() call is checking the rule. Success of that means delete it. func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, }, @@ -314,7 +346,176 @@ func TestDeleteRuleErrorCreating(t *testing.T) { if err == nil { t.Errorf("expected failure") } - if fcmd.CombinedOutputCalls != 2 { - t.Errorf("expected 2 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) + if fcmd.CombinedOutputCalls != 3 { + t.Errorf("expected 3 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls) + } +} + +func TestGetIptablesHasCheckCommand(t *testing.T) { + testCases := []struct { + Version string + Err bool + Expected bool + }{ + {"iptables v1.4.7", false, false}, + {"iptables v1.4.11", false, true}, + {"iptables v1.4.19.1", false, true}, + {"iptables v2.0.0", false, true}, + {"total junk", true, false}, + } + + for _, testCase := range testCases { + fcmd := exec.FakeCmd{ + CombinedOutputScript: []exec.FakeCombinedOutputAction{ + func() ([]byte, error) { return []byte(testCase.Version), nil }, + }, + } + fexec := exec.FakeExec{ + CommandScript: []exec.FakeCommandAction{ + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + }, + } + check, err := getIptablesHasCheckCommand(&fexec) + if (err != nil) != testCase.Err { + t.Errorf("Expected error: %v, Got error: %v", testCase.Err, err) + } + if err == nil && testCase.Expected != check { + t.Errorf("Expected result: %v, Got result: %v") + } + } +} + +func TestExtractIptablesVersion(t *testing.T) { + testCases := []struct { + Version string + V1 int + V2 int + V3 int + Err bool + }{ + {"iptables v1.4.7", 1, 4, 7, false}, + {"iptables v1.4.11", 1, 4, 11, false}, + {"iptables v0.2.5", 0, 2, 5, false}, + {"iptables v1.2.3.4.5.6", 1, 2, 3, false}, + {"iptables v1.4", 0, 0, 0, true}, + {"iptables v12345.12345.12345.12344", 12345, 12345, 12345, false}, + {"total junk", 0, 0, 0, true}, + } + + for _, testCase := range testCases { + v1, v2, v3, err := extractIptablesVersion(testCase.Version) + if (err != nil) != testCase.Err { + t.Errorf("Expected error: %v, Got error: %v", testCase.Err, err) + } + if err == nil { + if v1 != testCase.V1 { + t.Errorf("First version number incorrect for string \"%s\", got %d, expected %d", testCase.Version, v1, testCase.V1) + } + if v2 != testCase.V2 { + t.Errorf("Second version number incorrect for string \"%s\", got %d, expected %d", testCase.Version, v2, testCase.V2) + } + if v3 != testCase.V3 { + t.Errorf("Third version number incorrect for string \"%s\", got %d, expected %d", testCase.Version, v3, testCase.V3) + } + } + } +} + +func TestIptablesHasCheckCommand(t *testing.T) { + testCases := []struct { + V1 int + V2 int + V3 int + Result bool + }{ + {0, 55, 55, false}, + {1, 0, 55, false}, + {1, 4, 10, false}, + {1, 4, 11, true}, + {1, 4, 19, true}, + {1, 5, 0, true}, + {2, 0, 0, true}, + } + + for _, testCase := range testCases { + if result := iptablesHasCheckCommand(testCase.V1, testCase.V2, testCase.V3); result != testCase.Result { + t.Errorf("For %d.%d.%d expected %v got %v", testCase.V1, testCase.V2, testCase.V3, testCase.Result, result) + } + } +} + +func TestCheckRuleWithoutCheckPresent(t *testing.T) { + iptables_save_output := `# Generated by iptables-save v1.4.7 on Wed Oct 29 14:56:01 2014 +*nat +:PREROUTING ACCEPT [2136997:197881818] +:POSTROUTING ACCEPT [4284525:258542680] +:OUTPUT ACCEPT [5901660:357267963] +-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER +COMMIT +# Completed on Wed Oct 29 14:56:01 2014` + + fcmd := exec.FakeCmd{ + CombinedOutputScript: []exec.FakeCombinedOutputAction{ + // Success. + func() ([]byte, error) { return []byte(iptables_save_output), nil }, + }, + } + fexec := exec.FakeExec{ + CommandScript: []exec.FakeCommandAction{ + // The first Command() call is checking the rule. Success of that exec means "done". + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + }, + } + runner := &runner{exec: &fexec} + exists, err := runner.checkRuleWithoutCheck(TableNAT, ChainPrerouting, "-m", "addrtype", "-j", "DOCKER", "--dst-type", "LOCAL") + if err != nil { + t.Errorf("expected success, got %+v", err) + } + if !exists { + t.Errorf("expected exists = true") + } + if fcmd.CombinedOutputCalls != 1 { + t.Errorf("expected 1 CombinedOutput() call, got %d", fcmd.CombinedOutputCalls) + } + if !util.NewStringSet(fcmd.CombinedOutputLog[0]...).HasAll("iptables-save", "-t", "nat") { + t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0]) + } +} + +func TestCheckRuleWithoutCheckAbsent(t *testing.T) { + iptables_save_output := `# Generated by iptables-save v1.4.7 on Wed Oct 29 14:56:01 2014 +*nat +:PREROUTING ACCEPT [2136997:197881818] +:POSTROUTING ACCEPT [4284525:258542680] +:OUTPUT ACCEPT [5901660:357267963] +-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER +COMMIT +# Completed on Wed Oct 29 14:56:01 2014` + + fcmd := exec.FakeCmd{ + CombinedOutputScript: []exec.FakeCombinedOutputAction{ + // Success. + func() ([]byte, error) { return []byte(iptables_save_output), nil }, + }, + } + fexec := exec.FakeExec{ + CommandScript: []exec.FakeCommandAction{ + // The first Command() call is checking the rule. Success of that exec means "done". + func(cmd string, args ...string) exec.Cmd { return exec.InitFakeCmd(&fcmd, cmd, args...) }, + }, + } + runner := &runner{exec: &fexec} + exists, err := runner.checkRuleWithoutCheck(TableNAT, ChainPrerouting, "-m", "addrtype", "-j", "DOCKER") + if err != nil { + t.Errorf("expected success, got %+v", err) + } + if exists { + t.Errorf("expected exists = false") + } + if fcmd.CombinedOutputCalls != 1 { + t.Errorf("expected 1 CombinedOutput() call, got %d", fcmd.CombinedOutputCalls) + } + if !util.NewStringSet(fcmd.CombinedOutputLog[0]...).HasAll("iptables-save", "-t", "nat") { + t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0]) } }