Skip to content

Commit 1b9f11c

Browse files
committed
fix(e2e): access nodes via test container in LB network tests
Signed-off-by: knight42 <[email protected]>
1 parent bcdb3c5 commit 1b9f11c

File tree

4 files changed

+248
-84
lines changed

4 files changed

+248
-84
lines changed

test/e2e/framework/network/utils.go

Lines changed: 74 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,12 @@ type NetworkingTestConfig struct {
160160
Namespace string
161161
}
162162

163+
// NetexecDialResponse represents the response returned by the `netexec` subcommand of `agnhost`
164+
type NetexecDialResponse struct {
165+
Responses []string `json:"responses"`
166+
Errors []string `json:"errors"`
167+
}
168+
163169
// DialFromEndpointContainer executes a curl via kubectl exec in an endpoint container.
164170
func (config *NetworkingTestConfig) DialFromEndpointContainer(protocol, targetIP string, targetPort, maxTries, minTries int, expectedEps sets.String) {
165171
config.DialFromContainer(protocol, echoHostname, config.EndpointPods[0].Status.PodIP, targetIP, EndpointHTTPPort, targetPort, maxTries, minTries, expectedEps)
@@ -212,6 +218,18 @@ func (config *NetworkingTestConfig) EndpointHostnames() sets.String {
212218
return expectedEps
213219
}
214220

221+
func makeCURLDialCommand(ipPort, dialCmd, protocol, targetIP string, targetPort int) string {
222+
// The current versions of curl included in CentOS and RHEL distros
223+
// misinterpret square brackets around IPv6 as globbing, so use the -g
224+
// argument to disable globbing to handle the IPv6 case.
225+
return fmt.Sprintf("curl -g -q -s 'http://%s/dial?request=%s&protocol=%s&host=%s&port=%d&tries=1'",
226+
ipPort,
227+
dialCmd,
228+
protocol,
229+
targetIP,
230+
targetPort)
231+
}
232+
215233
// DialFromContainer executes a curl via kubectl exec in a test container,
216234
// which might then translate to a tcp or udp request based on the protocol
217235
// argument in the url.
@@ -232,38 +250,23 @@ func (config *NetworkingTestConfig) EndpointHostnames() sets.String {
232250
// pod and confirm it doesn't show up as an endpoint.
233251
func (config *NetworkingTestConfig) DialFromContainer(protocol, dialCommand, containerIP, targetIP string, containerHTTPPort, targetPort, maxTries, minTries int, expectedResponses sets.String) {
234252
ipPort := net.JoinHostPort(containerIP, strconv.Itoa(containerHTTPPort))
235-
// The current versions of curl included in CentOS and RHEL distros
236-
// misinterpret square brackets around IPv6 as globbing, so use the -g
237-
// argument to disable globbing to handle the IPv6 case.
238-
cmd := fmt.Sprintf("curl -g -q -s 'http://%s/dial?request=%s&protocol=%s&host=%s&port=%d&tries=1'",
239-
ipPort,
240-
dialCommand,
241-
protocol,
242-
targetIP,
243-
targetPort)
253+
cmd := makeCURLDialCommand(ipPort, dialCommand, protocol, targetIP, targetPort)
244254

245255
responses := sets.NewString()
246256

247257
for i := 0; i < maxTries; i++ {
248-
stdout, stderr, err := config.f.ExecShellInPodWithFullOutput(config.TestContainerPod.Name, cmd)
258+
resp, err := config.GetResponseFromContainer(protocol, dialCommand, containerIP, targetIP, containerHTTPPort, targetPort)
249259
if err != nil {
250260
// A failure to kubectl exec counts as a try, not a hard fail.
251261
// Also note that we will keep failing for maxTries in tests where
252262
// we confirm unreachability.
253-
framework.Logf("Failed to execute %q: %v, stdout: %q, stderr %q", cmd, err, stdout, stderr)
254-
} else {
255-
var output map[string][]string
256-
if err := json.Unmarshal([]byte(stdout), &output); err != nil {
257-
framework.Logf("WARNING: Failed to unmarshal curl response. Cmd %v run in %v, output: %s, err: %v",
258-
cmd, config.TestContainerPod.Name, stdout, err)
259-
continue
260-
}
261-
262-
for _, response := range output["responses"] {
263-
trimmed := strings.TrimSpace(response)
264-
if trimmed != "" {
265-
responses.Insert(trimmed)
266-
}
263+
framework.Logf("GetResponseFromContainer: %s", err)
264+
continue
265+
}
266+
for _, response := range resp.Responses {
267+
trimmed := strings.TrimSpace(response)
268+
if trimmed != "" {
269+
responses.Insert(trimmed)
267270
}
268271
}
269272
framework.Logf("Waiting for responses: %v", expectedResponses.Difference(responses))
@@ -294,14 +297,7 @@ func (config *NetworkingTestConfig) GetEndpointsFromTestContainer(protocol, targ
294297
// we don't see any endpoints, the test fails.
295298
func (config *NetworkingTestConfig) GetEndpointsFromContainer(protocol, containerIP, targetIP string, containerHTTPPort, targetPort, tries int) (sets.String, error) {
296299
ipPort := net.JoinHostPort(containerIP, strconv.Itoa(containerHTTPPort))
297-
// The current versions of curl included in CentOS and RHEL distros
298-
// misinterpret square brackets around IPv6 as globbing, so use the -g
299-
// argument to disable globbing to handle the IPv6 case.
300-
cmd := fmt.Sprintf("curl -g -q -s 'http://%s/dial?request=hostName&protocol=%s&host=%s&port=%d&tries=1'",
301-
ipPort,
302-
protocol,
303-
targetIP,
304-
targetPort)
300+
cmd := makeCURLDialCommand(ipPort, "hostName", protocol, targetIP, targetPort)
305301

306302
eps := sets.NewString()
307303

@@ -314,14 +310,14 @@ func (config *NetworkingTestConfig) GetEndpointsFromContainer(protocol, containe
314310
framework.Logf("Failed to execute %q: %v, stdout: %q, stderr: %q", cmd, err, stdout, stderr)
315311
} else {
316312
framework.Logf("Tries: %d, in try: %d, stdout: %v, stderr: %v, command run in: %#v", tries, i, stdout, stderr, config.TestContainerPod)
317-
var output map[string][]string
313+
var output NetexecDialResponse
318314
if err := json.Unmarshal([]byte(stdout), &output); err != nil {
319315
framework.Logf("WARNING: Failed to unmarshal curl response. Cmd %v run in %v, output: %s, err: %v",
320316
cmd, config.TestContainerPod.Name, stdout, err)
321317
continue
322318
}
323319

324-
for _, hostName := range output["responses"] {
320+
for _, hostName := range output.Responses {
325321
trimmed := strings.TrimSpace(hostName)
326322
if trimmed != "" {
327323
eps.Insert(trimmed)
@@ -334,6 +330,50 @@ func (config *NetworkingTestConfig) GetEndpointsFromContainer(protocol, containe
334330
return eps, nil
335331
}
336332

333+
// GetResponseFromContainer executes a curl via kubectl exec in a container.
334+
func (config *NetworkingTestConfig) GetResponseFromContainer(protocol, dialCommand, containerIP, targetIP string, containerHTTPPort, targetPort int) (NetexecDialResponse, error) {
335+
ipPort := net.JoinHostPort(containerIP, strconv.Itoa(containerHTTPPort))
336+
cmd := makeCURLDialCommand(ipPort, dialCommand, protocol, targetIP, targetPort)
337+
338+
stdout, stderr, err := config.f.ExecShellInPodWithFullOutput(config.TestContainerPod.Name, cmd)
339+
if err != nil {
340+
return NetexecDialResponse{}, fmt.Errorf("failed to execute %q: %v, stdout: %q, stderr: %q", cmd, err, stdout, stderr)
341+
}
342+
343+
var output NetexecDialResponse
344+
if err := json.Unmarshal([]byte(stdout), &output); err != nil {
345+
return NetexecDialResponse{}, fmt.Errorf("failed to unmarshal curl response. Cmd %v run in %v, output: %s, err: %v",
346+
cmd, config.TestContainerPod.Name, stdout, err)
347+
}
348+
return output, nil
349+
}
350+
351+
// GetResponseFromTestContainer executes a curl via kubectl exec in a test container.
352+
func (config *NetworkingTestConfig) GetResponseFromTestContainer(protocol, dialCommand, targetIP string, targetPort int) (NetexecDialResponse, error) {
353+
return config.GetResponseFromContainer(protocol, dialCommand, config.TestContainerPod.Status.PodIP, targetIP, testContainerHTTPPort, targetPort)
354+
}
355+
356+
// GetHTTPCodeFromTestContainer executes a curl via kubectl exec in a test container and returns the status code.
357+
func (config *NetworkingTestConfig) GetHTTPCodeFromTestContainer(path, targetIP string, targetPort int) (int, error) {
358+
cmd := fmt.Sprintf("curl -g -q -s -o /dev/null -w %%{http_code} http://%s:%d%s",
359+
targetIP,
360+
targetPort,
361+
path)
362+
stdout, stderr, err := config.f.ExecShellInPodWithFullOutput(config.TestContainerPod.Name, cmd)
363+
// We only care about the status code reported by curl,
364+
// and want to return any other errors, such as cannot execute command in the Pod.
365+
// If curl failed to connect to host, it would exit with code 7, which makes `ExecShellInPodWithFullOutput`
366+
// return a non-nil error and output "000" to stdout.
367+
if err != nil && len(stdout) == 0 {
368+
return 0, fmt.Errorf("failed to execute %q: %v, stderr: %q", cmd, err, stderr)
369+
}
370+
code, err := strconv.Atoi(stdout)
371+
if err != nil {
372+
return 0, fmt.Errorf("failed to parse status code returned by healthz endpoint: %w, code: %s", err, stdout)
373+
}
374+
return code, nil
375+
}
376+
337377
// DialFromNode executes a tcp or udp request based on protocol via kubectl exec
338378
// in a test container running with host networking.
339379
// - minTries is the minimum number of curl attempts required before declaring

test/e2e/framework/service/jig.go

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -260,21 +260,42 @@ func (j *TestJig) CreateLoadBalancerService(timeout time.Duration, tweak func(sv
260260
// GetEndpointNodes returns a map of nodenames:external-ip on which the
261261
// endpoints of the Service are running.
262262
func (j *TestJig) GetEndpointNodes() (map[string][]string, error) {
263-
nodes, err := e2enode.GetBoundedReadySchedulableNodes(j.Client, MaxNodesForEndpointsTests)
263+
return j.GetEndpointNodesWithIP(v1.NodeExternalIP)
264+
}
265+
266+
// GetEndpointNodesWithIP returns a map of nodenames:<ip of given type> on which the
267+
// endpoints of the Service are running.
268+
func (j *TestJig) GetEndpointNodesWithIP(addressType v1.NodeAddressType) (map[string][]string, error) {
269+
nodes, err := j.ListNodesWithEndpoint()
264270
if err != nil {
265271
return nil, err
266272
}
267-
epNodes, err := j.GetEndpointNodeNames()
273+
nodeMap := map[string][]string{}
274+
for _, node := range nodes {
275+
nodeMap[node.Name] = e2enode.GetAddresses(&node, addressType)
276+
}
277+
return nodeMap, nil
278+
}
279+
280+
// ListNodesWithEndpoint returns a list of nodes on which the
281+
// endpoints of the given Service are running.
282+
func (j *TestJig) ListNodesWithEndpoint() ([]v1.Node, error) {
283+
nodeNames, err := j.GetEndpointNodeNames()
268284
if err != nil {
269285
return nil, err
270286
}
271-
nodeMap := map[string][]string{}
272-
for _, n := range nodes.Items {
273-
if epNodes.Has(n.Name) {
274-
nodeMap[n.Name] = e2enode.GetAddresses(&n, v1.NodeExternalIP)
287+
ctx := context.TODO()
288+
allNodes, err := j.Client.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
289+
if err != nil {
290+
return nil, err
291+
}
292+
epNodes := make([]v1.Node, 0, nodeNames.Len())
293+
for _, node := range allNodes.Items {
294+
if nodeNames.Has(node.Name) {
295+
epNodes = append(epNodes, node)
275296
}
276297
}
277-
return nodeMap, nil
298+
return epNodes, nil
278299
}
279300

280301
// GetEndpointNodeNames returns a string set of node names on which the

0 commit comments

Comments
 (0)