Skip to content

Commit 698868e

Browse files
test(e2e): improve additional network interfaces e2e test reliability (#2290)
This PR improves the reliability and diagnostics of the VirtualMachineAdditionalNetworkInterfaces E2E test. Signed-off-by: Dmitry Lopatin <dmitry.lopatin@flant.com> Signed-off-by: Dmitry Lopatin <93423466+LopatinDmitr@users.noreply.github.com> Co-authored-by: Roman Sysoev <36233932+hardcoretime@users.noreply.github.com>
1 parent f706ed2 commit 698868e

2 files changed

Lines changed: 73 additions & 14 deletions

File tree

test/e2e/internal/framework/dump.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ func (f *Framework) saveTestCaseDump() {
6565
f.saveIntvirtvmiDescriptions(ft, tmpDir)
6666
f.saveNodeAdditionalInfo(ft, tmpDir)
6767
f.saveEvents(ft, tmpDir)
68+
f.saveClusterNetworkInfo(ft, tmpDir)
6869
}
6970

7071
// GetFormattedTestCaseFullText returns CurrentSpecReport().FullText(), formatted with the following rules:
@@ -303,3 +304,41 @@ func (f *Framework) saveEvents(testCaseFullText, dumpPath string) {
303304
}
304305
}
305306
}
307+
308+
func (f *Framework) saveClusterNetworkInfo(testCaseFullText, dumpPath string) {
309+
GinkgoHelper()
310+
311+
// Only for tests that use additional networks.
312+
// We use the original full text for checking because testCaseFullText may be truncated.
313+
if !strings.Contains(CurrentSpecReport().FullText(), "VirtualMachineAdditionalNetworkInterfaces") {
314+
return
315+
}
316+
317+
// Get all ClusterNetwork resources
318+
cmd := f.Clients.Kubectl().RawCommand("get clusternetwork -o yaml", ShortTimeout)
319+
if cmd.Error() != nil {
320+
GinkgoWriter.Printf("Failed to get clusternetwork:\nCmdError: %v\nStderr: %s\n", cmd.Error(), cmd.StdErr())
321+
}
322+
323+
fileName := fmt.Sprintf("%s/e2e_failed__%s__clusternetwork.yaml", dumpPath, testCaseFullText)
324+
if len(cmd.StdOutBytes()) > 0 {
325+
err := os.WriteFile(fileName, cmd.StdOutBytes(), 0o644)
326+
if err != nil {
327+
GinkgoWriter.Printf("Failed to write clusternetwork dump:\nFile: %s\nError: %v\n", fileName, err)
328+
}
329+
}
330+
331+
// Get CEP (Cilium Endpoints) for the namespace
332+
cepCmd := f.Clients.Kubectl().RawCommand(fmt.Sprintf("get cep -n %s -o yaml", f.Namespace().Name), ShortTimeout)
333+
if cepCmd.Error() != nil {
334+
GinkgoWriter.Printf("Failed to get cep:\nCmdError: %v\nStderr: %s\n", cepCmd.Error(), cepCmd.StdErr())
335+
}
336+
337+
cepFileName := fmt.Sprintf("%s/e2e_failed__%s__cep.yaml", dumpPath, testCaseFullText)
338+
if len(cepCmd.StdOutBytes()) > 0 {
339+
err := os.WriteFile(cepFileName, cepCmd.StdOutBytes(), 0o644)
340+
if err != nil {
341+
GinkgoWriter.Printf("Failed to write cep dump:\nFile: %s\nError: %v\n", cepFileName, err)
342+
}
343+
}
344+
}

test/e2e/vm/additional_network_interfaces.go

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package vm
1818

1919
import (
2020
"context"
21+
"encoding/json"
2122
"fmt"
2223
"strings"
2324
"time"
@@ -183,12 +184,10 @@ var _ = Describe("VirtualMachineAdditionalNetworkInterfaces", Label(precheck.NoP
183184
)
184185

185186
const (
186-
getLastInterfaceNameCmd = "ip -o link show | tail -1 | cut -d: -f2 | awk \"{print \\$1}\""
187+
expectedLastInterfaceName = "eno3"
187188
)
188189

189190
It("should preserve interface name after removing middle ClusterNetwork and rebooting", func() {
190-
var lastInterfaceNameBeforeRemoval string
191-
192191
By("Create VM with Main network and two additional ClusterNetworks", func() {
193192
ns := f.Namespace().Name
194193

@@ -216,10 +215,7 @@ var _ = Describe("VirtualMachineAdditionalNetworkInterfaces", Label(precheck.NoP
216215

217216
By("Get last interface name via SSH", func() {
218217
util.UntilSSHReady(f, vm, framework.LongTimeout)
219-
output, err := f.SSHCommand(vm.Name, vm.Namespace, getLastInterfaceNameCmd)
220-
Expect(err).NotTo(HaveOccurred())
221-
lastInterfaceNameBeforeRemoval = strings.TrimSpace(output)
222-
Expect(lastInterfaceNameBeforeRemoval).NotTo(BeEmpty(), "Failed to get last interface name")
218+
checkLastInterfaceName(f, vm.Name, vm.Namespace, expectedLastInterfaceName)
223219
})
224220

225221
By("Remove middle ClusterNetwork from VM spec", func() {
@@ -253,13 +249,7 @@ var _ = Describe("VirtualMachineAdditionalNetworkInterfaces", Label(precheck.NoP
253249

254250
By("Verify last interface name has not changed", func() {
255251
util.UntilSSHReady(f, vm, framework.LongTimeout)
256-
output, err := f.SSHCommand(vm.Name, vm.Namespace, getLastInterfaceNameCmd)
257-
Expect(err).NotTo(HaveOccurred())
258-
lastInterfaceNameAfterRemoval := strings.TrimSpace(output)
259-
Expect(lastInterfaceNameAfterRemoval).NotTo(BeEmpty(), "Failed to get last interface name")
260-
261-
Expect(lastInterfaceNameAfterRemoval).To(Equal(lastInterfaceNameBeforeRemoval),
262-
fmt.Sprintf("Interface name changed from %s to %s after removing middle ClusterNetwork", lastInterfaceNameBeforeRemoval, lastInterfaceNameAfterRemoval))
252+
checkLastInterfaceName(f, vm.Name, vm.Namespace, expectedLastInterfaceName)
263253
})
264254
})
265255
})
@@ -361,3 +351,33 @@ func checkResultSSHCommand(f *framework.Framework, vmName, vmNamespace, cmd, equ
361351
return strings.TrimSpace(res), nil
362352
}).WithTimeout(Timeout).WithPolling(Interval).Should(Equal(equal))
363353
}
354+
355+
func checkLastInterfaceName(f *framework.Framework, vmName, vmNamespace, expected string) {
356+
GinkgoHelper()
357+
Eventually(func() (string, error) {
358+
cmd := "ip -j link show"
359+
result, err := f.SSHCommand(vmName, vmNamespace, cmd, framework.WithSSHTimeout(5*time.Second))
360+
if err != nil {
361+
return "", fmt.Errorf("failed to execute command: %w: %s", err, result)
362+
}
363+
364+
var links IPLinks
365+
err = json.Unmarshal([]byte(result), &links)
366+
if err != nil {
367+
return "", fmt.Errorf("failed to parse ip JSON output: %w", err)
368+
}
369+
if len(links) == 0 {
370+
return "", fmt.Errorf("no network interfaces found")
371+
}
372+
373+
return links[len(links)-1].IFName, nil
374+
}).WithTimeout(Timeout).WithPolling(Interval).Should(Equal(expected))
375+
}
376+
377+
// IPLinks represents the JSON output of ip -j link show command.
378+
type IPLinks []IPLink
379+
380+
// IPLink represents a single network interface in the ip JSON output.
381+
type IPLink struct {
382+
IFName string `json:"ifname"`
383+
}

0 commit comments

Comments
 (0)