mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 17:30:00 +00:00
Clean up conntrack unit tests
Fix the test names to match the functions they are testing. Abstract out the repetitive FakeExec handling. Explicitly specify the "expectCommand" in each one, to make it clearer that that's really the part that we're testing. For everything except TestExec(), test each case with both a "success" result and a "nothing to delete" result from the conntrack binary.
This commit is contained in:
parent
12fc215656
commit
51063cb5c4
@ -27,47 +27,60 @@ import (
|
|||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/utils/exec"
|
"k8s.io/utils/exec"
|
||||||
fakeexec "k8s.io/utils/exec/testing"
|
fakeexec "k8s.io/utils/exec/testing"
|
||||||
utilnet "k8s.io/utils/net"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func familyParamStr(isIPv6 bool) string {
|
var success = func() ([]byte, []byte, error) { return []byte("1 flow entries have been deleted"), nil, nil }
|
||||||
if isIPv6 {
|
var nothingToDelete = func() ([]byte, []byte, error) {
|
||||||
return " -f ipv6"
|
return []byte(""), nil, fmt.Errorf("conntrack v1.4.2 (conntrack-tools): 0 flow entries have been deleted")
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExecConntrackTool(t *testing.T) {
|
func makeCT(result fakeexec.FakeAction) (*fakeexec.FakeExec, *fakeexec.FakeCmd) {
|
||||||
fcmd := fakeexec.FakeCmd{
|
fcmd := &fakeexec.FakeCmd{
|
||||||
CombinedOutputScript: []fakeexec.FakeAction{
|
CombinedOutputScript: []fakeexec.FakeAction{result},
|
||||||
func() ([]byte, []byte, error) { return []byte("1 flow entries have been deleted"), nil, nil },
|
|
||||||
func() ([]byte, []byte, error) { return []byte("1 flow entries have been deleted"), nil, nil },
|
|
||||||
func() ([]byte, []byte, error) {
|
|
||||||
return []byte(""), nil, fmt.Errorf("conntrack v1.4.2 (conntrack-tools): 0 flow entries have been deleted")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
fexec := &fakeexec.FakeExec{
|
fexec := &fakeexec.FakeExec{
|
||||||
CommandScript: []fakeexec.FakeCommandAction{
|
CommandScript: []fakeexec.FakeCommandAction{
|
||||||
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
|
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(fcmd, cmd, args...) },
|
||||||
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
|
|
||||||
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
|
|
||||||
},
|
},
|
||||||
LookPathFunc: func(cmd string) (string, error) { return cmd, nil },
|
LookPathFunc: func(cmd string) (string, error) { return cmd, nil },
|
||||||
}
|
}
|
||||||
|
|
||||||
testCases := [][]string{
|
return fexec, fcmd
|
||||||
{"-L", "-p", "udp"},
|
}
|
||||||
{"-D", "-p", "udp", "-d", "10.0.240.1"},
|
|
||||||
{"-D", "-p", "udp", "--orig-dst", "10.240.0.2", "--dst-nat", "10.0.10.2"},
|
// Gets the command that fexec executed. (If it didn't execute any commands, this will
|
||||||
|
// return "".)
|
||||||
|
func getExecutedCommand(fexec *fakeexec.FakeExec, fcmd *fakeexec.FakeCmd) string {
|
||||||
|
// FakeExec panics if you try to run more commands than you set it up for. So the
|
||||||
|
// only possibilities here are that we ran 1 command or we ran 0.
|
||||||
|
if fexec.CommandCalls != 1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.Join(fcmd.CombinedOutputLog[0], " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExec(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
args []string
|
||||||
|
result fakeexec.FakeAction
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
args: []string{"-D", "-p", "udp", "-d", "10.0.240.1"},
|
||||||
|
result: success,
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"-D", "-p", "udp", "--orig-dst", "10.240.0.2", "--dst-nat", "10.0.10.2"},
|
||||||
|
result: nothingToDelete,
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
expectErr := []bool{false, false, true}
|
for _, tc := range testCases {
|
||||||
|
fexec, fcmd := makeCT(tc.result)
|
||||||
for i := range testCases {
|
err := Exec(fexec, tc.args...)
|
||||||
err := Exec(fexec, testCases[i]...)
|
if tc.expectErr {
|
||||||
|
|
||||||
if expectErr[i] {
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("expected err, got %v", err)
|
t.Errorf("expected err, got %v", err)
|
||||||
}
|
}
|
||||||
@ -77,204 +90,177 @@ func TestExecConntrackTool(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
execCmd := strings.Join(fcmd.CombinedOutputLog[i], " ")
|
execCmd := getExecutedCommand(fexec, fcmd)
|
||||||
expectCmd := fmt.Sprintf("%s %s", "conntrack", strings.Join(testCases[i], " "))
|
expectCmd := "conntrack " + strings.Join(tc.args, " ")
|
||||||
|
|
||||||
if execCmd != expectCmd {
|
if execCmd != expectCmd {
|
||||||
t.Errorf("expect execute command: %s, but got: %s", expectCmd, execCmd)
|
t.Errorf("expect execute command: %s, but got: %s", expectCmd, execCmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClearUDPConntrackForIP(t *testing.T) {
|
func TestClearEntriesForIP(t *testing.T) {
|
||||||
fcmd := fakeexec.FakeCmd{
|
|
||||||
CombinedOutputScript: []fakeexec.FakeAction{
|
|
||||||
func() ([]byte, []byte, error) { return []byte("1 flow entries have been deleted"), nil, nil },
|
|
||||||
func() ([]byte, []byte, error) { return []byte("1 flow entries have been deleted"), nil, nil },
|
|
||||||
func() ([]byte, []byte, error) {
|
|
||||||
return []byte(""), nil, fmt.Errorf("conntrack v1.4.2 (conntrack-tools): 0 flow entries have been deleted")
|
|
||||||
},
|
|
||||||
func() ([]byte, []byte, error) { return []byte("1 flow entries have been deleted"), nil, nil },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
fexec := &fakeexec.FakeExec{
|
|
||||||
CommandScript: []fakeexec.FakeCommandAction{
|
|
||||||
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
|
|
||||||
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
|
|
||||||
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
|
|
||||||
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
|
|
||||||
},
|
|
||||||
LookPathFunc: func(cmd string) (string, error) { return cmd, nil },
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
ip string
|
ip string
|
||||||
|
|
||||||
|
expectCommand string
|
||||||
}{
|
}{
|
||||||
{"IPv4 success", "10.240.0.3"},
|
{
|
||||||
{"IPv4 success", "10.240.0.5"},
|
name: "IPv4",
|
||||||
{"IPv4 simulated error", "10.240.0.4"},
|
ip: "10.240.0.3",
|
||||||
{"IPv6 success", "2001:db8::10"},
|
|
||||||
|
expectCommand: "conntrack -D --orig-dst 10.240.0.3 -p udp",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6",
|
||||||
|
ip: "2001:db8::10",
|
||||||
|
|
||||||
|
expectCommand: "conntrack -D --orig-dst 2001:db8::10 -p udp -f ipv6",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
svcCount := 0
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
fexec, fcmd := makeCT(success)
|
||||||
if err := ClearEntriesForIP(fexec, tc.ip, v1.ProtocolUDP); err != nil {
|
if err := ClearEntriesForIP(fexec, tc.ip, v1.ProtocolUDP); err != nil {
|
||||||
t.Errorf("%s test case:, Unexpected error: %v", tc.name, err)
|
t.Errorf("%s/success: Unexpected error: %v", tc.name, err)
|
||||||
}
|
}
|
||||||
expectCommand := fmt.Sprintf("conntrack -D --orig-dst %s -p udp", tc.ip) + familyParamStr(utilnet.IsIPv6String(tc.ip))
|
execCommand := getExecutedCommand(fexec, fcmd)
|
||||||
execCommand := strings.Join(fcmd.CombinedOutputLog[svcCount], " ")
|
if tc.expectCommand != execCommand {
|
||||||
if expectCommand != execCommand {
|
t.Errorf("%s/success: Expect command: %s, but executed %s", tc.name, tc.expectCommand, execCommand)
|
||||||
t.Errorf("%s test case: Expect command: %s, but executed %s", tc.name, expectCommand, execCommand)
|
}
|
||||||
|
|
||||||
|
fexec, _ = makeCT(nothingToDelete)
|
||||||
|
if err := ClearEntriesForIP(fexec, tc.ip, v1.ProtocolUDP); err != nil {
|
||||||
|
t.Errorf("%s/nothing to delete: Unexpected error: %v", tc.name, err)
|
||||||
}
|
}
|
||||||
svcCount++
|
|
||||||
}
|
|
||||||
if svcCount != fexec.CommandCalls {
|
|
||||||
t.Errorf("Expect command executed %d times, but got %d", svcCount, fexec.CommandCalls)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClearUDPConntrackForPort(t *testing.T) {
|
func TestClearEntriesForPort(t *testing.T) {
|
||||||
fcmd := fakeexec.FakeCmd{
|
|
||||||
CombinedOutputScript: []fakeexec.FakeAction{
|
|
||||||
func() ([]byte, []byte, error) { return []byte("1 flow entries have been deleted"), nil, nil },
|
|
||||||
func() ([]byte, []byte, error) {
|
|
||||||
return []byte(""), nil, fmt.Errorf("conntrack v1.4.2 (conntrack-tools): 0 flow entries have been deleted")
|
|
||||||
},
|
|
||||||
func() ([]byte, []byte, error) { return []byte("1 flow entries have been deleted"), nil, nil },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
fexec := &fakeexec.FakeExec{
|
|
||||||
CommandScript: []fakeexec.FakeCommandAction{
|
|
||||||
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
|
|
||||||
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
|
|
||||||
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
|
|
||||||
},
|
|
||||||
LookPathFunc: func(cmd string) (string, error) { return cmd, nil },
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
port int
|
port int
|
||||||
isIPv6 bool
|
isIPv6 bool
|
||||||
|
|
||||||
|
expectCommand string
|
||||||
}{
|
}{
|
||||||
{"IPv4, no error", 8080, false},
|
{
|
||||||
{"IPv4, simulated error", 9090, false},
|
name: "IPv4",
|
||||||
{"IPv6, no error", 6666, true},
|
port: 8080,
|
||||||
|
isIPv6: false,
|
||||||
|
|
||||||
|
expectCommand: "conntrack -D -p udp --dport 8080",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6",
|
||||||
|
port: 6666,
|
||||||
|
isIPv6: true,
|
||||||
|
|
||||||
|
expectCommand: "conntrack -D -p udp --dport 6666 -f ipv6",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
svcCount := 0
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
fexec, fcmd := makeCT(success)
|
||||||
err := ClearEntriesForPort(fexec, tc.port, tc.isIPv6, v1.ProtocolUDP)
|
err := ClearEntriesForPort(fexec, tc.port, tc.isIPv6, v1.ProtocolUDP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("%s test case: Unexpected error: %v", tc.name, err)
|
t.Errorf("%s/success: Unexpected error: %v", tc.name, err)
|
||||||
}
|
}
|
||||||
expectCommand := fmt.Sprintf("conntrack -D -p udp --dport %d", tc.port) + familyParamStr(tc.isIPv6)
|
execCommand := getExecutedCommand(fexec, fcmd)
|
||||||
execCommand := strings.Join(fcmd.CombinedOutputLog[svcCount], " ")
|
if tc.expectCommand != execCommand {
|
||||||
if expectCommand != execCommand {
|
t.Errorf("%s/success: Expect command: %s, but executed %s", tc.name, tc.expectCommand, execCommand)
|
||||||
t.Errorf("%s test case: Expect command: %s, but executed %s", tc.name, expectCommand, execCommand)
|
}
|
||||||
|
|
||||||
|
fexec, _ = makeCT(nothingToDelete)
|
||||||
|
err = ClearEntriesForPort(fexec, tc.port, tc.isIPv6, v1.ProtocolUDP)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s/nothing to delete: Unexpected error: %v", tc.name, err)
|
||||||
}
|
}
|
||||||
svcCount++
|
|
||||||
}
|
|
||||||
if svcCount != fexec.CommandCalls {
|
|
||||||
t.Errorf("Expect command executed %d times, but got %d", svcCount, fexec.CommandCalls)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteUDPConnections(t *testing.T) {
|
func TestClearEntriesForNAT(t *testing.T) {
|
||||||
fcmd := fakeexec.FakeCmd{
|
|
||||||
CombinedOutputScript: []fakeexec.FakeAction{
|
|
||||||
func() ([]byte, []byte, error) { return []byte("1 flow entries have been deleted"), nil, nil },
|
|
||||||
func() ([]byte, []byte, error) {
|
|
||||||
return []byte(""), nil, fmt.Errorf("conntrack v1.4.2 (conntrack-tools): 0 flow entries have been deleted")
|
|
||||||
},
|
|
||||||
func() ([]byte, []byte, error) { return []byte("1 flow entries have been deleted"), nil, nil },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
fexec := &fakeexec.FakeExec{
|
|
||||||
CommandScript: []fakeexec.FakeCommandAction{
|
|
||||||
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
|
|
||||||
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
|
|
||||||
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
|
|
||||||
},
|
|
||||||
LookPathFunc: func(cmd string) (string, error) { return cmd, nil },
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
origin string
|
origin string
|
||||||
dest string
|
dest string
|
||||||
|
|
||||||
|
expectCommand string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "IPv4 success",
|
name: "IPv4",
|
||||||
origin: "1.2.3.4",
|
origin: "1.2.3.4",
|
||||||
dest: "10.20.30.40",
|
dest: "10.20.30.40",
|
||||||
|
|
||||||
|
expectCommand: "conntrack -D --orig-dst 1.2.3.4 --dst-nat 10.20.30.40 -p udp",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "IPv4 simulated failure",
|
name: "IPv6",
|
||||||
origin: "2.3.4.5",
|
|
||||||
dest: "20.30.40.50",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "IPv6 success",
|
|
||||||
origin: "fd00::600d:f00d",
|
origin: "fd00::600d:f00d",
|
||||||
dest: "2001:db8::5",
|
dest: "2001:db8::5",
|
||||||
|
|
||||||
|
expectCommand: "conntrack -D --orig-dst fd00::600d:f00d --dst-nat 2001:db8::5 -p udp -f ipv6",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
svcCount := 0
|
|
||||||
for i, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
fexec, fcmd := makeCT(success)
|
||||||
err := ClearEntriesForNAT(fexec, tc.origin, tc.dest, v1.ProtocolUDP)
|
err := ClearEntriesForNAT(fexec, tc.origin, tc.dest, v1.ProtocolUDP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("%s test case: unexpected error: %v", tc.name, err)
|
t.Errorf("%s/success: unexpected error: %v", tc.name, err)
|
||||||
}
|
}
|
||||||
expectCommand := fmt.Sprintf("conntrack -D --orig-dst %s --dst-nat %s -p udp", tc.origin, tc.dest) + familyParamStr(utilnet.IsIPv6String(tc.origin))
|
execCommand := getExecutedCommand(fexec, fcmd)
|
||||||
execCommand := strings.Join(fcmd.CombinedOutputLog[i], " ")
|
if tc.expectCommand != execCommand {
|
||||||
if expectCommand != execCommand {
|
t.Errorf("%s/success: Expect command: %s, but executed %s", tc.name, tc.expectCommand, execCommand)
|
||||||
t.Errorf("%s test case: Expect command: %s, but executed %s", tc.name, expectCommand, execCommand)
|
}
|
||||||
|
|
||||||
|
fexec, _ = makeCT(nothingToDelete)
|
||||||
|
err = ClearEntriesForNAT(fexec, tc.origin, tc.dest, v1.ProtocolUDP)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s/nothing to delete: unexpected error: %v", tc.name, err)
|
||||||
}
|
}
|
||||||
svcCount++
|
|
||||||
}
|
|
||||||
if svcCount != fexec.CommandCalls {
|
|
||||||
t.Errorf("Expect command executed %d times, but got %d", svcCount, fexec.CommandCalls)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClearUDPConntrackForPortNAT(t *testing.T) {
|
func TestClearEntriesForPortNAT(t *testing.T) {
|
||||||
fcmd := fakeexec.FakeCmd{
|
|
||||||
CombinedOutputScript: []fakeexec.FakeAction{
|
|
||||||
func() ([]byte, []byte, error) { return []byte("1 flow entries have been deleted"), nil, nil },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
fexec := &fakeexec.FakeExec{
|
|
||||||
CommandScript: []fakeexec.FakeCommandAction{
|
|
||||||
func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
|
|
||||||
},
|
|
||||||
LookPathFunc: func(cmd string) (string, error) { return cmd, nil },
|
|
||||||
}
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
port int
|
port int
|
||||||
dest string
|
dest string
|
||||||
|
|
||||||
|
expectCommand string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "IPv4 success",
|
name: "IPv4",
|
||||||
port: 30211,
|
port: 30211,
|
||||||
dest: "1.2.3.4",
|
dest: "1.2.3.4",
|
||||||
|
|
||||||
|
expectCommand: "conntrack -D -p udp --dport 30211 --dst-nat 1.2.3.4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "IPv6",
|
||||||
|
port: 30212,
|
||||||
|
dest: "2600:5200::7800",
|
||||||
|
|
||||||
|
expectCommand: "conntrack -D -p udp --dport 30212 --dst-nat 2600:5200::7800 -f ipv6",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
svcCount := 0
|
|
||||||
for i, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
fexec, fcmd := makeCT(success)
|
||||||
err := ClearEntriesForPortNAT(fexec, tc.dest, tc.port, v1.ProtocolUDP)
|
err := ClearEntriesForPortNAT(fexec, tc.dest, tc.port, v1.ProtocolUDP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("%s test case: unexpected error: %v", tc.name, err)
|
t.Errorf("%s/success: unexpected error: %v", tc.name, err)
|
||||||
}
|
}
|
||||||
expectCommand := fmt.Sprintf("conntrack -D -p udp --dport %d --dst-nat %s", tc.port, tc.dest) + familyParamStr(utilnet.IsIPv6String(tc.dest))
|
execCommand := getExecutedCommand(fexec, fcmd)
|
||||||
execCommand := strings.Join(fcmd.CombinedOutputLog[i], " ")
|
if tc.expectCommand != execCommand {
|
||||||
if expectCommand != execCommand {
|
t.Errorf("%s/success: Expect command: %s, but executed %s", tc.name, tc.expectCommand, execCommand)
|
||||||
t.Errorf("%s test case: Expect command: %s, but executed %s", tc.name, expectCommand, execCommand)
|
}
|
||||||
|
|
||||||
|
fexec, _ = makeCT(nothingToDelete)
|
||||||
|
err = ClearEntriesForPortNAT(fexec, tc.dest, tc.port, v1.ProtocolUDP)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s/nothing to delete: unexpected error: %v", tc.name, err)
|
||||||
}
|
}
|
||||||
svcCount++
|
|
||||||
}
|
|
||||||
if svcCount != fexec.CommandCalls {
|
|
||||||
t.Errorf("Expect command executed %d times, but got %d", svcCount, fexec.CommandCalls)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user