Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions images/virtualization-artifact/pkg/builder/vm/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,9 @@ func WithRestartApprovalMode(restartApprovalMode v1alpha2.RestartApprovalMode) O
vm.Spec.Disruptions.RestartApprovalMode = restartApprovalMode
}
}

func WithRunPolicy(runPolicy v1alpha2.RunPolicy) Option {
return func(vm *v1alpha2.VirtualMachine) {
vm.Spec.RunPolicy = runPolicy
}
}
18 changes: 18 additions & 0 deletions images/virtualization-artifact/pkg/builder/vmop/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,21 @@ func WithForce(force *bool) Option {
vmop.Spec.Force = force
}
}

func WithVMOPRestoreMode(mode v1alpha2.SnapshotOperationMode) Option {
return func(vmop *v1alpha2.VirtualMachineOperation) {
if vmop.Spec.Restore == nil {
vmop.Spec.Restore = &v1alpha2.VirtualMachineOperationRestoreSpec{}
}
vmop.Spec.Restore.Mode = mode
}
}

func WithVirtualMachineSnapshotName(name string) Option {
return func(vmop *v1alpha2.VirtualMachineOperation) {
if vmop.Spec.Restore == nil {
vmop.Spec.Restore = &v1alpha2.VirtualMachineOperationRestoreSpec{}
}
vmop.Spec.Restore.VirtualMachineSnapshotName = name
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ import (
)

func New(options ...Option) *v1alpha2.VirtualMachineSnapshot {
vdsnapshot := NewEmpty("", "")
ApplyOptions(vdsnapshot, options...)
return vdsnapshot
vmsnapshot := NewEmpty("", "")
ApplyOptions(vmsnapshot, options)
return vmsnapshot
}

func ApplyOptions(vmsnapshot *v1alpha2.VirtualMachineSnapshot, opts ...Option) {
func ApplyOptions(vmsnapshot *v1alpha2.VirtualMachineSnapshot, opts []Option) {
if vmsnapshot == nil {
return
}
Expand All @@ -47,5 +47,9 @@ func NewEmpty(name, namespace string) *v1alpha2.VirtualMachineSnapshot {
Name: name,
Namespace: namespace,
},
Spec: v1alpha2.VirtualMachineSnapshotSpec{
RequiredConsistency: true, // Default value from kubebuilder annotation
KeepIPAddress: v1alpha2.KeepIPAddressAlways, // Default value from kubebuilder annotation
},
}
}
1 change: 1 addition & 0 deletions test/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/deckhouse/virtualization/test/e2e/legacy"
_ "github.com/deckhouse/virtualization/test/e2e/snapshot"
_ "github.com/deckhouse/virtualization/test/e2e/vm"
_ "github.com/deckhouse/virtualization/test/e2e/vmop"
)

func TestE2E(t *testing.T) {
Expand Down
25 changes: 25 additions & 0 deletions test/e2e/internal/label/label.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
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 label

import (
. "github.com/onsi/ginkgo/v2"
)

func Slow() Labels {
return Label("Slow")
}
48 changes: 30 additions & 18 deletions test/e2e/internal/object/vd.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,52 +23,64 @@ import (
"github.com/deckhouse/virtualization/api/core/v1alpha2"
)

func NewGeneratedVDFromCVI(prefix, namespace string, cvi *v1alpha2.ClusterVirtualImage) *v1alpha2.VirtualDisk {
return vd.New(
func NewGeneratedVDFromCVI(prefix, namespace string, cvi *v1alpha2.ClusterVirtualImage, opts ...vd.Option) *v1alpha2.VirtualDisk {
baseOpts := []vd.Option{
vd.WithGenerateName(prefix),
vd.WithNamespace(namespace),
vd.WithDataSourceObjectRefFromCVI(cvi),
)
}
baseOpts = append(baseOpts, opts...)
return vd.New(baseOpts...)
}

func NewVDFromCVI(name, namespace string, cvi *v1alpha2.ClusterVirtualImage) *v1alpha2.VirtualDisk {
return vd.New(
func NewVDFromCVI(name, namespace string, cvi *v1alpha2.ClusterVirtualImage, opts ...vd.Option) *v1alpha2.VirtualDisk {
baseOpts := []vd.Option{
vd.WithName(name),
vd.WithNamespace(namespace),
vd.WithDataSourceObjectRefFromCVI(cvi),
)
}
baseOpts = append(baseOpts, opts...)
return vd.New(baseOpts...)
}

func NewGeneratedVDFromVI(prefix, namespace string, vi *v1alpha2.VirtualImage) *v1alpha2.VirtualDisk {
return vd.New(
func NewGeneratedVDFromVI(prefix, namespace string, vi *v1alpha2.VirtualImage, opts ...vd.Option) *v1alpha2.VirtualDisk {
baseOpts := []vd.Option{
vd.WithGenerateName(prefix),
vd.WithNamespace(namespace),
vd.WithDataSourceObjectRefFromVI(vi),
)
}
baseOpts = append(baseOpts, opts...)
return vd.New(baseOpts...)
}

func NewVDFromVI(name, namespace string, vi *v1alpha2.VirtualImage) *v1alpha2.VirtualDisk {
return vd.New(
func NewVDFromVI(name, namespace string, vi *v1alpha2.VirtualImage, opts ...vd.Option) *v1alpha2.VirtualDisk {
baseOpts := []vd.Option{
vd.WithName(name),
vd.WithNamespace(namespace),
vd.WithDataSourceObjectRefFromVI(vi),
)
}
baseOpts = append(baseOpts, opts...)
return vd.New(baseOpts...)
}

func NewBlankVD(name, namespace string, storageClass *string, size *resource.Quantity) *v1alpha2.VirtualDisk {
return vd.New(
func NewBlankVD(name, namespace string, storageClass *string, size *resource.Quantity, opts ...vd.Option) *v1alpha2.VirtualDisk {
baseOpts := []vd.Option{
vd.WithName(name),
vd.WithNamespace(namespace),
vd.WithPersistentVolumeClaim(storageClass, size),
)
}
baseOpts = append(baseOpts, opts...)
return vd.New(baseOpts...)
}

func NewGeneratedHTTPVDUbuntu(prefix, namespace string) *v1alpha2.VirtualDisk {
return vd.New(
func NewGeneratedHTTPVDUbuntu(prefix, namespace string, opts ...vd.Option) *v1alpha2.VirtualDisk {
baseOpts := []vd.Option{
vd.WithGenerateName(prefix),
vd.WithNamespace(namespace),
vd.WithDataSourceHTTP(&v1alpha2.DataSourceHTTP{
URL: ImageURLUbuntu,
}),
)
}
baseOpts = append(baseOpts, opts...)
return vd.New(baseOpts...)
}
148 changes: 148 additions & 0 deletions test/e2e/internal/util/block_device.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
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 util

import (
"context"
"errors"
"fmt"
"strings"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
virtv1 "kubevirt.io/api/core/v1"

"github.com/deckhouse/virtualization/api/core/v1alpha2"
"github.com/deckhouse/virtualization/test/e2e/internal/framework"
)

func GetBlockDevicePath(f *framework.Framework, vm *v1alpha2.VirtualMachine, bdKind v1alpha2.BlockDeviceKind, bdName string) string {
GinkgoHelper()

serial, ok := GetBlockDeviceSerialNumber(vm, bdKind, bdName)
Expect(ok).To(BeTrue(), "failed to get block device serial number")

devicePath, err := GetBlockDeviceBySerial(f, vm, serial)
Expect(err).NotTo(HaveOccurred(), fmt.Errorf("failed to get device by serial: %w", err))
return devicePath
}

func CreateBlockDeviceFilesystem(f *framework.Framework, vm *v1alpha2.VirtualMachine, bdKind v1alpha2.BlockDeviceKind, bdName, fsType string) {
GinkgoHelper()

serial, ok := GetBlockDeviceSerialNumber(vm, bdKind, bdName)
Expect(ok).To(BeTrue(), "failed to get block device serial number")

devicePath, err := GetBlockDeviceBySerial(f, vm, serial)
Expect(err).NotTo(HaveOccurred(), fmt.Errorf("failed to get device by serial: %w", err))

_, err = f.SSHCommand(vm.Name, vm.Namespace, fmt.Sprintf("sudo mkfs.%s %s", fsType, devicePath))
Expect(err).NotTo(HaveOccurred())
}

func MountBlockDevice(f *framework.Framework, vm *v1alpha2.VirtualMachine, bdKind v1alpha2.BlockDeviceKind, bdName string) {
GinkgoHelper()

serial, ok := GetBlockDeviceSerialNumber(vm, bdKind, bdName)
Expect(ok).To(BeTrue(), "failed to get block device serial number")

devicePath, err := GetBlockDeviceBySerial(f, vm, serial)
Expect(err).NotTo(HaveOccurred(), fmt.Errorf("failed to get device by serial: %w", err))

_, err = f.SSHCommand(vm.Name, vm.Namespace, fmt.Sprintf("sudo mount %s /mnt", devicePath))
Expect(err).NotTo(HaveOccurred())

cmd := fmt.Sprintf(`UUID=$(lsblk -o SERIAL,UUID | grep %s | awk "{print \$2}"); echo "UUID=$UUID /mnt ext4 defaults 0 0" | sudo tee -a /etc/fstab`, serial)
_, err = f.SSHCommand(vm.Name, vm.Namespace, cmd)
Expect(err).NotTo(HaveOccurred())
}

func GetBlockDeviceBySerial(f *framework.Framework, vm *v1alpha2.VirtualMachine, serial string) (string, error) {
cmdOut, err := f.SSHCommand(vm.Name, vm.Namespace, fmt.Sprintf("sudo lsblk -o PATH,SERIAL | grep %s | awk \"{print \\$1, \\$2}\"", serial))
if err != nil {
return "", err
}

cmdLines := strings.Split(strings.TrimSpace(cmdOut), "\n")
if len(cmdLines) == 0 {
return "", errors.New("shell out is empty")
}

columns := strings.Split(strings.TrimSpace(cmdLines[0]), " ")
if len(columns) != 2 {
return "", errors.New("shell out columns mismatch")
}

if columns[1] == serial {
return columns[0], nil
}

return "", errors.New("no block device found")
}

func GetBlockDeviceSerialNumber(vm *v1alpha2.VirtualMachine, bdKind v1alpha2.BlockDeviceKind, bdName string) (string, bool) {
unstructuredVMI, err := framework.GetClients().DynamicClient().Resource(schema.GroupVersionResource{
Group: "internal.virtualization.deckhouse.io",
Version: "v1",
Resource: "internalvirtualizationvirtualmachineinstances",
}).Namespace(vm.Namespace).Get(context.Background(), vm.Name, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred())

var kvvmi virtv1.VirtualMachineInstance
err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredVMI.Object, &kvvmi)
Expect(err).NotTo(HaveOccurred())

var blockDeviceName string
switch bdKind {
case v1alpha2.DiskDevice:
blockDeviceName = fmt.Sprintf("vd-%s", bdName)
case v1alpha2.ImageDevice:
blockDeviceName = fmt.Sprintf("vi-%s", bdName)
case v1alpha2.ClusterImageDevice:
blockDeviceName = fmt.Sprintf("cvi-%s", bdName)
default:
Fail(fmt.Sprintf("unknown block device kind %q", bdKind))
}

for _, disk := range kvvmi.Spec.Domain.Devices.Disks {
if disk.Name == blockDeviceName {
return disk.Serial, true
}
}

return "", false
}

func WriteFile(f *framework.Framework, vm *v1alpha2.VirtualMachine, path, value string) {
GinkgoHelper()

// Escape single quotes in value to prevent command injection.
escapedValue := strings.ReplaceAll(value, "'", "'\"'\"'")
_, err := f.SSHCommand(vm.Name, vm.Namespace, fmt.Sprintf("sudo bash -c \"echo '%s' > %s\"", escapedValue, path))
Expect(err).NotTo(HaveOccurred())
}

func ReadFile(f *framework.Framework, vm *v1alpha2.VirtualMachine, path string) string {
GinkgoHelper()

cmdOut, err := f.SSHCommand(vm.Name, vm.Namespace, fmt.Sprintf("sudo cat %s", path))
Expect(err).NotTo(HaveOccurred())
return strings.TrimSpace(cmdOut)
}
16 changes: 16 additions & 0 deletions test/e2e/internal/util/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,22 @@ func MigrateVirtualMachine(f *framework.Framework, vm *v1alpha2.VirtualMachine,
Expect(err).NotTo(HaveOccurred())
}

func StartVirtualMachine(f *framework.Framework, vm *v1alpha2.VirtualMachine, options ...vmopbuilder.Option) {
GinkgoHelper()

opts := []vmopbuilder.Option{
vmopbuilder.WithGenerateName("vmop-e2e-"),
vmopbuilder.WithNamespace(vm.Namespace),
vmopbuilder.WithType(v1alpha2.VMOPTypeStart),
vmopbuilder.WithVirtualMachine(vm.Name),
}
opts = append(opts, options...)
vmop := vmopbuilder.New(opts...)

err := f.CreateWithDeferredDeletion(context.Background(), vmop)
Expect(err).NotTo(HaveOccurred())
}

func StopVirtualMachineFromOS(f *framework.Framework, vm *v1alpha2.VirtualMachine) error {
_, err := f.SSHCommand(vm.Name, vm.Namespace, "sudo init 0")
if err != nil && strings.Contains(err.Error(), "unexpected EOF") {
Expand Down
Loading
Loading