From 2dcdb2275ac8c2deeeefdacc9b9c5bc5e5ff7b7d Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Sat, 6 Sep 2025 04:31:43 +0300 Subject: [PATCH] test(vm): check TCP connection during VM migration Signed-off-by: Roman Sysoev --- tests/e2e/config/config.go | 1 + tests/e2e/default_config.yaml | 1 + .../base/cfg/cloudinit.yaml | 11 + .../base/kustomization.yaml | 15 + .../base/transformer.yaml | 54 ++++ .../base/vd-attach.yaml | 8 + .../base/vd-blank.yaml | 8 + .../base/vd-root.yaml | 13 + .../vm-migration-tcp-session/base/vm.yaml | 24 ++ .../kustomization.yaml | 16 + .../testdata/vm-migration-tcp-session/ns.yaml | 4 + .../overlays/iperf-client/kustomization.yaml | 17 + .../overlays/iperf-server/kustomization.yaml | 17 + .../vm-migration-tcp-session/transformer.yaml | 52 +++ .../vi/kustomization.yaml | 4 + .../vi/vi-alpine-http.yaml | 11 + tests/e2e/vm_migration_tcp_session_test.go | 298 ++++++++++++++++++ 17 files changed, 554 insertions(+) create mode 100644 tests/e2e/testdata/vm-migration-tcp-session/base/cfg/cloudinit.yaml create mode 100644 tests/e2e/testdata/vm-migration-tcp-session/base/kustomization.yaml create mode 100644 tests/e2e/testdata/vm-migration-tcp-session/base/transformer.yaml create mode 100644 tests/e2e/testdata/vm-migration-tcp-session/base/vd-attach.yaml create mode 100644 tests/e2e/testdata/vm-migration-tcp-session/base/vd-blank.yaml create mode 100644 tests/e2e/testdata/vm-migration-tcp-session/base/vd-root.yaml create mode 100644 tests/e2e/testdata/vm-migration-tcp-session/base/vm.yaml create mode 100644 tests/e2e/testdata/vm-migration-tcp-session/kustomization.yaml create mode 100644 tests/e2e/testdata/vm-migration-tcp-session/ns.yaml create mode 100644 tests/e2e/testdata/vm-migration-tcp-session/overlays/iperf-client/kustomization.yaml create mode 100644 tests/e2e/testdata/vm-migration-tcp-session/overlays/iperf-server/kustomization.yaml create mode 100644 tests/e2e/testdata/vm-migration-tcp-session/transformer.yaml create mode 100644 tests/e2e/testdata/vm-migration-tcp-session/vi/kustomization.yaml create mode 100644 tests/e2e/testdata/vm-migration-tcp-session/vi/vi-alpine-http.yaml create mode 100644 tests/e2e/vm_migration_tcp_session_test.go diff --git a/tests/e2e/config/config.go b/tests/e2e/config/config.go index 75b21941f7..852148a17f 100644 --- a/tests/e2e/config/config.go +++ b/tests/e2e/config/config.go @@ -149,6 +149,7 @@ type TestData struct { VMLabelAnnotation string `yaml:"vmLabelAnnotation"` VMMigration string `yaml:"vmMigration"` VMMigrationCancel string `yaml:"vmMigrationCancel"` + VMMigrationTCPSession string `yaml:"vmMigrationTCPSession"` VMEvacuation string `yaml:"vmEvacuation"` VMDiskAttachment string `yaml:"vmDiskAttachment"` VMRestoreForce string `yaml:"vmRestoreForce"` diff --git a/tests/e2e/default_config.yaml b/tests/e2e/default_config.yaml index ea6033ec1d..5b6a749ee8 100644 --- a/tests/e2e/default_config.yaml +++ b/tests/e2e/default_config.yaml @@ -31,6 +31,7 @@ testData: vmLabelAnnotation: "/tmp/testdata/vm-label-annotation" vmMigration: "/tmp/testdata/vm-migration" vmMigrationCancel: "/tmp/testdata/vm-migration-cancel" + vmMigrationTCPSession: "/tmp/testdata/vm-migration-tcp-session" vmEvacuation: "/tmp/testdata/vm-evacuation" vmDiskAttachment: "/tmp/testdata/vm-disk-attachment" vmRestoreForce: "/tmp/testdata/vm-restore-force" diff --git a/tests/e2e/testdata/vm-migration-tcp-session/base/cfg/cloudinit.yaml b/tests/e2e/testdata/vm-migration-tcp-session/base/cfg/cloudinit.yaml new file mode 100644 index 0000000000..b2372f3279 --- /dev/null +++ b/tests/e2e/testdata/vm-migration-tcp-session/base/cfg/cloudinit.yaml @@ -0,0 +1,11 @@ +#cloud-config +users: + - name: cloud + # passwd: cloud + passwd: $6$rounds=4096$vln/.aPHBOI7BMYR$bBMkqQvuGs5Gyd/1H5DP4m9HjQSy.kgrxpaGEHwkX7KEFV8BS.HZWPitAtZ2Vd8ZqIZRqmlykRCagTgPejt1i. + shell: /bin/bash + sudo: ALL=(ALL) NOPASSWD:ALL + lock_passwd: false + ssh_authorized_keys: + # testcases + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxcXHmwaGnJ8scJaEN5RzklBPZpVSic4GdaAsKjQoeA your_email@example.com diff --git a/tests/e2e/testdata/vm-migration-tcp-session/base/kustomization.yaml b/tests/e2e/testdata/vm-migration-tcp-session/base/kustomization.yaml new file mode 100644 index 0000000000..8ff06116f3 --- /dev/null +++ b/tests/e2e/testdata/vm-migration-tcp-session/base/kustomization.yaml @@ -0,0 +1,15 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./vm.yaml + - ./vd-root.yaml + - ./vd-blank.yaml +configurations: + - transformer.yaml +generatorOptions: + disableNameSuffixHash: true +secretGenerator: + - files: + - userData=cfg/cloudinit.yaml + name: cloud-init + type: provisioning.virtualization.deckhouse.io/cloud-init diff --git a/tests/e2e/testdata/vm-migration-tcp-session/base/transformer.yaml b/tests/e2e/testdata/vm-migration-tcp-session/base/transformer.yaml new file mode 100644 index 0000000000..c70c289afa --- /dev/null +++ b/tests/e2e/testdata/vm-migration-tcp-session/base/transformer.yaml @@ -0,0 +1,54 @@ +# https://github.com/kubernetes-sigs/kustomize/blob/master/examples/transformerconfigs/README.md#transformer-configurations + +namespace: + - kind: ClusterVirtualImage + path: spec/dataSource/objectRef/namespace +nameReference: + - kind: VirtualImage + version: v1alpha2 # optional + fieldSpecs: + - path: spec/dataSource/objectRef/name + kind: ClusterVirtualImage + - path: spec/dataSource/objectRef/name + kind: VirtualImage + - path: spec/dataSource/objectRef/name + kind: VirtualDisk + - path: spec/blockDeviceRefs/name + kind: VirtualMachine + - kind: ClusterVirtualImage + version: v1alpha2 # optional + fieldSpecs: + - path: spec/dataSource/objectRef/name + kind: ClusterVirtualImage + - path: spec/dataSource/objectRef/name + kind: VirtualImage + - path: spec/dataSource/objectRef/name + kind: VirtualDisk + - path: spec/blockDeviceRefs/name + kind: VirtualMachine + - kind: VirtualDisk + version: v1alpha2 # optional + fieldSpecs: + - path: spec/blockDeviceRefs/name + kind: VirtualMachine + - path: spec/blockDeviceRef/name + kind: VirtualMachineBlockDeviceAttachment + - kind: Secret + fieldSpecs: + - path: spec/provisioning/userDataRef/name + kind: VirtualMachine + - kind: VirtualMachineIPAddress + version: v1alpha2 + fieldSpecs: + - path: spec/virtualMachineIPAddressName + kind: VirtualMachine + - kind: VirtualMachine + version: v1alpha2 + fieldSpecs: + - path: spec/virtualMachineName + kind: VirtualMachineBlockDeviceAttachment + - kind: VirtualMachineClass + version: v1alpha2 + fieldSpecs: + - path: spec/virtualMachineClassName + kind: VirtualMachine diff --git a/tests/e2e/testdata/vm-migration-tcp-session/base/vd-attach.yaml b/tests/e2e/testdata/vm-migration-tcp-session/base/vd-attach.yaml new file mode 100644 index 0000000000..a68911289e --- /dev/null +++ b/tests/e2e/testdata/vm-migration-tcp-session/base/vd-attach.yaml @@ -0,0 +1,8 @@ +apiVersion: virtualization.deckhouse.io/v1alpha2 +kind: VirtualDisk +metadata: + name: vd-attach +spec: + persistentVolumeClaim: + storageClassName: "{{ .STORAGE_CLASS_NAME }}" + size: 100Mi diff --git a/tests/e2e/testdata/vm-migration-tcp-session/base/vd-blank.yaml b/tests/e2e/testdata/vm-migration-tcp-session/base/vd-blank.yaml new file mode 100644 index 0000000000..fcc4b94ea8 --- /dev/null +++ b/tests/e2e/testdata/vm-migration-tcp-session/base/vd-blank.yaml @@ -0,0 +1,8 @@ +apiVersion: virtualization.deckhouse.io/v1alpha2 +kind: VirtualDisk +metadata: + name: vd-blank +spec: + persistentVolumeClaim: + storageClassName: "{{ .STORAGE_CLASS_NAME }}" + size: 100Mi diff --git a/tests/e2e/testdata/vm-migration-tcp-session/base/vd-root.yaml b/tests/e2e/testdata/vm-migration-tcp-session/base/vd-root.yaml new file mode 100644 index 0000000000..0f98ff3633 --- /dev/null +++ b/tests/e2e/testdata/vm-migration-tcp-session/base/vd-root.yaml @@ -0,0 +1,13 @@ +apiVersion: virtualization.deckhouse.io/v1alpha2 +kind: VirtualDisk +metadata: + name: vd-root +spec: + persistentVolumeClaim: + storageClassName: "{{ .STORAGE_CLASS_NAME }}" + size: 512Mi + dataSource: + type: ObjectRef + objectRef: + kind: VirtualImage + name: vi-alpine-http diff --git a/tests/e2e/testdata/vm-migration-tcp-session/base/vm.yaml b/tests/e2e/testdata/vm-migration-tcp-session/base/vm.yaml new file mode 100644 index 0000000000..6a38cd9ee8 --- /dev/null +++ b/tests/e2e/testdata/vm-migration-tcp-session/base/vm.yaml @@ -0,0 +1,24 @@ +apiVersion: virtualization.deckhouse.io/v1alpha2 +kind: VirtualMachine +metadata: + name: vm +spec: + bootloader: EFI + virtualMachineClassName: generic + cpu: + cores: 1 + coreFraction: 5% + memory: + size: 256Mi + disruptions: + restartApprovalMode: Manual + provisioning: + type: UserDataRef + userDataRef: + kind: Secret + name: cloud-init + blockDeviceRefs: + - kind: VirtualDisk + name: vd-root + - kind: VirtualDisk + name: vd-blank diff --git a/tests/e2e/testdata/vm-migration-tcp-session/kustomization.yaml b/tests/e2e/testdata/vm-migration-tcp-session/kustomization.yaml new file mode 100644 index 0000000000..df86825665 --- /dev/null +++ b/tests/e2e/testdata/vm-migration-tcp-session/kustomization.yaml @@ -0,0 +1,16 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: end-to-end +namePrefix: pr-number-or-commit-hash- +resources: + - ns.yaml + - vi + - overlays/iperf-client + - overlays/iperf-server +configurations: + - transformer.yaml +labels: + - includeSelectors: true + pairs: + id: pr-number-or-commit-hash + testcase: vm-migration-tcp-session diff --git a/tests/e2e/testdata/vm-migration-tcp-session/ns.yaml b/tests/e2e/testdata/vm-migration-tcp-session/ns.yaml new file mode 100644 index 0000000000..5efde875b6 --- /dev/null +++ b/tests/e2e/testdata/vm-migration-tcp-session/ns.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: default diff --git a/tests/e2e/testdata/vm-migration-tcp-session/overlays/iperf-client/kustomization.yaml b/tests/e2e/testdata/vm-migration-tcp-session/overlays/iperf-client/kustomization.yaml new file mode 100644 index 0000000000..d22ae2727c --- /dev/null +++ b/tests/e2e/testdata/vm-migration-tcp-session/overlays/iperf-client/kustomization.yaml @@ -0,0 +1,17 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +nameSuffix: -iperf-client +resources: + - ../../base +patches: + - patch: |- + - op: replace + path: /spec/runPolicy + value: AlwaysOn + target: + kind: VirtualMachine + name: vm +labels: + - includeSelectors: true + pairs: + vm: iperf-client diff --git a/tests/e2e/testdata/vm-migration-tcp-session/overlays/iperf-server/kustomization.yaml b/tests/e2e/testdata/vm-migration-tcp-session/overlays/iperf-server/kustomization.yaml new file mode 100644 index 0000000000..87d9d94dcf --- /dev/null +++ b/tests/e2e/testdata/vm-migration-tcp-session/overlays/iperf-server/kustomization.yaml @@ -0,0 +1,17 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +nameSuffix: -iperf-server +resources: + - ../../base +patches: + - patch: |- + - op: replace + path: /spec/runPolicy + value: AlwaysOn + target: + kind: VirtualMachine + name: vm +labels: + - includeSelectors: true + pairs: + vm: iperf-server diff --git a/tests/e2e/testdata/vm-migration-tcp-session/transformer.yaml b/tests/e2e/testdata/vm-migration-tcp-session/transformer.yaml new file mode 100644 index 0000000000..e827a19238 --- /dev/null +++ b/tests/e2e/testdata/vm-migration-tcp-session/transformer.yaml @@ -0,0 +1,52 @@ +namespace: + - kind: ClusterVirtualImage + path: spec/dataSource/objectRef/namespace +nameReference: + - kind: VirtualImage + version: v1alpha2 # optional + fieldSpecs: + - path: spec/dataSource/objectRef/name + kind: ClusterVirtualImage + - path: spec/dataSource/objectRef/name + kind: VirtualImage + - path: spec/dataSource/objectRef/name + kind: VirtualDisk + - path: spec/blockDeviceRefs/name + kind: VirtualMachine + - kind: ClusterVirtualImage + version: v1alpha2 # optional + fieldSpecs: + - path: spec/dataSource/objectRef/name + kind: ClusterVirtualImage + - path: spec/dataSource/objectRef/name + kind: VirtualImage + - path: spec/dataSource/objectRef/name + kind: VirtualDisk + - path: spec/blockDeviceRefs/name + kind: VirtualMachine + - kind: VirtualDisk + version: v1alpha2 # optional + fieldSpecs: + - path: spec/blockDeviceRefs/name + kind: VirtualMachine + - path: spec/blockDeviceRef/name + kind: VirtualMachineBlockDeviceAttachment + - kind: Secret + fieldSpecs: + - path: spec/provisioning/userDataRef/name + kind: VirtualMachine + - kind: VirtualMachineIPAddress + version: v1alpha2 + fieldSpecs: + - path: spec/virtualMachineIPAddressName + kind: VirtualMachine + - kind: VirtualMachine + version: v1alpha2 + fieldSpecs: + - path: spec/virtualMachineName + kind: VirtualMachineBlockDeviceAttachment + - kind: VirtualMachineClass + version: v1alpha2 + fieldSpecs: + - path: spec/virtualMachineClassName + kind: VirtualMachine diff --git a/tests/e2e/testdata/vm-migration-tcp-session/vi/kustomization.yaml b/tests/e2e/testdata/vm-migration-tcp-session/vi/kustomization.yaml new file mode 100644 index 0000000000..b807d8e2d2 --- /dev/null +++ b/tests/e2e/testdata/vm-migration-tcp-session/vi/kustomization.yaml @@ -0,0 +1,4 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - vi-alpine-http.yaml diff --git a/tests/e2e/testdata/vm-migration-tcp-session/vi/vi-alpine-http.yaml b/tests/e2e/testdata/vm-migration-tcp-session/vi/vi-alpine-http.yaml new file mode 100644 index 0000000000..abac06b48c --- /dev/null +++ b/tests/e2e/testdata/vm-migration-tcp-session/vi/vi-alpine-http.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: virtualization.deckhouse.io/v1alpha2 +kind: VirtualImage +metadata: + name: vi-alpine-http +spec: + storage: ContainerRegistry + dataSource: + type: HTTP + http: + url: https://89d64382-20df-4581-8cc7-80df331f67fa.selstorage.ru/alpine/alpine-3-21-uefi-perf.qcow2 diff --git a/tests/e2e/vm_migration_tcp_session_test.go b/tests/e2e/vm_migration_tcp_session_test.go new file mode 100644 index 0000000000..cdd7f41bc2 --- /dev/null +++ b/tests/e2e/vm_migration_tcp_session_test.go @@ -0,0 +1,298 @@ +/* +Copyright 2025 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package e2e + +import ( + "encoding/json" + "fmt" + "os" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/tests/e2e/config" + "github.com/deckhouse/virtualization/tests/e2e/d8" + "github.com/deckhouse/virtualization/tests/e2e/ginkgoutil" + kc "github.com/deckhouse/virtualization/tests/e2e/kubectl" +) + +var _ = Describe("VirtualMachineMigrationTCPSession", SIGMigration(), ginkgoutil.CommonE2ETestDecorators(), func() { + var ( + testCaseLabel = map[string]string{"testcase": "vm-migration-tcp-session"} + iperfClientLabel = map[string]string{"vm": "iperf-client"} + iperfServerLabel = map[string]string{"vm": "iperf-server"} + rawReport = new(string) + ns string + ) + + BeforeAll(func() { + kustomization := fmt.Sprintf("%s/%s", conf.TestData.VMMigrationTCPSession, "kustomization.yaml") + var err error + ns, err = kustomize.GetNamespace(kustomization) + Expect(err).NotTo(HaveOccurred(), "%w", err) + + CreateNamespace(ns) + }) + + AfterEach(func() { + if CurrentSpecReport().Failed() { + SaveTestResources(testCaseLabel, CurrentSpecReport().LeafNodeText) + SaveIPerfClientReport(testCaseLabel, rawReport) + } + }) + + Context("When resources are applied", func() { + It("result should be succeeded", func() { + if config.IsReusable() { + res := kubectl.List(kc.ResourceVM, kc.GetOptions{ + Labels: testCaseLabel, + Namespace: ns, + Output: "jsonpath='{.items[*].metadata.name}'", + }) + Expect(res.Error()).NotTo(HaveOccurred(), res.StdErr()) + + if res.StdOut() != "" { + return + } + } + + res := kubectl.Apply(kc.ApplyOptions{ + Filename: []string{conf.TestData.VMMigrationTCPSession}, + FilenameOption: kc.Kustomize, + }) + Expect(res.Error()).NotTo(HaveOccurred(), res.StdErr()) + + WaitVMAgentReady(kc.WaitOptions{ + Labels: testCaseLabel, + Namespace: ns, + Timeout: MaxWaitTimeout, + }) + }) + }) + + Context("Virtual Machine Migration", func() { + It("checks TCP connection", func() { + reportName := "report.json" + iperfClientVM, iperfServerVM, err := getVirtualMachinesByLabel(iperfClientLabel, iperfServerLabel, testCaseLabel, ns) + + Expect(err).NotTo(HaveOccurred()) + Expect(iperfClientVM).NotTo(BeNil()) + Expect(iperfServerVM).NotTo(BeNil()) + + By("Run the iPerf client.", func() { + cmd := fmt.Sprintf("nohup iperf3 --client %s --time 0 --json > ~/%s 2>&1 &", iperfServerVM.Status.IPAddress, reportName) + ExecSSHCommand(ns, iperfClientVM.Name, cmd) + }) + By("Migrate the iPerf server.", func() { + MigrateVirtualMachines(testCaseLabel, ns, iperfServerVM.Name) + WaitMigrationEnd(iperfServerVM.Name, ns) + }) + + By("Wait for packets to be transmitted after migration.", func() { + time.Sleep(10 * time.Second) + }) + + By("Check the iPerf client report.", func() { + StopIPerfClient(iperfClientVM.Name, ns, iperfServerVM.Status.IPAddress) + GetIPerfClientReport(iperfClientVM.Name, ns, reportName, rawReport) + + report := &IPerfReport{} + err := json.Unmarshal([]byte(*rawReport), report) + Expect(err).NotTo(HaveOccurred()) + + iperfServerVMAfterMigration := &v1alpha2.VirtualMachine{} + err = GetObject(kc.ResourceVM, iperfServerVM.Name, iperfServerVMAfterMigration, kc.GetOptions{Namespace: ns}) + Expect(err).NotTo(HaveOccurred()) + + iPerfClientStartTime, err := time.Parse(time.RFC1123, report.Start.Timestamp.Time) + Expect(err).NotTo(HaveOccurred()) + Expect(iPerfClientStartTime.Before(iperfServerVMAfterMigration.Status.MigrationState.StartTimestamp.Time)).To(BeTrue(), "the iPerfClient connection test should start before the virtual machine is migrated") + + iPerfClientEndTimeUnix := int64(report.Start.Timestamp.Timesecs) + int64(report.End.SumSent.End) + iPerfClientEndTime := time.Unix(iPerfClientEndTimeUnix, int64((report.End.SumSent.End-float64(int64(report.End.SumSent.End)))*1e9)).UTC() + Expect(iPerfClientEndTime.After(iperfServerVMAfterMigration.Status.MigrationState.EndTimestamp.Time)).To(BeTrue(), "the iPerfClient connection test should stop after the virtual machine is migrated") + + zeroBytesIntervalCounter := 0 + for _, i := range report.Intervals { + if i.Sum.Bytes == 0 { + zeroBytesIntervalCounter++ + } + } + Expect(zeroBytesIntervalCounter).To(BeNumerically("<=", 1), "there should not be more than one zero-byte interval during the migration process") + }) + }) + }) + + Context("When test is completed", func() { + It("deletes test case resources", func() { + var resourcesToDelete ResourcesToDelete + + if config.IsCleanUpNeeded() { + resourcesToDelete.KustomizationDir = conf.TestData.VMMigrationTCPSession + } + + DeleteTestCaseResources(ns, resourcesToDelete) + }) + }) +}) + +func getVirtualMachinesByLabel(iperfClientLabel, iperfServerLabel, testCaseLabel map[string]string, namespace string) (iperfClientVM, iperfServerVM *v1alpha2.VirtualMachine, err error) { + vms := &v1alpha2.VirtualMachineList{} + err = GetObjects(kc.ResourceVM, vms, kc.GetOptions{ + Labels: testCaseLabel, + Namespace: namespace, + }) + if err != nil { + return iperfClientVM, iperfServerVM, err + } + + for virtualMachineKey, iperfClientValue := range iperfClientLabel { + for _, vm := range vms.Items { + if v, ok := vm.Labels[virtualMachineKey]; ok { + if v == iperfClientValue { + iperfClientVM = &vm + break + } + } + } + } + + for virtualMachineKey, iperfServerValue := range iperfServerLabel { + for _, vm := range vms.Items { + if v, ok := vm.Labels[virtualMachineKey]; ok { + if v == iperfServerValue { + iperfServerVM = &vm + break + } + } + } + } + + return iperfClientVM, iperfServerVM, nil +} + +func WaitMigrationEnd(vmName, namespace string) { + GinkgoHelper() + + Eventually(func() error { + vmAfterMigration := &v1alpha2.VirtualMachine{} + err := GetObject(kc.ResourceVM, vmName, vmAfterMigration, kc.GetOptions{Namespace: namespace}) + if err != nil { + return err + } + + if vmAfterMigration.Status.MigrationState != nil { + if vmAfterMigration.Status.MigrationState.Result == v1alpha2.MigrationResultSucceeded { + return nil + } + } + + return fmt.Errorf("failed to get `VirtualMachine.Status.MigrationState`: %s", vmName) + }).WithTimeout(LongWaitDuration).WithPolling(Interval).Should(Succeed()) +} + +func StopIPerfClient(vmName, namespace, ip string) { + GinkgoHelper() + + var pid string + + iPerfClientPidCmd := fmt.Sprintf("ps aux | grep \"iperf3 --client %s\" | grep -v grep | awk \"{print \\$1}\"", ip) + Eventually(func() error { + res := d8Virtualization.SSHCommand(vmName, iPerfClientPidCmd, d8.SSHOptions{ + Namespace: namespace, + Username: conf.TestData.SSHUser, + IdenityFile: conf.TestData.Sshkey, + }) + if res.Error() != nil { + return fmt.Errorf("cmd: %s\nstderr: %s", res.GetCmd(), res.StdErr()) + } + pid = res.StdOut() + return nil + }).WithTimeout(Timeout).WithPolling(Interval).ShouldNot(HaveOccurred()) + + stopIPerfClientCmd := fmt.Sprintf("kill %s", pid) + Eventually(func() error { + res := d8Virtualization.SSHCommand(vmName, stopIPerfClientCmd, d8.SSHOptions{ + Namespace: namespace, + Username: conf.TestData.SSHUser, + IdenityFile: conf.TestData.Sshkey, + }) + if res.Error() != nil { + return fmt.Errorf("cmd: %s\nstderr: %s", res.GetCmd(), res.StdErr()) + } + return nil + }).WithTimeout(Timeout).WithPolling(Interval).ShouldNot(HaveOccurred()) +} + +func GetIPerfClientReport(vmName, namespace, reportName string, report *string) { + GinkgoHelper() + + cmd := fmt.Sprintf("jq . ~/%s", reportName) + Eventually(func() error { + res := d8Virtualization.SSHCommand(vmName, cmd, d8.SSHOptions{ + Namespace: namespace, + Username: conf.TestData.SSHUser, + IdenityFile: conf.TestData.Sshkey, + }) + if res.Error() != nil { + return fmt.Errorf("cmd: %s\nstderr: %s", res.GetCmd(), res.StdErr()) + } + + *report = res.StdOut() + + return nil + }).WithTimeout(Timeout).WithPolling(Interval).ShouldNot(HaveOccurred()) +} + +func SaveIPerfClientReport(labels map[string]string, rawReport *string) { + GinkgoHelper() + + var jsonObject map[string]any + err := json.Unmarshal([]byte(*rawReport), &jsonObject) + Expect(err).NotTo(HaveOccurred()) + + r, err := json.MarshalIndent(&jsonObject, "", " ") + Expect(err).NotTo(HaveOccurred()) + + name := fmt.Sprintf("/tmp/e2e_failed__%s__iperf_client_report.json", labels["testcase"]) + err = os.WriteFile(name, r, 0o644) + Expect(err).NotTo(HaveOccurred()) +} + +type IPerfReport struct { + Start struct { + Timestamp struct { + Time string `json:"time"` + Timesecs int `json:"timesecs"` + } `json:"timestamp"` + } `json:"start"` + Intervals []IPerfInterval `json:"intervals"` + End struct { + SumSent struct { + End float64 `json:"end"` + } `json:"sum_sent"` + } `json:"end"` + Error string `json:"error,omitempty"` +} + +type IPerfInterval struct { + Sum struct { + Bytes int64 `json:"bytes"` + } `json:"sum"` +}