diff --git a/test/e2e/legacy/testdata/vm-vpc/kustomization.yaml b/test/e2e/legacy/testdata/vm-vpc/kustomization.yaml deleted file mode 100644 index d174c81e73..0000000000 --- a/test/e2e/legacy/testdata/vm-vpc/kustomization.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -namespace: testcases -namePrefix: pr-number-or-commit-hash- -resources: - - ns.yaml - - vm-foo - - vm-bar - - vi -configurations: - - transformer.yaml -labels: - - includeSelectors: true - pairs: - id: pr-number-or-commit-hash - testcase: vm-vpc diff --git a/test/e2e/legacy/testdata/vm-vpc/network/clusternetwork.yaml b/test/e2e/legacy/testdata/vm-vpc/network/clusternetwork.yaml deleted file mode 100644 index 31134997b6..0000000000 --- a/test/e2e/legacy/testdata/vm-vpc/network/clusternetwork.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: network.deckhouse.io/v1alpha1 -kind: ClusterNetwork -metadata: - name: cn-1003-for-e2e-test -spec: - parentNodeNetworkInterfaces: - labelSelector: - matchLabels: - network.deckhouse.io/interface-type: NIC - network.deckhouse.io/node-role: worker - type: VLAN - vlan: - id: 1003 diff --git a/test/e2e/legacy/testdata/vm-vpc/ns.yaml b/test/e2e/legacy/testdata/vm-vpc/ns.yaml deleted file mode 100644 index 5efde875b6..0000000000 --- a/test/e2e/legacy/testdata/vm-vpc/ns.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: default diff --git a/test/e2e/legacy/testdata/vm-vpc/transformer.yaml b/test/e2e/legacy/testdata/vm-vpc/transformer.yaml deleted file mode 100644 index ec70d37fcd..0000000000 --- a/test/e2e/legacy/testdata/vm-vpc/transformer.yaml +++ /dev/null @@ -1,52 +0,0 @@ -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: v1alpha3 - fieldSpecs: - - path: spec/virtualMachineClassName - kind: VirtualMachine diff --git a/test/e2e/legacy/testdata/vm-vpc/vi/kustomization.yaml b/test/e2e/legacy/testdata/vm-vpc/vi/kustomization.yaml deleted file mode 100644 index b807d8e2d2..0000000000 --- a/test/e2e/legacy/testdata/vm-vpc/vi/kustomization.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - vi-alpine-http.yaml diff --git a/test/e2e/legacy/testdata/vm-vpc/vi/vi-alpine-http.yaml b/test/e2e/legacy/testdata/vm-vpc/vi/vi-alpine-http.yaml deleted file mode 100644 index abac06b48c..0000000000 --- a/test/e2e/legacy/testdata/vm-vpc/vi/vi-alpine-http.yaml +++ /dev/null @@ -1,11 +0,0 @@ ---- -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/test/e2e/legacy/testdata/vm-vpc/vm-bar/cfg/cloudinit-bar.yaml b/test/e2e/legacy/testdata/vm-vpc/vm-bar/cfg/cloudinit-bar.yaml deleted file mode 100644 index c585a6fc55..0000000000 --- a/test/e2e/legacy/testdata/vm-vpc/vm-bar/cfg/cloudinit-bar.yaml +++ /dev/null @@ -1,28 +0,0 @@ -#cloud-config -ssh_pwauth: True -users: - - name: cloud - # passwd: cloud - passwd: $6$rounds=4096$vln/.aPHBOI7BMYR$bBMkqQvuGs5Gyd/1H5DP4m9HjQSy.kgrxpaGEHwkX7KEFV8BS.HZWPitAtZ2Vd8ZqIZRqmlykRCagTgPejt1i. - shell: /bin/bash - sudo: ALL=(ALL) NOPASSWD:ALL - chpasswd: { expire: False } - lock_passwd: False - ssh_authorized_keys: - - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxcXHmwaGnJ8scJaEN5RzklBPZpVSic4GdaAsKjQoeA your_email@example.com -packages: - - qemu-guest-agent -write_files: - - path: /etc/network/interfaces - append: true - content: | - - auto eth1 - iface eth1 inet static - address 192.168.1.11 - netmask 255.255.255.0 -runcmd: - - sudo rc-update add qemu-guest-agent default - - sudo rc-service qemu-guest-agent start - - sudo /etc/init.d/networking restart - - chown -R cloud:cloud /home/cloud diff --git a/test/e2e/legacy/testdata/vm-vpc/vm-bar/kustomization.yaml b/test/e2e/legacy/testdata/vm-vpc/vm-bar/kustomization.yaml deleted file mode 100644 index 9e464e53b4..0000000000 --- a/test/e2e/legacy/testdata/vm-vpc/vm-bar/kustomization.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - vm-bar.yaml - - vd-bar-root.yaml -configurations: - - transformer.yaml -generatorOptions: - disableNameSuffixHash: true -secretGenerator: - - files: - - userData=cfg/cloudinit-bar.yaml - name: cloud-init-bar - type: provisioning.virtualization.deckhouse.io/cloud-init diff --git a/test/e2e/legacy/testdata/vm-vpc/vm-bar/transformer.yaml b/test/e2e/legacy/testdata/vm-vpc/vm-bar/transformer.yaml deleted file mode 100644 index 1dc146a3af..0000000000 --- a/test/e2e/legacy/testdata/vm-vpc/vm-bar/transformer.yaml +++ /dev/null @@ -1,54 +0,0 @@ -# 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: v1alpha3 - fieldSpecs: - - path: spec/virtualMachineClassName - kind: VirtualMachine diff --git a/test/e2e/legacy/testdata/vm-vpc/vm-bar/vd-bar-root.yaml b/test/e2e/legacy/testdata/vm-vpc/vm-bar/vd-bar-root.yaml deleted file mode 100644 index a97e711b19..0000000000 --- a/test/e2e/legacy/testdata/vm-vpc/vm-bar/vd-bar-root.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: virtualization.deckhouse.io/v1alpha2 -kind: VirtualDisk -metadata: - name: vd-bar-root -spec: - persistentVolumeClaim: - storageClassName: "{{ .STORAGE_CLASS_NAME }}" - size: 512Mi - dataSource: - type: ObjectRef - objectRef: - kind: VirtualImage - name: vi-alpine-http diff --git a/test/e2e/legacy/testdata/vm-vpc/vm-bar/vm-bar.yaml b/test/e2e/legacy/testdata/vm-vpc/vm-bar/vm-bar.yaml deleted file mode 100644 index fae42abb89..0000000000 --- a/test/e2e/legacy/testdata/vm-vpc/vm-bar/vm-bar.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: virtualization.deckhouse.io/v1alpha2 -kind: VirtualMachine -metadata: - name: vm-bar -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-bar - blockDeviceRefs: - - kind: VirtualDisk - name: vd-bar-root - networks: - - type: Main - - type: ClusterNetwork - name: cn-1003-for-e2e-test diff --git a/test/e2e/legacy/testdata/vm-vpc/vm-foo/cfg/cloudinit-foo.yaml b/test/e2e/legacy/testdata/vm-vpc/vm-foo/cfg/cloudinit-foo.yaml deleted file mode 100644 index b90e630637..0000000000 --- a/test/e2e/legacy/testdata/vm-vpc/vm-foo/cfg/cloudinit-foo.yaml +++ /dev/null @@ -1,28 +0,0 @@ -#cloud-config -ssh_pwauth: True -users: - - name: cloud - # passwd: cloud - passwd: $6$rounds=4096$vln/.aPHBOI7BMYR$bBMkqQvuGs5Gyd/1H5DP4m9HjQSy.kgrxpaGEHwkX7KEFV8BS.HZWPitAtZ2Vd8ZqIZRqmlykRCagTgPejt1i. - shell: /bin/bash - sudo: ALL=(ALL) NOPASSWD:ALL - chpasswd: { expire: False } - lock_passwd: False - ssh_authorized_keys: - - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxcXHmwaGnJ8scJaEN5RzklBPZpVSic4GdaAsKjQoeA your_email@example.com -packages: - - qemu-guest-agent -write_files: - - path: /etc/network/interfaces - append: true - content: | - - auto eth1 - iface eth1 inet static - address 192.168.1.10 - netmask 255.255.255.0 -runcmd: - - sudo rc-update add qemu-guest-agent default - - sudo rc-service qemu-guest-agent start - - sudo /etc/init.d/networking restart - - chown -R cloud:cloud /home/cloud diff --git a/test/e2e/legacy/testdata/vm-vpc/vm-foo/kustomization.yaml b/test/e2e/legacy/testdata/vm-vpc/vm-foo/kustomization.yaml deleted file mode 100644 index 04f2cfeb95..0000000000 --- a/test/e2e/legacy/testdata/vm-vpc/vm-foo/kustomization.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - vm-foo.yaml - - vd-foo-root.yaml -configurations: - - transformer.yaml -generatorOptions: - disableNameSuffixHash: true -secretGenerator: - - files: - - userData=cfg/cloudinit-foo.yaml - name: cloud-init-foo - type: provisioning.virtualization.deckhouse.io/cloud-init diff --git a/test/e2e/legacy/testdata/vm-vpc/vm-foo/transformer.yaml b/test/e2e/legacy/testdata/vm-vpc/vm-foo/transformer.yaml deleted file mode 100644 index 1dc146a3af..0000000000 --- a/test/e2e/legacy/testdata/vm-vpc/vm-foo/transformer.yaml +++ /dev/null @@ -1,54 +0,0 @@ -# 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: v1alpha3 - fieldSpecs: - - path: spec/virtualMachineClassName - kind: VirtualMachine diff --git a/test/e2e/legacy/testdata/vm-vpc/vm-foo/vd-foo-root.yaml b/test/e2e/legacy/testdata/vm-vpc/vm-foo/vd-foo-root.yaml deleted file mode 100644 index 9a3a323a67..0000000000 --- a/test/e2e/legacy/testdata/vm-vpc/vm-foo/vd-foo-root.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: virtualization.deckhouse.io/v1alpha2 -kind: VirtualDisk -metadata: - name: vd-foo-root -spec: - persistentVolumeClaim: - size: 512Mi - dataSource: - type: ObjectRef - objectRef: - kind: VirtualImage - name: vi-alpine-http diff --git a/test/e2e/legacy/testdata/vm-vpc/vm-foo/vm-foo.yaml b/test/e2e/legacy/testdata/vm-vpc/vm-foo/vm-foo.yaml deleted file mode 100644 index 6b62c5c397..0000000000 --- a/test/e2e/legacy/testdata/vm-vpc/vm-foo/vm-foo.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: virtualization.deckhouse.io/v1alpha2 -kind: VirtualMachine -metadata: - name: vm-foo -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-foo - blockDeviceRefs: - - kind: VirtualDisk - name: vd-foo-root - networks: - - type: Main - - type: ClusterNetwork - name: cn-1003-for-e2e-test diff --git a/test/e2e/legacy/vm_vpc.go b/test/e2e/legacy/vm_vpc.go deleted file mode 100644 index b89a182ce4..0000000000 --- a/test/e2e/legacy/vm_vpc.go +++ /dev/null @@ -1,204 +0,0 @@ -/* -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 legacy - -import ( - "fmt" - "strings" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/deckhouse/virtualization/api/core/v1alpha2" - "github.com/deckhouse/virtualization/api/core/v1alpha2/vmcondition" - "github.com/deckhouse/virtualization/test/e2e/internal/config" - "github.com/deckhouse/virtualization/test/e2e/internal/framework" - kc "github.com/deckhouse/virtualization/test/e2e/internal/kubectl" -) - -func WaitForVMNetworkReady(opts kc.WaitOptions) { - GinkgoHelper() - WaitConditionIsTrueByLabel(kc.ResourceVM, vmcondition.TypeNetworkReady.String(), opts) -} - -func WaitForVMRunningPhase(opts kc.WaitOptions) { - GinkgoHelper() - WaitPhaseByLabel(kc.ResourceVM, PhaseRunning, opts) -} - -var _ = Describe("VirtualMachineAdditionalNetworkInterfaces", Ordered, func() { - testCaseLabel := map[string]string{"testcase": "vm-vpc"} - var ns string - - BeforeAll(func() { - sdnEnabled, err := isSdnModuleEnabled() - if err != nil || !sdnEnabled { - Skip("Module SDN is disabled. Skipping all tests for module SDN.") - } - - kustomization := fmt.Sprintf("%s/%s", conf.TestData.VMVpc, "kustomization.yaml") - ns, err = kustomize.GetNamespace(kustomization) - Expect(err).NotTo(HaveOccurred(), "%w", err) - - CreateNamespace(ns) - }) - - AfterAll(func() { - if CurrentSpecReport().Failed() { - SaveTestCaseDump(testCaseLabel, CurrentSpecReport().LeafNodeText, ns) - } - }) - - Context("When resources are applied", func() { - It("result should be succeeded", func() { - res := kubectl.Apply(kc.ApplyOptions{ - Filename: []string{conf.TestData.VMVpc}, - FilenameOption: kc.Kustomize, - }) - Expect(res.Error()).NotTo(HaveOccurred(), res.StdErr()) - }) - }) - - Context("When virtual machines are applied", func() { - It("checks VMs phases", func() { - By("Virtual machine should be running") - WaitForVMRunningPhase(kc.WaitOptions{ - Labels: testCaseLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - }) - It("checks network availability", func() { - By("Network condition should be true") - WaitForVMNetworkReady(kc.WaitOptions{ - Labels: testCaseLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - - CheckVMConnectivityToTargetIPs(ns, testCaseLabel) - }) - }) - - Context("When virtual machine agents and network are ready", func() { - It("starts migrations", func() { - res := kubectl.List(kc.ResourceVM, kc.GetOptions{ - Labels: testCaseLabel, - Namespace: ns, - Output: "jsonpath='{.items[*].metadata.name}'", - }) - Expect(res.Error()).NotTo(HaveOccurred(), res.StdErr()) - - vms := strings.Split(res.StdOut(), " ") - MigrateVirtualMachines(testCaseLabel, ns, vms...) - }) - }) - - Context("When VMs migrations are applied", func() { - It("checks VMs and VMOPs phases", func() { - By(fmt.Sprintf("VMOPs should be in %s phases", v1alpha2.VMOPPhaseCompleted)) - WaitPhaseByLabel(kc.ResourceVMOP, string(v1alpha2.VMOPPhaseCompleted), kc.WaitOptions{ - Labels: testCaseLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - By("Virtual machines should be migrated") - WaitByLabel(kc.ResourceVM, kc.WaitOptions{ - Labels: testCaseLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - For: "'jsonpath={.status.migrationState.result}=Succeeded'", - }) - }) - - It("checks VMs external connection after migrations", func() { - res := kubectl.List(kc.ResourceVM, kc.GetOptions{ - Labels: testCaseLabel, - Namespace: ns, - Output: "jsonpath='{.items[*].metadata.name}'", - }) - Expect(res.Error()).NotTo(HaveOccurred(), res.StdErr()) - - vms := strings.Split(res.StdOut(), " ") - Expect(vms).NotTo(BeEmpty()) - - // There is a known issue with the Cilium agent check. - CheckCiliumAgents(kubectl, ns, vms...) - CheckExternalConnection(externalHost, httpStatusOk, ns, vms...) - }) - - It("checks network availability after migrations", func() { - By("Network condition should be true") - WaitForVMNetworkReady(kc.WaitOptions{ - Labels: testCaseLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - - CheckVMConnectivityToTargetIPs(ns, testCaseLabel) - }) - }) - - Context("When test is completed", func() { - It("deletes test case resources", func() { - resourcesToDelete := ResourcesToDelete{ - AdditionalResources: []AdditionalResource{ - { - Resource: kc.ResourceVMOP, - Labels: testCaseLabel, - }, - }, - } - - if config.IsCleanUpNeeded() { - resourcesToDelete.KustomizationDir = conf.TestData.VMVpc - } - - DeleteTestCaseResources(ns, resourcesToDelete) - }) - }) -}) - -func isSdnModuleEnabled() (bool, error) { - sdnModule, err := framework.NewFramework("").GetModuleConfig("sdn") - if err != nil { - return false, err - } - enabled := sdnModule.Spec.Enabled - - return enabled != nil && *enabled, nil -} - -func CheckVMConnectivityToTargetIPs(ns string, testCaseLabel map[string]string) { - var vmList v1alpha2.VirtualMachineList - err := GetObjects(kc.ResourceVM, &vmList, kc.GetOptions{ - Labels: testCaseLabel, - Namespace: ns, - }) - Expect(err).ShouldNot(HaveOccurred()) - - for _, vm := range vmList.Items { - switch { - case strings.Contains(vm.Name, "foo"): - By(fmt.Sprintf("VM %q should have connectivity to 192.168.1.10 (target: vm-bar)", vm.Name)) - CheckResultSSHCommand(ns, vm.Name, `ping -c 2 -W 2 -w 5 -q 192.168.1.10 2>&1 | grep -o "[0-9]\+%\s*packet loss"`, "0% packet loss") - case strings.Contains(vm.Name, "bar"): - By(fmt.Sprintf("VM %q should have connectivity to 192.168.1.11 (target: vm-foo)", vm.Name)) - CheckResultSSHCommand(ns, vm.Name, `ping -c 2 -W 2 -w 5 -q 192.168.1.11 2>&1 | grep -o "[0-9]\+%\s*packet loss"`, "0% packet loss") - } - } -} diff --git a/test/e2e/vm/additional_network_interfaces.go b/test/e2e/vm/additional_network_interfaces.go new file mode 100644 index 0000000000..3675bea47c --- /dev/null +++ b/test/e2e/vm/additional_network_interfaces.go @@ -0,0 +1,260 @@ +/* +Copyright 2026 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 vm + +import ( + "context" + "fmt" + "strings" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/utils/ptr" + crclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/deckhouse/virtualization-controller/pkg/builder/vd" + "github.com/deckhouse/virtualization-controller/pkg/builder/vm" + "github.com/deckhouse/virtualization-controller/pkg/builder/vmop" + "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vmcondition" + "github.com/deckhouse/virtualization/test/e2e/internal/framework" + "github.com/deckhouse/virtualization/test/e2e/internal/network" + "github.com/deckhouse/virtualization/test/e2e/internal/object" + "github.com/deckhouse/virtualization/test/e2e/internal/util" +) + +const ( + // IPs on additional network interface for connectivity check between VMs. + // When VM has Main network, additional interface is eth1; otherwise it's eth0. + vmFooAdditionalIP = "192.168.1.10" + vmBarAdditionalIP = "192.168.1.11" +) + +type additionalNetworkTestCase struct { + vmBarHasMainNetwork bool +} + +var _ = Describe("VirtualMachineAdditionalNetworkInterfaces", func() { + var ( + vdFooRoot *v1alpha2.VirtualDisk + vdBarRoot *v1alpha2.VirtualDisk + vmFoo *v1alpha2.VirtualMachine + vmBar *v1alpha2.VirtualMachine + + f = framework.NewFramework("vm-additional-network") + ) + + BeforeEach(func() { + DeferCleanup(f.After) + + f.Before() + + if !util.IsSdnModuleEnabled(f) { + Skip("SDN module is disabled. Skipping test.") + } + + Expect(util.IsClusterNetworkExists(f)).To(BeTrue(), + fmt.Sprintf("Cluster network %s does not exist. Create it first: %s", util.ClusterNetworkName, util.ClusterNetworkCreateCommand)) + }) + + DescribeTable("verifies additional network interfaces and connectivity before and after migration", + func(tc additionalNetworkTestCase) { + By("Environment preparation", func() { + ns := f.Namespace().Name + + vdFooRoot = vd.New( + vd.WithName("vd-foo-root"), + vd.WithNamespace(ns), + vd.WithSize(ptr.To(resource.MustParse("512Mi"))), + vd.WithDataSourceHTTP(&v1alpha2.DataSourceHTTP{ + URL: object.ImageURLAlpineUEFIPerf, + }), + ) + vdBarRoot = vd.New( + vd.WithName("vd-bar-root"), + vd.WithNamespace(ns), + vd.WithSize(ptr.To(resource.MustParse("512Mi"))), + vd.WithDataSourceHTTP(&v1alpha2.DataSourceHTTP{ + URL: object.ImageURLAlpineUEFIPerf, + }), + ) + + // vm-foo always has Main + ClusterNetwork so we can SSH to it. + vmFoo = buildVMWithNetworks("vm-foo", ns, vdFooRoot.Name, vmFooAdditionalIP, true) + vmBar = buildVMWithNetworks("vm-bar", ns, vdBarRoot.Name, vmBarAdditionalIP, tc.vmBarHasMainNetwork) + + err := f.CreateWithDeferredDeletion(context.Background(), vdFooRoot, vdBarRoot, vmFoo, vmBar) + Expect(err).NotTo(HaveOccurred()) + + util.UntilObjectPhase(string(v1alpha2.MachineRunning), framework.LongTimeout, vmFoo, vmBar) + util.UntilVMAgentReady(crclient.ObjectKeyFromObject(vmFoo), framework.LongTimeout) + if tc.vmBarHasMainNetwork { + util.UntilVMAgentReady(crclient.ObjectKeyFromObject(vmBar), framework.LongTimeout) + } + }) + + By("Wait for additional network interfaces to be ready", func() { + util.UntilConditionStatus(vmcondition.TypeNetworkReady.String(), "True", framework.LongTimeout, vmFoo, vmBar) + }) + + By("Check connectivity between VMs via additional network", func() { + checkConnectivityBetweenVMs(f, vmFoo, vmBar, tc.vmBarHasMainNetwork) + }) + + By("Create VMOPs to trigger migration", func() { + util.MigrateVirtualMachine(f, vmFoo, vmop.WithGenerateName("vmop-migrate-foo-")) + util.MigrateVirtualMachine(f, vmBar, vmop.WithGenerateName("vmop-migrate-bar-")) + }) + + By("Wait for migration to complete", func() { + util.UntilVMMigrationSucceeded(crclient.ObjectKeyFromObject(vmFoo), framework.LongTimeout) + util.UntilVMMigrationSucceeded(crclient.ObjectKeyFromObject(vmBar), framework.LongTimeout) + }) + + By("Check Cilium agents after migration", func() { + err := network.CheckCiliumAgents(context.Background(), f.Clients.Kubectl(), vmFoo.Name, f.Namespace().Name) + Expect(err).NotTo(HaveOccurred(), "Cilium agents check for VM %s", vmFoo.Name) + + if tc.vmBarHasMainNetwork { + err = network.CheckCiliumAgents(context.Background(), f.Clients.Kubectl(), vmBar.Name, f.Namespace().Name) + Expect(err).NotTo(HaveOccurred(), "Cilium agents check for VM %s", vmBar.Name) + } + }) + + By("Check VM can reach external network after migration", func() { + network.CheckExternalConnectivity(f, vmFoo.Name, network.ExternalHost, network.HTTPStatusOk) + + if tc.vmBarHasMainNetwork { + network.CheckExternalConnectivity(f, vmBar.Name, network.ExternalHost, network.HTTPStatusOk) + } + }) + + By("Wait for additional network interfaces to be ready after migration", func() { + util.UntilConditionStatus(vmcondition.TypeNetworkReady.String(), "True", framework.LongTimeout, vmFoo, vmBar) + }) + + By("Check connectivity between VMs via additional network after migration", func() { + checkConnectivityBetweenVMs(f, vmFoo, vmBar, tc.vmBarHasMainNetwork) + }) + }, + Entry("Main + additional network", additionalNetworkTestCase{vmBarHasMainNetwork: true}), + Entry("Only additional network (vm-bar without Main)", additionalNetworkTestCase{vmBarHasMainNetwork: false}), + ) +}) + +// buildVMWithNetworks creates a VM with optional Main + ClusterNetwork. +// If hasMain is false, only ClusterNetwork is added (VM without Main network). +// The additional network interface is eth1 when hasMain is true, eth0 otherwise. +func buildVMWithNetworks(name, ns, vdRootName, additionalIP string, hasMain bool) *v1alpha2.VirtualMachine { + opts := []vm.Option{ + vm.WithName(name), + vm.WithNamespace(ns), + vm.WithBootloader(v1alpha2.EFI), + vm.WithCPU(1, ptr.To("5%")), + vm.WithMemory(resource.MustParse("256Mi")), + vm.WithRestartApprovalMode(v1alpha2.Manual), + vm.WithVirtualMachineClass(object.DefaultVMClass), + vm.WithLiveMigrationPolicy(v1alpha2.PreferSafeMigrationPolicy), + vm.WithProvisioningUserData(cloudInitAdditionalNetwork(additionalIP, hasMain)), + vm.WithBlockDeviceRefs(v1alpha2.BlockDeviceSpecRef{ + Kind: v1alpha2.VirtualDiskKind, + Name: vdRootName, + }), + } + if hasMain { + opts = append(opts, + vm.WithNetwork(v1alpha2.NetworksSpec{Type: v1alpha2.NetworksTypeMain}), + ) + } + opts = append(opts, + vm.WithNetwork(v1alpha2.NetworksSpec{ + Type: v1alpha2.NetworksTypeClusterNetwork, + Name: util.ClusterNetworkName, + }), + ) + return vm.New(opts...) +} + +// cloudInitAdditionalNetwork returns cloud-init that configures the additional network interface with the given static IP. +// When hasMain is true, the additional interface is eth1; when false, it's eth0. +func cloudInitAdditionalNetwork(additionalIP string, hasMain bool) string { + ifaceName := "eth0" + if hasMain { + ifaceName = "eth1" + } + return fmt.Sprintf(`#cloud-config +ssh_pwauth: True +users: + - name: cloud + passwd: $6$rounds=4096$vln/.aPHBOI7BMYR$bBMkqQvuGs5Gyd/1H5DP4m9HjQSy.kgrxpaGEHwkX7KEFV8BS.HZWPitAtZ2Vd8ZqIZRqmlykRCagTgPejt1i. + shell: /bin/bash + sudo: ALL=(ALL) NOPASSWD:ALL + chpasswd: { expire: False } + lock_passwd: False + ssh_authorized_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxcXHmwaGnJ8scJaEN5RzklBPZpVSic4GdaAsKjQoeA your_email@example.com +packages: + - qemu-guest-agent +write_files: + - path: /etc/network/interfaces + append: true + content: | + + auto %s + iface %s inet static + address %s + netmask 255.255.255.0 +runcmd: + - sudo rc-update add qemu-guest-agent default + - sudo rc-service qemu-guest-agent start + - sudo /etc/init.d/networking restart + - chown -R cloud:cloud /home/cloud +`, ifaceName, ifaceName, additionalIP) +} + +func checkConnectivityBetweenVMs(f *framework.Framework, vmFoo, vmBar *v1alpha2.VirtualMachine, vmBarHasMainNetwork bool) { + GinkgoHelper() + + pingCmd := "ping -c 2 -W 2 -w 5 -q %s 2>&1 | grep -o \"[0-9]\\+%%\\s*packet loss\"" // %% -> % in output + expectedOut := "0% packet loss" + + By(fmt.Sprintf("VM %s should have connectivity to %s (vm-bar)", vmFoo.Name, vmBarAdditionalIP)) + checkResultSSHCommand(f, vmFoo.Name, vmFoo.Namespace, fmt.Sprintf(pingCmd, vmBarAdditionalIP), expectedOut) + + if vmBarHasMainNetwork { + By(fmt.Sprintf("VM %s should have connectivity to %s (vm-foo)", vmBar.Name, vmFooAdditionalIP)) + checkResultSSHCommand(f, vmBar.Name, vmBar.Namespace, fmt.Sprintf(pingCmd, vmFooAdditionalIP), expectedOut) + } +} + +const ( + Interval = 5 * time.Second + Timeout = 90 * time.Second +) + +func checkResultSSHCommand(f *framework.Framework, vmName, vmNamespace, cmd, equal string) { + GinkgoHelper() + Eventually(func() (string, error) { + res, err := f.SSHCommand(vmName, vmNamespace, cmd) + if err != nil { + return "", fmt.Errorf("cmd: %s\nstderr: %w", cmd, err) + } + return strings.TrimSpace(res), nil + }).WithTimeout(Timeout).WithPolling(Interval).Should(Equal(equal)) +}