Skip to content
Draft
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
51 changes: 2 additions & 49 deletions test/e2e/blockdevice/data_exports.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ import (

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/ptr"
Expand All @@ -40,7 +38,6 @@ import (
vmbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vm"
vmopbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vmop"
"github.com/deckhouse/virtualization/api/core/v1alpha2"
"github.com/deckhouse/virtualization/test/e2e/internal/d8"
"github.com/deckhouse/virtualization/test/e2e/internal/framework"
"github.com/deckhouse/virtualization/test/e2e/internal/label"
"github.com/deckhouse/virtualization/test/e2e/internal/object"
Expand Down Expand Up @@ -155,11 +152,11 @@ var _ = Describe("DataExports", label.Slow(), Label(precheck.PrecheckSVDM, prech
})

By("Exporting VirtualDisk to local file", func() {
exportData(f, "vd", vdData.Name, exportedDiskFile)
DataExport(f, "vd", vdData.Name, exportedDiskFile)
})

By("Exporting VirtualDiskSnapshot to local file", func() {
exportData(f, "vds", vdSnapshot.Name, exportedSnapshotFile)
DataExport(f, "vds", vdSnapshot.Name, exportedSnapshotFile)
})

By("Deleting the original data disk", func() {
Expand Down Expand Up @@ -236,50 +233,6 @@ var _ = Describe("DataExports", label.Slow(), Label(precheck.PrecheckSVDM, prech
})
})

func IsNFS() bool {
sc := framework.GetConfig().StorageClass.TemplateStorageClass
if sc == nil {
return false
}
return sc.Provisioner == framework.NFS
}

func needPublishOption(f *framework.Framework) bool {
hostname, err := os.Hostname()
Expect(err).NotTo(HaveOccurred(), "Failed to get hostname")
var node corev1.Node
err = f.Clients.GenericClient().Get(
context.Background(),
types.NamespacedName{Name: hostname},
&node,
)
if k8serrors.IsNotFound(err) {
return true
}
Expect(err).NotTo(HaveOccurred(), "Failed to get node %s", hostname)
return false
}

func exportData(f *framework.Framework, resourceType, name, outputFile string) {
opts := d8.DataExportOptions{
Namespace: f.Namespace().Name,
OutputFile: outputFile,
Publish: needPublishOption(f),
Timeout: framework.LongTimeout,
Cleanup: true,
}
if IsNFS() {
opts.SourcePath = diskImageExportFile
}
err := f.D8Virtualization().DataExportDownload(resourceType, name, opts)
Expect(err).NotTo(HaveOccurred())

DeferCleanup(func() {
err := os.Remove(outputFile)
Expect(err == nil || errors.Is(err, os.ErrNotExist)).To(BeTrue(), "Failed to remove exported file %s: %v", outputFile, err)
})
}

func createUploadDisk(f *framework.Framework, name string) *v1alpha2.VirtualDisk {
vd := vdbuilder.New(
vdbuilder.WithName(name),
Expand Down
84 changes: 84 additions & 0 deletions test/e2e/blockdevice/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
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 blockdevice

import (
"context"
"errors"
"os"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"

"github.com/deckhouse/virtualization/test/e2e/internal/d8"
"github.com/deckhouse/virtualization/test/e2e/internal/framework"
)

// IsNFS returns true if the storage class is NFS.
func IsNFS() bool {
sc := framework.GetConfig().StorageClass.TemplateStorageClass
if sc == nil {
return false
}
return sc.Provisioner == framework.NFS
}

// needPublishOption returns true if publish option should be used for export.
func needPublishOption(f *framework.Framework) bool {
hostname, err := os.Hostname()
Expect(err).NotTo(HaveOccurred(), "Failed to get hostname")
var node corev1.Node
err = f.Clients.GenericClient().Get(
context.Background(),
types.NamespacedName{Name: hostname},
&node,
)
if k8serrors.IsNotFound(err) {
return true
}
Expect(err).NotTo(HaveOccurred(), "Failed to get node %s", hostname)
return false
}

// DataExport exports a resource (VirtualDisk, VirtualDiskSnapshot, etc.) to a local file.
// Automatically cleans up the exported file after test.
//
// resourceType: "vd" for VirtualDisk, "vds" for VirtualDiskSnapshot, "vi" for VirtualImage, etc.
// Use needPublishOption and IsNFS from data_exports.go for configuration.
func DataExport(f *framework.Framework, resourceType, name, outputFile string) {
opts := d8.DataExportOptions{
Namespace: f.Namespace().Name,
OutputFile: outputFile,
Publish: needPublishOption(f),
Timeout: framework.LongTimeout,
Cleanup: true,
}
if IsNFS() {
opts.SourcePath = diskImageExportFile
}
err := f.D8Virtualization().DataExportDownload(resourceType, name, opts)
Expect(err).NotTo(HaveOccurred())

DeferCleanup(func() {
err := os.Remove(outputFile)
Expect(err == nil || errors.Is(err, os.ErrNotExist)).To(BeTrue(),
"Failed to remove exported file %s: %v", outputFile, err)
})
}
81 changes: 81 additions & 0 deletions test/e2e/blockdevice/vd_phase_transitions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
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 blockdevice

import (
"context"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

vdbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vd"
"github.com/deckhouse/virtualization/api/core/v1alpha2"
"github.com/deckhouse/virtualization/test/e2e/internal/framework"
"github.com/deckhouse/virtualization/test/e2e/internal/label"
"github.com/deckhouse/virtualization/test/e2e/internal/object"
"github.com/deckhouse/virtualization/test/e2e/internal/precheck"
"github.com/deckhouse/virtualization/test/e2e/internal/util"
)

var _ = Describe("VirtualDiskPhaseTransitions", label.Slow(), Label(precheck.PrecheckImmediateStorageClass), func() {
var f *framework.Framework

BeforeEach(func() {
f = framework.NewFramework("vd-phase-transitions")
f.Before()
DeferCleanup(f.After)
})

It("tracks phase transitions during export", func() {
var vd *v1alpha2.VirtualDisk

// Get immediate storage class for the test to ensure disk becomes Ready immediately
sc := framework.GetConfig().StorageClass.ImmediateStorageClass
Expect(sc).NotTo(BeNil(), "immediate storage class is required for this test")

By("Creating VirtualDisk from CVI", func() {
vd = object.NewVDFromCVI("vd-test", f.Namespace().Name, object.PrecreatedCVIAlpineBIOS,
vdbuilder.WithPersistentVolumeClaim(&sc.Name, nil))

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

By("Waiting for VirtualDisk to become Ready", func() {
util.UntilObjectPhase(string(v1alpha2.DiskReady), framework.LongTimeout, vd)
})

By("Starting event-based phase watcher", func() {
watcher := util.WatchPhases(context.Background(), vd)
Expect(watcher).NotTo(BeNil(), "failed to create event watcher for VirtualDisk")

// Register defer early to ensure verification runs even if test fails later
defer util.VerifyPhaseTransitions(watcher,
string(v1alpha2.DiskReady),
string(v1alpha2.DiskExporting),
string(v1alpha2.DiskReady))

By("Exporting VirtualDisk", func() {
DataExport(f, "vd", vd.Name, "exported-disk-phases.img")
})

By("Waiting for VirtualDisk to return to Ready after export", func() {
util.UntilObjectPhase(string(v1alpha2.DiskReady), framework.LongTimeout, vd)
})
})
})
})
Loading
Loading