Skip to content

Commit 5001dae

Browse files
authored
test(vmop): add restore vmop e2e test (#1648)
Signed-off-by: Valeriy Khorunzhin <valeriy.khorunzhin@flant.com>
1 parent fee241e commit 5001dae

File tree

12 files changed

+929
-24
lines changed

12 files changed

+929
-24
lines changed

images/virtualization-artifact/pkg/builder/vm/option.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,15 @@ func WithRestartApprovalMode(restartApprovalMode v1alpha2.RestartApprovalMode) O
127127
vm.Spec.Disruptions.RestartApprovalMode = restartApprovalMode
128128
}
129129
}
130+
131+
func WithRunPolicy(runPolicy v1alpha2.RunPolicy) Option {
132+
return func(vm *v1alpha2.VirtualMachine) {
133+
vm.Spec.RunPolicy = runPolicy
134+
}
135+
}
136+
137+
func WithNetwork(network v1alpha2.NetworksSpec) Option {
138+
return func(vm *v1alpha2.VirtualMachine) {
139+
vm.Spec.Networks = append(vm.Spec.Networks, network)
140+
}
141+
}

images/virtualization-artifact/pkg/builder/vmop/option.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,21 @@ func WithForce(force *bool) Option {
5151
vmop.Spec.Force = force
5252
}
5353
}
54+
55+
func WithVMOPRestoreMode(mode v1alpha2.SnapshotOperationMode) Option {
56+
return func(vmop *v1alpha2.VirtualMachineOperation) {
57+
if vmop.Spec.Restore == nil {
58+
vmop.Spec.Restore = &v1alpha2.VirtualMachineOperationRestoreSpec{}
59+
}
60+
vmop.Spec.Restore.Mode = mode
61+
}
62+
}
63+
64+
func WithVirtualMachineSnapshotName(name string) Option {
65+
return func(vmop *v1alpha2.VirtualMachineOperation) {
66+
if vmop.Spec.Restore == nil {
67+
vmop.Spec.Restore = &v1alpha2.VirtualMachineOperationRestoreSpec{}
68+
}
69+
vmop.Spec.Restore.VirtualMachineSnapshotName = name
70+
}
71+
}

images/virtualization-artifact/pkg/builder/vmsnapshot/vdsnapshot.go renamed to images/virtualization-artifact/pkg/builder/vmsnapshot/vmsnapshot.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ import (
2323
)
2424

2525
func New(options ...Option) *v1alpha2.VirtualMachineSnapshot {
26-
vdsnapshot := NewEmpty("", "")
27-
ApplyOptions(vdsnapshot, options...)
28-
return vdsnapshot
26+
vmsnapshot := NewEmpty("", "")
27+
ApplyOptions(vmsnapshot, options)
28+
return vmsnapshot
2929
}
3030

31-
func ApplyOptions(vmsnapshot *v1alpha2.VirtualMachineSnapshot, opts ...Option) {
31+
func ApplyOptions(vmsnapshot *v1alpha2.VirtualMachineSnapshot, opts []Option) {
3232
if vmsnapshot == nil {
3333
return
3434
}
@@ -47,5 +47,9 @@ func NewEmpty(name, namespace string) *v1alpha2.VirtualMachineSnapshot {
4747
Name: name,
4848
Namespace: namespace,
4949
},
50+
Spec: v1alpha2.VirtualMachineSnapshotSpec{
51+
RequiredConsistency: true, // Default value from kubebuilder annotation
52+
KeepIPAddress: v1alpha2.KeepIPAddressAlways, // Default value from kubebuilder annotation
53+
},
5054
}
5155
}

test/e2e/Taskfile.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@ tasks:
5050
cmds:
5151
- |
5252
go tool ginkgo -v \
53-
--label-filter="!Slow" \
5453
--race \
54+
{{if not .FOCUS -}}
55+
--label-filter="!Slow" \
56+
{{end -}}
5557
{{if .LABELS -}}
5658
--label-filter="{{ .LABELS }}" \
5759
{{end -}}

test/e2e/e2e_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/deckhouse/virtualization/test/e2e/legacy"
3030
_ "github.com/deckhouse/virtualization/test/e2e/snapshot"
3131
_ "github.com/deckhouse/virtualization/test/e2e/vm"
32+
_ "github.com/deckhouse/virtualization/test/e2e/vmop"
3233
)
3334

3435
func TestE2E(t *testing.T) {

test/e2e/internal/label/label.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
Copyright 2025 Flant JSC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package label
18+
19+
import (
20+
. "github.com/onsi/ginkgo/v2"
21+
)
22+
23+
func Slow() Labels {
24+
return Label("Slow")
25+
}

test/e2e/internal/object/vd.go

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,52 +23,64 @@ import (
2323
"github.com/deckhouse/virtualization/api/core/v1alpha2"
2424
)
2525

26-
func NewGeneratedVDFromCVI(prefix, namespace string, cvi *v1alpha2.ClusterVirtualImage) *v1alpha2.VirtualDisk {
27-
return vd.New(
26+
func NewGeneratedVDFromCVI(prefix, namespace string, cvi *v1alpha2.ClusterVirtualImage, opts ...vd.Option) *v1alpha2.VirtualDisk {
27+
baseOpts := []vd.Option{
2828
vd.WithGenerateName(prefix),
2929
vd.WithNamespace(namespace),
3030
vd.WithDataSourceObjectRefFromCVI(cvi),
31-
)
31+
}
32+
baseOpts = append(baseOpts, opts...)
33+
return vd.New(baseOpts...)
3234
}
3335

34-
func NewVDFromCVI(name, namespace string, cvi *v1alpha2.ClusterVirtualImage) *v1alpha2.VirtualDisk {
35-
return vd.New(
36+
func NewVDFromCVI(name, namespace string, cvi *v1alpha2.ClusterVirtualImage, opts ...vd.Option) *v1alpha2.VirtualDisk {
37+
baseOpts := []vd.Option{
3638
vd.WithName(name),
3739
vd.WithNamespace(namespace),
3840
vd.WithDataSourceObjectRefFromCVI(cvi),
39-
)
41+
}
42+
baseOpts = append(baseOpts, opts...)
43+
return vd.New(baseOpts...)
4044
}
4145

42-
func NewGeneratedVDFromVI(prefix, namespace string, vi *v1alpha2.VirtualImage) *v1alpha2.VirtualDisk {
43-
return vd.New(
46+
func NewGeneratedVDFromVI(prefix, namespace string, vi *v1alpha2.VirtualImage, opts ...vd.Option) *v1alpha2.VirtualDisk {
47+
baseOpts := []vd.Option{
4448
vd.WithGenerateName(prefix),
4549
vd.WithNamespace(namespace),
4650
vd.WithDataSourceObjectRefFromVI(vi),
47-
)
51+
}
52+
baseOpts = append(baseOpts, opts...)
53+
return vd.New(baseOpts...)
4854
}
4955

50-
func NewVDFromVI(name, namespace string, vi *v1alpha2.VirtualImage) *v1alpha2.VirtualDisk {
51-
return vd.New(
56+
func NewVDFromVI(name, namespace string, vi *v1alpha2.VirtualImage, opts ...vd.Option) *v1alpha2.VirtualDisk {
57+
baseOpts := []vd.Option{
5258
vd.WithName(name),
5359
vd.WithNamespace(namespace),
5460
vd.WithDataSourceObjectRefFromVI(vi),
55-
)
61+
}
62+
baseOpts = append(baseOpts, opts...)
63+
return vd.New(baseOpts...)
5664
}
5765

58-
func NewBlankVD(name, namespace string, storageClass *string, size *resource.Quantity) *v1alpha2.VirtualDisk {
59-
return vd.New(
66+
func NewBlankVD(name, namespace string, storageClass *string, size *resource.Quantity, opts ...vd.Option) *v1alpha2.VirtualDisk {
67+
baseOpts := []vd.Option{
6068
vd.WithName(name),
6169
vd.WithNamespace(namespace),
6270
vd.WithPersistentVolumeClaim(storageClass, size),
63-
)
71+
}
72+
baseOpts = append(baseOpts, opts...)
73+
return vd.New(baseOpts...)
6474
}
6575

66-
func NewGeneratedHTTPVDUbuntu(prefix, namespace string) *v1alpha2.VirtualDisk {
67-
return vd.New(
76+
func NewGeneratedHTTPVDUbuntu(prefix, namespace string, opts ...vd.Option) *v1alpha2.VirtualDisk {
77+
baseOpts := []vd.Option{
6878
vd.WithGenerateName(prefix),
6979
vd.WithNamespace(namespace),
7080
vd.WithDataSourceHTTP(&v1alpha2.DataSourceHTTP{
7181
URL: ImageURLUbuntu,
7282
}),
73-
)
83+
}
84+
baseOpts = append(baseOpts, opts...)
85+
return vd.New(baseOpts...)
7486
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/*
2+
Copyright 2025 Flant JSC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package util
18+
19+
import (
20+
"context"
21+
"errors"
22+
"fmt"
23+
"strings"
24+
25+
. "github.com/onsi/ginkgo/v2"
26+
. "github.com/onsi/gomega"
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/apimachinery/pkg/runtime"
29+
"k8s.io/apimachinery/pkg/runtime/schema"
30+
virtv1 "kubevirt.io/api/core/v1"
31+
32+
"github.com/deckhouse/virtualization/api/core/v1alpha2"
33+
"github.com/deckhouse/virtualization/test/e2e/internal/framework"
34+
)
35+
36+
func GetBlockDevicePath(f *framework.Framework, vm *v1alpha2.VirtualMachine, bdKind v1alpha2.BlockDeviceKind, bdName string) string {
37+
GinkgoHelper()
38+
39+
serial, ok := GetBlockDeviceSerialNumber(vm, bdKind, bdName)
40+
Expect(ok).To(BeTrue(), "failed to get block device serial number")
41+
42+
devicePath, err := GetBlockDeviceBySerial(f, vm, serial)
43+
Expect(err).NotTo(HaveOccurred(), "failed to get device by serial")
44+
return devicePath
45+
}
46+
47+
func CreateBlockDeviceFilesystem(f *framework.Framework, vm *v1alpha2.VirtualMachine, bdKind v1alpha2.BlockDeviceKind, bdName, fsType string) {
48+
GinkgoHelper()
49+
50+
serial, ok := GetBlockDeviceSerialNumber(vm, bdKind, bdName)
51+
Expect(ok).To(BeTrue(), "failed to get block device serial number")
52+
53+
devicePath, err := GetBlockDeviceBySerial(f, vm, serial)
54+
Expect(err).NotTo(HaveOccurred(), "failed to get device by serial")
55+
56+
_, err = f.SSHCommand(vm.Name, vm.Namespace, fmt.Sprintf("sudo mkfs.%s %s", fsType, devicePath))
57+
Expect(err).NotTo(HaveOccurred())
58+
}
59+
60+
func MountBlockDevice(f *framework.Framework, vm *v1alpha2.VirtualMachine, bdKind v1alpha2.BlockDeviceKind, bdName, mountPoint string) {
61+
GinkgoHelper()
62+
63+
serial, ok := GetBlockDeviceSerialNumber(vm, bdKind, bdName)
64+
Expect(ok).To(BeTrue(), "failed to get block device serial number")
65+
66+
devicePath, err := GetBlockDeviceBySerial(f, vm, serial)
67+
Expect(err).NotTo(HaveOccurred(), "failed to get device by serial")
68+
69+
_, err = f.SSHCommand(vm.Name, vm.Namespace, fmt.Sprintf("sudo mount %s %s", devicePath, mountPoint))
70+
Expect(err).NotTo(HaveOccurred())
71+
}
72+
73+
func UnmountBlockDevice(f *framework.Framework, vm *v1alpha2.VirtualMachine, mountPoint string) {
74+
GinkgoHelper()
75+
76+
_, err := f.SSHCommand(vm.Name, vm.Namespace, fmt.Sprintf("sudo umount %s", mountPoint))
77+
Expect(err).NotTo(HaveOccurred())
78+
}
79+
80+
func RegisterFstabEntry(f *framework.Framework, vm *v1alpha2.VirtualMachine, bdKind v1alpha2.BlockDeviceKind, bdName string) {
81+
GinkgoHelper()
82+
83+
serial, ok := GetBlockDeviceSerialNumber(vm, bdKind, bdName)
84+
Expect(ok).To(BeTrue(), "failed to get block device serial number")
85+
86+
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)
87+
_, err := f.SSHCommand(vm.Name, vm.Namespace, cmd)
88+
Expect(err).NotTo(HaveOccurred())
89+
}
90+
91+
func GetBlockDeviceHash(f *framework.Framework, vm *v1alpha2.VirtualMachine, bdKind v1alpha2.BlockDeviceKind, bdName string) string {
92+
GinkgoHelper()
93+
94+
serial, ok := GetBlockDeviceSerialNumber(vm, bdKind, bdName)
95+
Expect(ok).To(BeTrue(), "failed to get block device serial number")
96+
97+
devicePath, err := GetBlockDeviceBySerial(f, vm, serial)
98+
Expect(err).NotTo(HaveOccurred(), "failed to get device by serial")
99+
100+
// We use dd to ensure the entire disk is read.
101+
cmdOut, err := f.SSHCommand(vm.Name, vm.Namespace, fmt.Sprintf("sudo dd if=%s bs=4M | sha256sum | awk \"{print \\$1}\"", devicePath))
102+
Expect(err).NotTo(HaveOccurred())
103+
return strings.TrimSpace(cmdOut)
104+
}
105+
106+
func GetBlockDeviceBySerial(f *framework.Framework, vm *v1alpha2.VirtualMachine, serial string) (string, error) {
107+
cmdOut, err := f.SSHCommand(vm.Name, vm.Namespace, fmt.Sprintf("sudo lsblk -o PATH,SERIAL | grep %s | awk \"{print \\$1, \\$2}\"", serial))
108+
if err != nil {
109+
return "", err
110+
}
111+
112+
cmdLines := strings.Split(strings.TrimSpace(cmdOut), "\n")
113+
if len(cmdLines) == 0 {
114+
return "", errors.New("shell out is empty")
115+
}
116+
117+
columns := strings.Split(strings.TrimSpace(cmdLines[0]), " ")
118+
if len(columns) != 2 {
119+
return "", errors.New("shell out columns mismatch")
120+
}
121+
122+
if columns[1] == serial {
123+
return columns[0], nil
124+
}
125+
126+
return "", errors.New("no block device found")
127+
}
128+
129+
func GetBlockDeviceSerialNumber(vm *v1alpha2.VirtualMachine, bdKind v1alpha2.BlockDeviceKind, bdName string) (string, bool) {
130+
unstructuredVMI, err := framework.GetClients().DynamicClient().Resource(schema.GroupVersionResource{
131+
Group: "internal.virtualization.deckhouse.io",
132+
Version: "v1",
133+
Resource: "internalvirtualizationvirtualmachineinstances",
134+
}).Namespace(vm.Namespace).Get(context.Background(), vm.Name, metav1.GetOptions{})
135+
Expect(err).NotTo(HaveOccurred())
136+
137+
var kvvmi virtv1.VirtualMachineInstance
138+
err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredVMI.Object, &kvvmi)
139+
Expect(err).NotTo(HaveOccurred())
140+
141+
var blockDeviceName string
142+
switch bdKind {
143+
case v1alpha2.DiskDevice:
144+
blockDeviceName = fmt.Sprintf("vd-%s", bdName)
145+
case v1alpha2.ImageDevice:
146+
blockDeviceName = fmt.Sprintf("vi-%s", bdName)
147+
case v1alpha2.ClusterImageDevice:
148+
blockDeviceName = fmt.Sprintf("cvi-%s", bdName)
149+
default:
150+
Fail(fmt.Sprintf("unknown block device kind %q", bdKind))
151+
}
152+
153+
for _, disk := range kvvmi.Spec.Domain.Devices.Disks {
154+
if disk.Name == blockDeviceName {
155+
return disk.Serial, true
156+
}
157+
}
158+
159+
return "", false
160+
}
161+
162+
func WriteFile(f *framework.Framework, vm *v1alpha2.VirtualMachine, path, value string) {
163+
GinkgoHelper()
164+
165+
// Escape single quotes in value to prevent command injection.
166+
escapedValue := strings.ReplaceAll(value, "'", "'\"'\"'")
167+
_, err := f.SSHCommand(vm.Name, vm.Namespace, fmt.Sprintf("sudo bash -c \"echo '%s' > %s\"", escapedValue, path))
168+
Expect(err).NotTo(HaveOccurred())
169+
}
170+
171+
func ReadFile(f *framework.Framework, vm *v1alpha2.VirtualMachine, path string) string {
172+
GinkgoHelper()
173+
174+
cmdOut, err := f.SSHCommand(vm.Name, vm.Namespace, fmt.Sprintf("sudo cat %s", path))
175+
Expect(err).NotTo(HaveOccurred())
176+
return strings.TrimSpace(cmdOut)
177+
}

0 commit comments

Comments
 (0)