From 84f7d1e636965cebaae000b9a1550123567ec5c4 Mon Sep 17 00:00:00 2001 From: jay vyas Date: Sun, 9 Aug 2020 16:32:20 -0400 Subject: [PATCH 1/7] e2e test intra-pod breadth first logging and summary gofmt --- test/e2e/common/networking.go | 17 ++++++++++++++++- test/e2e/framework/network/utils.go | 20 +++++++++++--------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/test/e2e/common/networking.go b/test/e2e/common/networking.go index 97010b2d734..fa0344b54d9 100644 --- a/test/e2e/common/networking.go +++ b/test/e2e/common/networking.go @@ -39,8 +39,23 @@ var _ = ginkgo.Describe("[sig-network] Networking", func() { */ framework.ConformanceIt("should function for intra-pod communication: http [NodeConformance]", func() { config := e2enetwork.NewCoreNetworkingTestConfig(f, false) + + // Extra debugging info since this is the most common diagnostic for failing clusters, and is a Conformance test. + errors := []error{} for _, endpointPod := range config.EndpointPods { - config.DialFromTestContainer("http", endpointPod.Status.PodIP, e2enetwork.EndpointHTTPPort, config.MaxTries, 0, sets.NewString(endpointPod.Name)) + if err := config.DialFromTestContainer("http", endpointPod.Status.PodIP, e2enetwork.EndpointHTTPPort, config.MaxTries, 0, sets.NewString(endpointPod.Name)); err != nil { + errors = append(errors, err) + framework.Logf("Was able to reach %v on %v ", endpointPod.Status.PodIP, endpointPod.Status.HostIP) + } else { + framework.Logf("Warning: Test failure (%v) will occur due to %v", len(errors)+1, err) // convenient error message for diagnosis... how many pods failed, and on what hosts? + } + } + if len(errors) == 0 { + framework.Logf("Pod polling failure summary:") + for _, e := range errors { + framework.Logf("%v", e) + } + framework.Failf("Failed due to %v errors polling %v pods", errors, len(config.EndpointPods)) } }) diff --git a/test/e2e/framework/network/utils.go b/test/e2e/framework/network/utils.go index c66cb3cf47a..7a84afb832e 100644 --- a/test/e2e/framework/network/utils.go +++ b/test/e2e/framework/network/utils.go @@ -167,17 +167,17 @@ type NetexecDialResponse struct { } // DialFromEndpointContainer executes a curl via kubectl exec in an endpoint container. -func (config *NetworkingTestConfig) DialFromEndpointContainer(protocol, targetIP string, targetPort, maxTries, minTries int, expectedEps sets.String) { - config.DialFromContainer(protocol, echoHostname, config.EndpointPods[0].Status.PodIP, targetIP, EndpointHTTPPort, targetPort, maxTries, minTries, expectedEps) +func (config *NetworkingTestConfig) DialFromEndpointContainer(protocol, targetIP string, targetPort, maxTries, minTries int, expectedEps sets.String) error { + return config.DialFromContainer(protocol, echoHostname, config.EndpointPods[0].Status.PodIP, targetIP, EndpointHTTPPort, targetPort, maxTries, minTries, expectedEps) } // DialFromTestContainer executes a curl via kubectl exec in a test container. -func (config *NetworkingTestConfig) DialFromTestContainer(protocol, targetIP string, targetPort, maxTries, minTries int, expectedEps sets.String) { - config.DialFromContainer(protocol, echoHostname, config.TestContainerPod.Status.PodIP, targetIP, testContainerHTTPPort, targetPort, maxTries, minTries, expectedEps) +func (config *NetworkingTestConfig) DialFromTestContainer(protocol, targetIP string, targetPort, maxTries, minTries int, expectedEps sets.String) error { + return config.DialFromContainer(protocol, echoHostname, config.TestContainerPod.Status.PodIP, targetIP, testContainerHTTPPort, targetPort, maxTries, minTries, expectedEps) } // DialEchoFromTestContainer executes a curl via kubectl exec in a test container. The response is expected to match the echoMessage. -func (config *NetworkingTestConfig) DialEchoFromTestContainer(protocol, targetIP string, targetPort, maxTries, minTries int, echoMessage string) { +func (config *NetworkingTestConfig) DialEchoFromTestContainer(protocol, targetIP string, targetPort, maxTries, minTries int, echoMessage string) error { expectedResponse := sets.NewString() expectedResponse.Insert(echoMessage) var dialCommand string @@ -191,7 +191,7 @@ func (config *NetworkingTestConfig) DialEchoFromTestContainer(protocol, targetIP } else { dialCommand = fmt.Sprintf("echo%%20%s", echoMessage) } - config.DialFromContainer(protocol, dialCommand, config.TestContainerPod.Status.PodIP, targetIP, testContainerHTTPPort, targetPort, maxTries, minTries, expectedResponse) + return config.DialFromContainer(protocol, dialCommand, config.TestContainerPod.Status.PodIP, targetIP, testContainerHTTPPort, targetPort, maxTries, minTries, expectedResponse) } // diagnoseMissingEndpoints prints debug information about the endpoints that @@ -248,7 +248,9 @@ func makeCURLDialCommand(ipPort, dialCmd, protocol, targetIP string, targetPort // maxTries == minTries will confirm that we see the expected endpoints and no // more for maxTries. Use this if you want to eg: fail a readiness check on a // pod and confirm it doesn't show up as an endpoint. -func (config *NetworkingTestConfig) DialFromContainer(protocol, dialCommand, containerIP, targetIP string, containerHTTPPort, targetPort, maxTries, minTries int, expectedResponses sets.String) { +// +// returns nil if no error, or error message if failed after trying maxTries. +func (config *NetworkingTestConfig) DialFromContainer(protocol, dialCommand, containerIP, targetIP string, containerHTTPPort, targetPort, maxTries, minTries int, expectedResponses sets.String) error { ipPort := net.JoinHostPort(containerIP, strconv.Itoa(containerHTTPPort)) cmd := makeCURLDialCommand(ipPort, dialCommand, protocol, targetIP, targetPort) @@ -273,7 +275,7 @@ func (config *NetworkingTestConfig) DialFromContainer(protocol, dialCommand, con // Check against i+1 so we exit if minTries == maxTries. if (responses.Equal(expectedResponses) || responses.Len() == 0 && expectedResponses.Len() == 0) && i+1 >= minTries { - return + return nil } // TODO: get rid of this delay #36281 time.Sleep(hitEndpointRetryDelay) @@ -282,7 +284,7 @@ func (config *NetworkingTestConfig) DialFromContainer(protocol, dialCommand, con if dialCommand == echoHostname { config.diagnoseMissingEndpoints(responses) } - framework.Failf("Failed to find expected responses:\nTries %d\nCommand %v\nretrieved %v\nexpected %v\n", maxTries, cmd, responses, expectedResponses) + return fmt.Errorf("Did not find expected responses... \nTries %d\nCommand %v\nretrieved %v\nexpected %v\n", maxTries, cmd, responses, expectedResponses) } // GetEndpointsFromTestContainer executes a curl via kubectl exec in a test container. From 35f5e8e66f4dfd2ea6ddd5a3146a41bd47b219ce Mon Sep 17 00:00:00 2001 From: jay vyas Date: Sun, 9 Aug 2020 16:45:51 -0400 Subject: [PATCH 2/7] minor logic fix --- test/e2e/common/networking.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/common/networking.go b/test/e2e/common/networking.go index fa0344b54d9..1dde2d221db 100644 --- a/test/e2e/common/networking.go +++ b/test/e2e/common/networking.go @@ -50,12 +50,12 @@ var _ = ginkgo.Describe("[sig-network] Networking", func() { framework.Logf("Warning: Test failure (%v) will occur due to %v", len(errors)+1, err) // convenient error message for diagnosis... how many pods failed, and on what hosts? } } - if len(errors) == 0 { + if len(errors) > 0 { framework.Logf("Pod polling failure summary:") for _, e := range errors { framework.Logf("%v", e) } - framework.Failf("Failed due to %v errors polling %v pods", errors, len(config.EndpointPods)) + framework.Failf("Failed due to %v errors polling %v pods", len(errors), len(config.EndpointPods)) } }) From ccbfa841734fcf416bce75ad7ab9af1e894d9b6c Mon Sep 17 00:00:00 2001 From: jay vyas Date: Mon, 10 Aug 2020 10:52:50 -0400 Subject: [PATCH 3/7] golint flip logging messages run pod->node conformance tests the same way --- test/e2e/common/networking.go | 50 +++++++++++++---------------- test/e2e/framework/network/utils.go | 3 +- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/test/e2e/common/networking.go b/test/e2e/common/networking.go index 1dde2d221db..3ef721cb84a 100644 --- a/test/e2e/common/networking.go +++ b/test/e2e/common/networking.go @@ -28,6 +28,25 @@ var _ = ginkgo.Describe("[sig-network] Networking", func() { ginkgo.Describe("Granular Checks: Pods", func() { + checkNodeConnectivity := func(config *e2enetwork.NetworkingTestConfig, protocol string, port int) { + errors := []error{} + for _, endpointPod := range config.EndpointPods { + if err := config.DialFromTestContainer(protocol, endpointPod.Status.PodIP, port, config.MaxTries, 0, sets.NewString(endpointPod.Name)); err != nil { + errors = append(errors, err) + framework.Logf("Warning: Test failure (%v) will occur due to %v", len(errors)+1, err) // convenient error message for diagnosis... how many pods failed, and on what hosts? + } else { + framework.Logf("Was able to reach %v on %v ", endpointPod.Status.PodIP, endpointPod.Status.HostIP) + } + } + if len(errors) > 0 { + framework.Logf("Pod polling failure summary:") + for _, e := range errors { + framework.Logf("%v", e) + } + framework.Failf("Failed due to %v errors polling %v pods", len(errors), len(config.EndpointPods)) + } + } + // Try to hit all endpoints through a test container, retry 5 times, // expect exactly one unique hostname. Each of these endpoints reports // its own hostname. @@ -39,24 +58,7 @@ var _ = ginkgo.Describe("[sig-network] Networking", func() { */ framework.ConformanceIt("should function for intra-pod communication: http [NodeConformance]", func() { config := e2enetwork.NewCoreNetworkingTestConfig(f, false) - - // Extra debugging info since this is the most common diagnostic for failing clusters, and is a Conformance test. - errors := []error{} - for _, endpointPod := range config.EndpointPods { - if err := config.DialFromTestContainer("http", endpointPod.Status.PodIP, e2enetwork.EndpointHTTPPort, config.MaxTries, 0, sets.NewString(endpointPod.Name)); err != nil { - errors = append(errors, err) - framework.Logf("Was able to reach %v on %v ", endpointPod.Status.PodIP, endpointPod.Status.HostIP) - } else { - framework.Logf("Warning: Test failure (%v) will occur due to %v", len(errors)+1, err) // convenient error message for diagnosis... how many pods failed, and on what hosts? - } - } - if len(errors) > 0 { - framework.Logf("Pod polling failure summary:") - for _, e := range errors { - framework.Logf("%v", e) - } - framework.Failf("Failed due to %v errors polling %v pods", len(errors), len(config.EndpointPods)) - } + checkNodeConnectivity(config, "http", e2enetwork.EndpointHTTPPort) }) /* @@ -67,9 +69,7 @@ var _ = ginkgo.Describe("[sig-network] Networking", func() { */ framework.ConformanceIt("should function for intra-pod communication: udp [NodeConformance]", func() { config := e2enetwork.NewCoreNetworkingTestConfig(f, false) - for _, endpointPod := range config.EndpointPods { - config.DialFromTestContainer("udp", endpointPod.Status.PodIP, e2enetwork.EndpointUDPPort, config.MaxTries, 0, sets.NewString(endpointPod.Name)) - } + checkNodeConnectivity(config, "udp", e2enetwork.EndpointUDPPort) }) /* @@ -81,9 +81,7 @@ var _ = ginkgo.Describe("[sig-network] Networking", func() { */ framework.ConformanceIt("should function for node-pod communication: http [LinuxOnly] [NodeConformance]", func() { config := e2enetwork.NewCoreNetworkingTestConfig(f, true) - for _, endpointPod := range config.EndpointPods { - config.DialFromNode("http", endpointPod.Status.PodIP, e2enetwork.EndpointHTTPPort, config.MaxTries, 0, sets.NewString(endpointPod.Name)) - } + checkNodeConnectivity(config, "http", e2enetwork.EndpointHTTPPort) }) /* @@ -95,9 +93,7 @@ var _ = ginkgo.Describe("[sig-network] Networking", func() { */ framework.ConformanceIt("should function for node-pod communication: udp [LinuxOnly] [NodeConformance]", func() { config := e2enetwork.NewCoreNetworkingTestConfig(f, true) - for _, endpointPod := range config.EndpointPods { - config.DialFromNode("udp", endpointPod.Status.PodIP, e2enetwork.EndpointUDPPort, config.MaxTries, 0, sets.NewString(endpointPod.Name)) - } + checkNodeConnectivity(config, "udp", e2enetwork.EndpointUDPPort) }) }) }) diff --git a/test/e2e/framework/network/utils.go b/test/e2e/framework/network/utils.go index 7a84afb832e..d8f75722b96 100644 --- a/test/e2e/framework/network/utils.go +++ b/test/e2e/framework/network/utils.go @@ -280,11 +280,10 @@ func (config *NetworkingTestConfig) DialFromContainer(protocol, dialCommand, con // TODO: get rid of this delay #36281 time.Sleep(hitEndpointRetryDelay) } - if dialCommand == echoHostname { config.diagnoseMissingEndpoints(responses) } - return fmt.Errorf("Did not find expected responses... \nTries %d\nCommand %v\nretrieved %v\nexpected %v\n", maxTries, cmd, responses, expectedResponses) + return fmt.Errorf("did not find expected responses... \nTries %d\nCommand %v\nretrieved %v\nexpected %v", maxTries, cmd, responses, expectedResponses) } // GetEndpointsFromTestContainer executes a curl via kubectl exec in a test container. From 56e2adbd791ed271bad1843657cad6889f58fcd9 Mon Sep 17 00:00:00 2001 From: jay vyas Date: Tue, 11 Aug 2020 11:32:46 -0400 Subject: [PATCH 4/7] implement breadth first try --- test/e2e/common/networking.go | 44 +++++++++++++++++++++-------- test/e2e/framework/network/utils.go | 6 +++- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/test/e2e/common/networking.go b/test/e2e/common/networking.go index 3ef721cb84a..f7b1b1cba90 100644 --- a/test/e2e/common/networking.go +++ b/test/e2e/common/networking.go @@ -18,6 +18,7 @@ package common import ( "github.com/onsi/ginkgo" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/kubernetes/test/e2e/framework" e2enetwork "k8s.io/kubernetes/test/e2e/framework/network" @@ -29,21 +30,42 @@ var _ = ginkgo.Describe("[sig-network] Networking", func() { ginkgo.Describe("Granular Checks: Pods", func() { checkNodeConnectivity := func(config *e2enetwork.NetworkingTestConfig, protocol string, port int) { - errors := []error{} + // breadth first poll to quickly estimate failure. + failedPodsByHost := map[string][]*v1.Pod{} + // First time, we'll quickly try all pods, breadth first. for _, endpointPod := range config.EndpointPods { - if err := config.DialFromTestContainer(protocol, endpointPod.Status.PodIP, port, config.MaxTries, 0, sets.NewString(endpointPod.Name)); err != nil { - errors = append(errors, err) - framework.Logf("Warning: Test failure (%v) will occur due to %v", len(errors)+1, err) // convenient error message for diagnosis... how many pods failed, and on what hosts? - } else { - framework.Logf("Was able to reach %v on %v ", endpointPod.Status.PodIP, endpointPod.Status.HostIP) + framework.Logf("Breadth first check of %v on host %v...", endpointPod.Status.PodIP, endpointPod.Status.HostIP) + if err := config.DialFromTestContainer(protocol, endpointPod.Status.PodIP, port, 1, 0, sets.NewString(endpointPod.Name)); err != nil { + if _, ok := failedPodsByHost[endpointPod.Status.HostIP]; !ok { + failedPodsByHost[endpointPod.Status.HostIP] = []*v1.Pod{} + } + failedPodsByHost[endpointPod.Status.HostIP] = append(failedPodsByHost[endpointPod.Status.HostIP], endpointPod) + framework.Logf("...failed...will try again in next pass") } } - if len(errors) > 0 { - framework.Logf("Pod polling failure summary:") - for _, e := range errors { - framework.Logf("%v", e) + errors := []error{} + // Second time, we pass through pods more carefully... + framework.Logf("Going to retry %v out of %v pods....", len(failedPodsByHost), len(config.EndpointPods)) + for host, failedPods := range failedPodsByHost { + framework.Logf("Doublechecking %v pods in host %v which werent seen the first time.", len(failedPods), host) + for _, endpointPod := range failedPods { + framework.Logf("Now attempting to probe pod [[[ %v ]]]", endpointPod.Status.PodIP) + if err := config.DialFromTestContainer(protocol, endpointPod.Status.PodIP, port, config.MaxTries, 0, sets.NewString(endpointPod.Name)); err != nil { + errors = append(errors, err) + framework.Logf("Warning: Test failure (%v) will occur due to %v", len(errors), err) // convenient error message for diagnosis... how many pods failed, and on what hosts? + } else { + framework.Logf("Was able to reach %v on %v ", endpointPod.Status.PodIP, endpointPod.Status.HostIP) + } + framework.Logf("... Done probing pod [[[ %v ]]]", endpointPod.Status.PodIP) } - framework.Failf("Failed due to %v errors polling %v pods", len(errors), len(config.EndpointPods)) + framework.Logf("succeeded at polling %v out of %v connections", len(config.EndpointPods)-len(errors), len(config.EndpointPods)) + } + if len(errors) > 0 { + framework.Logf("pod polling failure summary:") + for _, e := range errors { + framework.Logf("Collected error: %v", e) + } + framework.Failf("failed, %v out of %v connections failed", len(errors), len(config.EndpointPods)) } } diff --git a/test/e2e/framework/network/utils.go b/test/e2e/framework/network/utils.go index d8f75722b96..ccca5abf349 100644 --- a/test/e2e/framework/network/utils.go +++ b/test/e2e/framework/network/utils.go @@ -275,6 +275,7 @@ func (config *NetworkingTestConfig) DialFromContainer(protocol, dialCommand, con // Check against i+1 so we exit if minTries == maxTries. if (responses.Equal(expectedResponses) || responses.Len() == 0 && expectedResponses.Len() == 0) && i+1 >= minTries { + framework.Logf("reached %v after %v/%v tries", targetIP, i, maxTries) return nil } // TODO: get rid of this delay #36281 @@ -283,7 +284,10 @@ func (config *NetworkingTestConfig) DialFromContainer(protocol, dialCommand, con if dialCommand == echoHostname { config.diagnoseMissingEndpoints(responses) } - return fmt.Errorf("did not find expected responses... \nTries %d\nCommand %v\nretrieved %v\nexpected %v", maxTries, cmd, responses, expectedResponses) + returnMsg := fmt.Errorf("did not find expected responses... \nTries %d\nCommand %v\nretrieved %v\nexpected %v", maxTries, cmd, responses, expectedResponses) + framework.Logf("encountered error during dial (%v)", returnMsg) + return returnMsg + } // GetEndpointsFromTestContainer executes a curl via kubectl exec in a test container. From 9ecf96cb4620ef18a3ff587d3cbdd4cdd599c1a1 Mon Sep 17 00:00:00 2001 From: jay vyas Date: Tue, 11 Aug 2020 12:28:08 -0400 Subject: [PATCH 5/7] Add maxTries logging statement, otherwise the numbers are really hard to interpret --- test/e2e/framework/network/utils.go | 1 + 1 file changed, 1 insertion(+) diff --git a/test/e2e/framework/network/utils.go b/test/e2e/framework/network/utils.go index ccca5abf349..ce289f839c1 100644 --- a/test/e2e/framework/network/utils.go +++ b/test/e2e/framework/network/utils.go @@ -681,6 +681,7 @@ func (config *NetworkingTestConfig) setupCore(selector map[string]string) { epCount := len(config.EndpointPods) config.MaxTries = epCount*epCount + testTries + framework.Logf("Setting MaxTries for pod polling to %v for networking test based on endpoint count %v", config.MaxTries, epCount) } // setup includes setupCore and also sets up services From 14fe8115630c75ab0ed55b644a5ec192eae48de0 Mon Sep 17 00:00:00 2001 From: Matthew Fenwick Date: Wed, 12 Aug 2020 13:11:51 -0400 Subject: [PATCH 6/7] back out conversion of DialFromNode->DialFromTestContainer --- test/e2e/common/networking.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/e2e/common/networking.go b/test/e2e/common/networking.go index f7b1b1cba90..7d95e8892b3 100644 --- a/test/e2e/common/networking.go +++ b/test/e2e/common/networking.go @@ -103,7 +103,9 @@ var _ = ginkgo.Describe("[sig-network] Networking", func() { */ framework.ConformanceIt("should function for node-pod communication: http [LinuxOnly] [NodeConformance]", func() { config := e2enetwork.NewCoreNetworkingTestConfig(f, true) - checkNodeConnectivity(config, "http", e2enetwork.EndpointHTTPPort) + for _, endpointPod := range config.EndpointPods { + config.DialFromNode("http", endpointPod.Status.PodIP, e2enetwork.EndpointHTTPPort, config.MaxTries, 0, sets.NewString(endpointPod.Name)) + } }) /* @@ -115,7 +117,9 @@ var _ = ginkgo.Describe("[sig-network] Networking", func() { */ framework.ConformanceIt("should function for node-pod communication: udp [LinuxOnly] [NodeConformance]", func() { config := e2enetwork.NewCoreNetworkingTestConfig(f, true) - checkNodeConnectivity(config, "udp", e2enetwork.EndpointUDPPort) + for _, endpointPod := range config.EndpointPods { + config.DialFromNode("udp", endpointPod.Status.PodIP, e2enetwork.EndpointUDPPort, config.MaxTries, 0, sets.NewString(endpointPod.Name)) + } }) }) }) From 81219ca9d2524176708c28ed34fdb750abc7bdab Mon Sep 17 00:00:00 2001 From: Matthew Fenwick Date: Wed, 2 Sep 2020 21:49:45 -0400 Subject: [PATCH 7/7] address review comments --- test/e2e/common/networking.go | 1 - test/e2e/framework/network/utils.go | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/test/e2e/common/networking.go b/test/e2e/common/networking.go index 7d95e8892b3..f08520f9e9d 100644 --- a/test/e2e/common/networking.go +++ b/test/e2e/common/networking.go @@ -52,7 +52,6 @@ var _ = ginkgo.Describe("[sig-network] Networking", func() { framework.Logf("Now attempting to probe pod [[[ %v ]]]", endpointPod.Status.PodIP) if err := config.DialFromTestContainer(protocol, endpointPod.Status.PodIP, port, config.MaxTries, 0, sets.NewString(endpointPod.Name)); err != nil { errors = append(errors, err) - framework.Logf("Warning: Test failure (%v) will occur due to %v", len(errors), err) // convenient error message for diagnosis... how many pods failed, and on what hosts? } else { framework.Logf("Was able to reach %v on %v ", endpointPod.Status.PodIP, endpointPod.Status.HostIP) } diff --git a/test/e2e/framework/network/utils.go b/test/e2e/framework/network/utils.go index ce289f839c1..0ca63f3f6e0 100644 --- a/test/e2e/framework/network/utils.go +++ b/test/e2e/framework/network/utils.go @@ -248,8 +248,7 @@ func makeCURLDialCommand(ipPort, dialCmd, protocol, targetIP string, targetPort // maxTries == minTries will confirm that we see the expected endpoints and no // more for maxTries. Use this if you want to eg: fail a readiness check on a // pod and confirm it doesn't show up as an endpoint. -// -// returns nil if no error, or error message if failed after trying maxTries. +// Returns nil if no error, or error message if failed after trying maxTries. func (config *NetworkingTestConfig) DialFromContainer(protocol, dialCommand, containerIP, targetIP string, containerHTTPPort, targetPort, maxTries, minTries int, expectedResponses sets.String) error { ipPort := net.JoinHostPort(containerIP, strconv.Itoa(containerHTTPPort)) cmd := makeCURLDialCommand(ipPort, dialCommand, protocol, targetIP, targetPort)