diff --git a/test/e2e/Taskfile.yaml b/test/e2e/Taskfile.yaml index a893dfe3ff..0ea2c3ce5f 100644 --- a/test/e2e/Taskfile.yaml +++ b/test/e2e/Taskfile.yaml @@ -41,12 +41,21 @@ tasks: cmds: - ./scripts/task_run_ci.sh + precheck:prepare: + desc: "Generate JSON report via ginkgo dry-run for precheck preparation" + cmds: + - | + # Run ginkgo dry-run with same filters as run task to collect only filtered specs + # Prechecks are executed in e2e_test.go SynchronizedBeforeSuite based on labels from this report + ./scripts/precheck-prepare.sh + run: desc: "Run e2e tests" deps: - copy - kubectl - d8 + - precheck:prepare cmds: - | go tool ginkgo -v \ diff --git a/test/e2e/blockdevice/data_exports.go b/test/e2e/blockdevice/data_exports.go index 80d1117644..acf58fc293 100644 --- a/test/e2e/blockdevice/data_exports.go +++ b/test/e2e/blockdevice/data_exports.go @@ -44,6 +44,7 @@ import ( "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" ) @@ -57,10 +58,11 @@ const ( diskImageExportFile = "disk.img" ) -var _ = Describe("DataExports", label.Slow(), func() { - f := framework.NewFramework("data-exports") +var _ = Describe("DataExports", label.Slow(), Label(precheck.PrecheckSVDM, precheck.PrecheckSnapshot), func() { + var f *framework.Framework BeforeEach(func() { + f = framework.NewFramework("data-exports") moduleEnabled, err := checkStorageVolumeDataManagerEnabled() Expect(err).NotTo(HaveOccurred(), "Failed to get modules") if !moduleEnabled { @@ -365,7 +367,7 @@ func handleUploadResponse(resp *http.Response) error { } func checkStorageVolumeDataManagerEnabled() (bool, error) { - sdnModule, err := framework.NewFramework("").GetModuleConfig("storage-volume-data-manager") + sdnModule, err := framework.NewFramework("").GetModuleConfig(context.Background(), "storage-volume-data-manager") if err != nil { return false, err } diff --git a/test/e2e/blockdevice/importer_network_policy.go b/test/e2e/blockdevice/importer_network_policy.go index 8aabf4fec7..a9ad9c9138 100644 --- a/test/e2e/blockdevice/importer_network_policy.go +++ b/test/e2e/blockdevice/importer_network_policy.go @@ -26,14 +26,17 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/test/e2e/internal/framework" "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("ImporterNetworkPolicy", func() { +var _ = Describe("ImporterNetworkPolicy", Label(precheck.NoPrecheck), func() { const testName = "importer-network-policy" - f := framework.NewFramework("") + + var f *framework.Framework BeforeEach(func() { + f = framework.NewFramework("") f.Before() DeferCleanup(f.After) }) diff --git a/test/e2e/blockdevice/virtual_disk_provisioning.go b/test/e2e/blockdevice/virtual_disk_provisioning.go index 73a67babbf..687697c6c8 100644 --- a/test/e2e/blockdevice/virtual_disk_provisioning.go +++ b/test/e2e/blockdevice/virtual_disk_provisioning.go @@ -29,13 +29,15 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/test/e2e/internal/framework" "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("VirtualDiskProvisioning", func() { - f := framework.NewFramework("vd-provisioning") +var _ = Describe("VirtualDiskProvisioning", Label(precheck.NoPrecheck), func() { + var f *framework.Framework BeforeEach(func() { + f = framework.NewFramework("vd-provisioning") sc := framework.GetConfig().StorageClass.TemplateStorageClass if sc != nil && sc.Provisioner == framework.NFS { Skip("VirtualImages on PVC only work with block storage classes, skipping NFS") diff --git a/test/e2e/blockdevice/virtual_image_creation.go b/test/e2e/blockdevice/virtual_image_creation.go index c7b421d188..a7a864a345 100644 --- a/test/e2e/blockdevice/virtual_image_creation.go +++ b/test/e2e/blockdevice/virtual_image_creation.go @@ -34,13 +34,15 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/test/e2e/internal/framework" "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("VirtualImageCreation", func() { - f := framework.NewFramework("vi-creation") +var _ = Describe("VirtualImageCreation", Label(precheck.PrecheckSnapshot), func() { + var f *framework.Framework BeforeEach(func() { + f = framework.NewFramework("vi-creation") sc := framework.GetConfig().StorageClass.TemplateStorageClass if sc != nil && sc.Provisioner == framework.NFS { Skip("VirtualImages on PVC only work with block storage classes, skipping NFS") diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index cee161cb4f..a7bea2acef 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -26,6 +26,8 @@ import ( _ "github.com/deckhouse/virtualization/test/e2e/blockdevice" "github.com/deckhouse/virtualization/test/e2e/controller" + "github.com/deckhouse/virtualization/test/e2e/internal/framework" + "github.com/deckhouse/virtualization/test/e2e/internal/precheck" "github.com/deckhouse/virtualization/test/e2e/legacy" _ "github.com/deckhouse/virtualization/test/e2e/snapshot" _ "github.com/deckhouse/virtualization/test/e2e/vm" @@ -39,8 +41,22 @@ func TestE2E(t *testing.T) { } var _ = SynchronizedBeforeSuite(func() { + // Initialize test resources BEFORE running prechecks + // This ensures resources are available even if prechecks fail controller.NewBeforeProcess1Body() legacy.NewBeforeProcess1Body() + + // Validate precheck labels from JSON report (created by dry-run during prepare) + if err := precheck.ValidateFromJSONFile(precheck.LabelsFile); err != nil { + Fail("precheck validation failed: " + err.Error()) + } + + // Load spec labels to determine which prechecks to run + precheck.LoadSpecLabelsFromFile(precheck.LabelsFile, GinkgoLabelFilter()) + // Run prechecks based on loaded labels + // Must run after resource initialization to avoid panic in SynchronizedAfterSuite + precheck.Run(framework.NewFramework(""), GinkgoLabelFilter()) + bootstrapPrecreatedCVIs() }, func() {}) diff --git a/test/e2e/internal/api/deckhouse/v1alpha1/deep_copy.go b/test/e2e/internal/api/deckhouse/v1alpha1/deep_copy.go index 567c91c8b1..b66f5d192c 100644 --- a/test/e2e/internal/api/deckhouse/v1alpha1/deep_copy.go +++ b/test/e2e/internal/api/deckhouse/v1alpha1/deep_copy.go @@ -123,3 +123,68 @@ func (v SettingsValues) DeepCopyInto(out *SettingsValues) { return } } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Module) DeepCopyInto(out *Module) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Module. +func (in *Module) DeepCopy() *Module { + if in == nil { + return nil + } + out := new(Module) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Module) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ModuleCondition) DeepCopyInto(out *ModuleCondition) { + *out = *in + in.LastProbeTime.DeepCopyInto(&out.LastProbeTime) + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModuleCondition. +func (in *ModuleCondition) DeepCopy() *ModuleCondition { + if in == nil { + return nil + } + out := new(ModuleCondition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ModuleStatus) DeepCopyInto(out *ModuleStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]ModuleCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModuleStatus. +func (in *ModuleStatus) DeepCopy() *ModuleStatus { + if in == nil { + return nil + } + out := new(ModuleStatus) + in.DeepCopyInto(out) + return out +} diff --git a/test/e2e/internal/api/deckhouse/v1alpha1/module.go b/test/e2e/internal/api/deckhouse/v1alpha1/module.go new file mode 100644 index 0000000000..dfb8b62d37 --- /dev/null +++ b/test/e2e/internal/api/deckhouse/v1alpha1/module.go @@ -0,0 +1,61 @@ +/* +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 v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +k8s:deepcopy-gen=true +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type Module struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Status ModuleStatus `json:"status"` +} + +// +k8s:deepcopy-gen=true +type ModuleStatus struct { + Phase string `json:"phase,omitempty"` + HooksState string `json:"hooksState,omitempty"` + Conditions []ModuleCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` +} + +// +k8s:deepcopy-gen=true +type ModuleCondition struct { + // Type is the type of the condition. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions + Type string `json:"type,omitempty"` + // Machine-readable, UpperCamelCase text indicating the reason for the condition's last transition. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions + Reason string `json:"reason,omitempty"` + // Human-readable message indicating details about last transition. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions + Message string `json:"message,omitempty"` + // Status is the status of the condition. + // Can be True, False, Unknown. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions + Status corev1.ConditionStatus `json:"status,omitempty"` + // Timestamp of when the condition was last probed. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions + LastProbeTime metav1.Time `json:"lastProbeTime"` + // Last time the condition transitioned from one status to another. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions + LastTransitionTime metav1.Time `json:"lastTransitionTime"` +} diff --git a/test/e2e/internal/api/deckhouse/v1alpha1/register.go b/test/e2e/internal/api/deckhouse/v1alpha1/register.go index 5336a3f399..bbeea61abc 100644 --- a/test/e2e/internal/api/deckhouse/v1alpha1/register.go +++ b/test/e2e/internal/api/deckhouse/v1alpha1/register.go @@ -47,6 +47,7 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &ModuleConfig{}, &ModuleConfigList{}, + &Module{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) diff --git a/test/e2e/internal/config/config.go b/test/e2e/internal/config/config.go index 40286dc843..8d96c1c3e7 100644 --- a/test/e2e/internal/config/config.go +++ b/test/e2e/internal/config/config.go @@ -87,7 +87,6 @@ type TestData struct { VMMigration string `yaml:"vmMigration"` VMMigrationCancel string `yaml:"vmMigrationCancel"` VMEvacuation string `yaml:"vmEvacuation"` - VMVersions string `yaml:"vmVersions"` VdSnapshots string `yaml:"vdSnapshots"` Sshkey string `yaml:"sshKey"` SSHUser string `yaml:"sshUser"` diff --git a/test/e2e/internal/framework/client.go b/test/e2e/internal/framework/client.go index 8255297a5d..29e08c5db7 100644 --- a/test/e2e/internal/framework/client.go +++ b/test/e2e/internal/framework/client.go @@ -40,6 +40,8 @@ import ( var clients = Clients{} func GetClients() Clients { + onceLoadConfig() + InitClients() return clients } @@ -87,57 +89,61 @@ func (c Clients) Git() gt.Git { return c.git } -func init() { - onceLoadConfig() +// InitClients initializes Kubernetes clients. +// This should be called before using framework clients. +func InitClients() { + clientsOnce.Do(func() { + _ = GetConfig() + + restConfig, err := conf.ClusterTransport.RestConfig() + if err != nil { + panic(err) + } + clients.virtClient, err = kubeclient.GetClientFromRESTConfig(restConfig) + if err != nil { + panic(err) + } + clients.kubeClient, err = kubernetes.NewForConfig(restConfig) + if err != nil { + panic(err) + } + clients.dynamic, err = dynamic.NewForConfig(restConfig) + if err != nil { + panic(err) + } + clients.rewriteClient = rewrite.NewRewriteClient(clients.dynamic) + clients.kubectl, err = kubectl.NewKubectl(kubectl.KubectlConf(conf.ClusterTransport)) + if err != nil { + panic(err) + } + clients.d8virtualization, err = d8.NewD8Virtualization(d8.D8VirtualizationConf(conf.ClusterTransport)) + if err != nil { + panic(err) + } + + scheme := apiruntime.NewScheme() + // virtv1 and cdiv1 are not registered in the scheme, + // The main reason is that we cannot use kubevirt types in tests because in DVP we use rewritten kubevirt types + // use dynamic client for get kubevirt types + for _, f := range []func(*apiruntime.Scheme) error{ + v1alpha2.AddToScheme, + v1alpha3.AddToScheme, + clientgoscheme.AddToScheme, + dv1alpha1.AddToScheme, + dv1alpha2.AddToScheme, + } { + if err := f(scheme); err != nil { + panic(err) + } + } + clients.client, err = client.New(restConfig, client.Options{Scheme: scheme}) + if err != nil { + panic(err) + } - restConfig, err := conf.ClusterTransport.RestConfig() - if err != nil { - panic(err) - } - clients.virtClient, err = kubeclient.GetClientFromRESTConfig(restConfig) - if err != nil { - panic(err) - } - clients.kubeClient, err = kubernetes.NewForConfig(restConfig) - if err != nil { - panic(err) - } - clients.dynamic, err = dynamic.NewForConfig(restConfig) - if err != nil { - panic(err) - } - clients.rewriteClient = rewrite.NewRewriteClient(clients.dynamic) - clients.kubectl, err = kubectl.NewKubectl(kubectl.KubectlConf(conf.ClusterTransport)) - if err != nil { - panic(err) - } - clients.d8virtualization, err = d8.NewD8Virtualization(d8.D8VirtualizationConf(conf.ClusterTransport)) - if err != nil { - panic(err) - } - - scheme := apiruntime.NewScheme() - // virtv1 and cdiv1 are not registered in the scheme, - // The main reason is that we cannot use kubevirt types in tests because in DVP we use rewritten kubevirt types - // use dynamic client for get kubevirt types - for _, f := range []func(*apiruntime.Scheme) error{ - v1alpha2.AddToScheme, - v1alpha3.AddToScheme, - clientgoscheme.AddToScheme, - dv1alpha1.AddToScheme, - dv1alpha2.AddToScheme, - } { - if err := f(scheme); err != nil { + clients.git, err = gt.NewGit() + if err != nil { panic(err) } - } - clients.client, err = client.New(restConfig, client.Options{Scheme: scheme}) - if err != nil { - panic(err) - } - - clients.git, err = gt.NewGit() - if err != nil { - panic(err) - } + }) } diff --git a/test/e2e/internal/framework/config.go b/test/e2e/internal/framework/config.go index 7c35e64287..fa00c5ce50 100644 --- a/test/e2e/internal/framework/config.go +++ b/test/e2e/internal/framework/config.go @@ -23,8 +23,9 @@ import ( ) var ( - conf *config.Config - once sync.Once + conf *config.Config + once sync.Once + clientsOnce sync.Once ) func onceLoadConfig() { @@ -38,6 +39,8 @@ func onceLoadConfig() { } func GetConfig() *config.Config { + onceLoadConfig() + copied := *conf return &copied } @@ -48,7 +51,3 @@ func GetConfig() *config.Config { func SetConfig(c *config.Config) { conf = c } - -func init() { - onceLoadConfig() -} diff --git a/test/e2e/internal/framework/mc.go b/test/e2e/internal/framework/mc.go index 12af1e6858..eeb66d35a9 100644 --- a/test/e2e/internal/framework/mc.go +++ b/test/e2e/internal/framework/mc.go @@ -26,14 +26,14 @@ import ( dv1alpha1 "github.com/deckhouse/virtualization/test/e2e/internal/api/deckhouse/v1alpha1" ) -func (f *Framework) GetModuleConfig(name string) (*dv1alpha1.ModuleConfig, error) { +func (f *Framework) GetModuleConfig(ctx context.Context, name string) (*dv1alpha1.ModuleConfig, error) { mc := &dv1alpha1.ModuleConfig{} - err := f.GenericClient().Get(context.Background(), client.ObjectKey{Name: name}, mc) + err := f.GenericClient().Get(ctx, client.ObjectKey{Name: name}, mc) return mc, err } -func (f *Framework) GetVirtualizationModuleConfig() (*VirtualizationModuleConfig, error) { - mc, err := f.GetModuleConfig("virtualization") +func (f *Framework) GetVirtualizationModuleConfig(ctx context.Context) (*VirtualizationModuleConfig, error) { + mc, err := f.GetModuleConfig(ctx, "virtualization") if err != nil { return nil, err } diff --git a/test/e2e/internal/precheck/common.go b/test/e2e/internal/precheck/common.go new file mode 100644 index 0000000000..606dff9b19 --- /dev/null +++ b/test/e2e/internal/precheck/common.go @@ -0,0 +1,361 @@ +/* +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 precheck + +import ( + "context" + "encoding/json" + "fmt" + "os" + "regexp" + "strings" + + . "github.com/onsi/ginkgo/v2" + + "github.com/deckhouse/virtualization/test/e2e/internal/framework" +) + +const ( + modulePhaseReady = "Ready" +) + +const ( + LabelsFile = "/tmp/e2e-specs.json" +) + +// specInfo represents a test spec with its location and labels. +type specInfo struct { + Location string `json:"location"` + Labels []string `json:"labels"` +} + +// ginkgoReport represents the structure of Ginkgo JSON report. +type ginkgoReport struct { + SpecReports []specReport `json:"SpecReports"` +} + +// specReport represents a spec in Ginkgo JSON report. +type specReport struct { + ContainerHierarchyTexts []string `json:"ContainerHierarchyTexts"` + ContainerHierarchyLabels [][]string `json:"ContainerHierarchyLabels"` + LeafNodeText string `json:"LeafNodeText"` + LeafNodeType string `json:"LeafNodeType"` +} + +// Precheck defines interface for precheck implementations. +type Precheck interface { + // Label returns the precheck label that tests must use to require this precheck. + Label() string + + // Run executes the precheck. + // Returns error if precheck fails. + Run(ctx context.Context, f *framework.Framework) error +} + +// registeredPrechecks holds all registered precheck implementations. +var registeredPrechecks = make(map[string]Precheck) + +// commonPrechecks are prechecks that run for all tests (no label required). +var commonPrechecks []Precheck + +// specLabels stores labels collected from all specs (loaded from file). +var specLabels []string + +// RegisterPrecheck registers a precheck implementation. +// If isCommon is true, the precheck runs for all tests regardless of labels. +func RegisterPrecheck(p Precheck, isCommon bool) { + registeredPrechecks[p.Label()] = p + if isCommon { + commonPrechecks = append(commonPrechecks, p) + } +} + +// LoadSpecLabelsFromFile loads spec labels from file and filters by labelFilter. +// Called from SynchronizedBeforeSuite. +func LoadSpecLabelsFromFile(filename, labelFilter string) { + data, err := os.ReadFile(filename) + if err != nil { + return + } + + // Parse Ginkgo JSON report + var reports []ginkgoReport + if err := json.Unmarshal(data, &reports); err != nil { + return + } + + // Convert to specInfo + var allSpecs []specInfo + for _, report := range reports { + for _, r := range report.SpecReports { + if r.LeafNodeType == "" || r.LeafNodeType == "ReportBeforeSuite" || r.LeafNodeType == "ReportAfterSuite" { + continue + } + + location := "" + if len(r.ContainerHierarchyTexts) > 0 { + location = r.ContainerHierarchyTexts[0] + for i := 1; i < len(r.ContainerHierarchyTexts); i++ { + location += " / " + r.ContainerHierarchyTexts[i] + } + if r.LeafNodeText != "" { + location += " - " + r.LeafNodeText + } + } + + if location == "" { + continue + } + + var labels []string + for _, levelLabels := range r.ContainerHierarchyLabels { + labels = append(labels, levelLabels...) + } + + allSpecs = append(allSpecs, specInfo{ + Location: location, + Labels: labels, + }) + } + } + + // Filter specs based on FOCUS or LABELS filter. + // FOCUS filters by spec location (description), LABELS filters by labels. + // Parameter labelFilter takes precedence over LABELS env var. + focusRegex := os.Getenv("FOCUS") + if labelFilter == "" { + labelFilter = os.Getenv("LABELS") + } + + filteredSpecs := allSpecs + if focusRegex != "" || labelFilter != "" { + filteredSpecs = filterSpecs(allSpecs, focusRegex, labelFilter) + } + + // Collect precheck labels only from filtered specs + labelSet := make(map[string]bool) + for _, spec := range filteredSpecs { + for _, label := range spec.Labels { + if _, ok := registeredPrechecks[label]; ok { + labelSet[label] = true + } + } + } + + for label := range labelSet { + specLabels = append(specLabels, label) + } +} + +// filterSpecs filters specs by FOCUS (regex on location) and LABELS (comma-separated labels). +func filterSpecs(specs []specInfo, focusRegex, labelFilter string) []specInfo { + var filtered []specInfo + + var focus *regexp.Regexp + var err error + if focusRegex != "" { + focus, err = regexp.Compile(focusRegex) + if err != nil { + // Invalid regex, skip focus filter + focus = nil + } + } + + // Parse label filter (comma-separated, supports ~ for negated) + requiredLabels := make(map[string]bool) + negatedLabels := make(map[string]bool) + if labelFilter != "" { + for _, l := range strings.Split(labelFilter, ",") { + l = strings.TrimSpace(l) + if strings.HasPrefix(l, "~") { + negatedLabels[l[1:]] = true + } else { + requiredLabels[l] = true + } + } + } + + for _, spec := range specs { + // Check FOCUS filter + if focus != nil && !focus.MatchString(spec.Location) { + continue + } + + // Check LABELS filter + if len(requiredLabels) > 0 { + matched := false + for _, sl := range spec.Labels { + if requiredLabels[sl] { + matched = true + break + } + } + if !matched { + continue + } + } + + // Check negated labels (~label) + hasNegated := false + for _, sl := range spec.Labels { + if negatedLabels[sl] { + hasNegated = true + break + } + } + if hasNegated { + continue + } + + filtered = append(filtered, spec) + } + + return filtered +} + +// ValidateFromJSONFile validates specs from Ginkgo JSON report. +// This is used with ginkgo --json-report flag. +func ValidateFromJSONFile(filename string) error { + data, err := os.ReadFile(filename) + if err != nil { + return fmt.Errorf("failed to read spec file: %w", err) + } + + // Ginkgo JSON report is an array of suite reports + var reports []ginkgoReport + if err := json.Unmarshal(data, &reports); err != nil { + // Debug: show first 200 chars of data + dataStr := string(data) + if len(dataStr) > 200 { + dataStr = dataStr[:200] + } + return fmt.Errorf("failed to parse ginkgo report: %w, data: %s", err, dataStr) + } + + // Convert ginkgo spec reports to our format + var specs []specInfo + for _, report := range reports { + for _, r := range report.SpecReports { + // Skip non-spec entries like ReportBeforeSuite, ReportAfterSuite + if r.LeafNodeType == "" || r.LeafNodeType == "ReportBeforeSuite" || r.LeafNodeType == "ReportAfterSuite" { + continue + } + + location := "" + if len(r.ContainerHierarchyTexts) > 0 { + location = r.ContainerHierarchyTexts[0] + for i := 1; i < len(r.ContainerHierarchyTexts); i++ { + location += " / " + r.ContainerHierarchyTexts[i] + } + if r.LeafNodeText != "" { + location += " - " + r.LeafNodeText + } + } + + if location == "" { + continue + } + + // Flatten labels from all hierarchy levels + var labels []string + for _, levelLabels := range r.ContainerHierarchyLabels { + labels = append(labels, levelLabels...) + } + + specs = append(specs, specInfo{ + Location: location, + Labels: labels, + }) + } + } + + return validateSpecs(specs) +} + +func validateSpecs(specs []specInfo) error { + var missingSpecs []string + + for _, spec := range specs { + hasPrecheckLabel := false + for _, label := range spec.Labels { + if IsPrecheckLabel(label) { + hasPrecheckLabel = true + break + } + } + if !hasPrecheckLabel { + missingSpecs = append(missingSpecs, spec.Location) + } + } + + if len(missingSpecs) > 0 { + display := missingSpecs + if len(display) > 20 { + display = append(display[:20], fmt.Sprintf("... and %d more", len(missingSpecs)-20)) + } + return fmt.Errorf("found %d specs without precheck labels:\n%s\n\n"+ + "Add Label(precheck.NoPrecheck) or Label(precheck.PrecheckXXX) to your Describe/It", + len(missingSpecs), strings.Join(display, "\n")) + } + + fmt.Printf("PASS: all %d specs have precheck labels\n", len(specs)) + return nil +} + +// Run executes prechecks based on loaded spec labels. +func Run(f *framework.Framework, labelFilter string) { + ctx := context.Background() + // Run common prechecks first (always run) + for _, p := range commonPrechecks { + _, _ = GinkgoWriter.Write([]byte("Running common precheck: " + p.Label() + "\n")) + if err := p.Run(ctx, f); err != nil { + Fail("common precheck " + p.Label() + " failed: " + err.Error()) + } + } + + // Run prechecks for loaded labels + for _, label := range specLabels { + p := registeredPrechecks[label] + if p == nil { + continue + } + _, _ = GinkgoWriter.Write([]byte("Running precheck: " + label + "\n")) + if err := p.Run(ctx, f); err != nil { + Fail("precheck " + label + " failed: " + err.Error()) + } + } +} + +// isCheckEnabled returns true if the precheck is not disabled. +func isCheckEnabled(envName string) bool { + if os.Getenv("PRECHECK") == "no" { + return false + } + return os.Getenv(envName) != "no" +} + +// IsModuleEnabled checks if a Deckhouse module is enabled. +// Returns true if the module exists and is enabled (Spec.Enabled = true). +func IsModuleEnabled(ctx context.Context, f *framework.Framework, moduleName string) bool { + module, err := f.GetModuleConfig(ctx, moduleName) + if err != nil { + _, _ = fmt.Fprintf(GinkgoWriter, "failed to get %s module config: %v\n", moduleName, err) + return false + } + enabled := module.Spec.Enabled + return enabled != nil && *enabled +} diff --git a/test/e2e/internal/precheck/labels.go b/test/e2e/internal/precheck/labels.go new file mode 100644 index 0000000000..69c13a7ca6 --- /dev/null +++ b/test/e2e/internal/precheck/labels.go @@ -0,0 +1,72 @@ +/* +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 precheck + +// Precheck labels for tests. +// Tests must declare required prechecks using these labels. +// Use NoPrecheck if test doesn't require any prechecks. + +const ( + // PrecheckSDN - test requires SDN module to be enabled. + PrecheckSDN = "sdn-precheck" + + // PrecheckVMC - test requires VMC module to be enabled. + PrecheckVMC = "vmc-precheck" + + // PrecheckSVDM - test requires SVDM module to be enabled. + PrecheckSVDM = "svdm-precheck" + + // PrecheckStorageClass - test requires default StorageClass to be configured. + PrecheckStorageClass = "storageclass-precheck" + + // PrecheckSnapshot - test requires snapshot-controller module to be enabled. + PrecheckSnapshot = "snapshot-precheck" + + // PrecheckVirtualization - test requires virtualization module to be enabled. + PrecheckVirtualization = "virtualization-precheck" + + // PrecheckUSB - test requires USB device with dummy_hcd to be configured. + PrecheckUSB = "usb-precheck" + + // NoPrecheck - test doesn't require any prechecks. + // Use this label for tests that don't depend on cluster configuration. + NoPrecheck = "no-precheck" +) + +// KnownPrecheckLabels returns all known precheck label constants. +func KnownPrecheckLabels() []string { + return []string{ + PrecheckSDN, + PrecheckVMC, + PrecheckSVDM, + PrecheckStorageClass, + PrecheckSnapshot, + PrecheckVirtualization, + PrecheckUSB, + NoPrecheck, + } +} + +// IsPrecheckLabel returns true if the given label is a known precheck label. +func IsPrecheckLabel(label string) bool { + for _, known := range KnownPrecheckLabels() { + if label == known { + return true + } + } + return false +} diff --git a/test/e2e/internal/precheck/sdn.go b/test/e2e/internal/precheck/sdn.go new file mode 100644 index 0000000000..66b61c5ea6 --- /dev/null +++ b/test/e2e/internal/precheck/sdn.go @@ -0,0 +1,124 @@ +/* +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 precheck + +import ( + "context" + "fmt" + + . "github.com/onsi/ginkgo/v2" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" + + dv1alpha1 "github.com/deckhouse/virtualization/test/e2e/internal/api/deckhouse/v1alpha1" + "github.com/deckhouse/virtualization/test/e2e/internal/framework" +) + +const ( + sdnModuleName = "sdn" + sdnModuleCheckEnvName = "SDN_MODULE_PRECHECK" + + // Required VLAN IDs for e2e tests + additionalInterfaceVLANID = 4006 + secondAdditionalInterfaceVLANID = 4007 +) + +// ClusterNetworkName returns the name of ClusterNetwork for given VLAN ID. +func ClusterNetworkName(vlanID int) string { + return fmt.Sprintf("cn-%d-for-e2e-test", vlanID) +} + +// ClusterNetworkCreateCommand returns the kubectl command to create ClusterNetwork for given VLAN ID. +func ClusterNetworkCreateCommand(vlanID int) string { + return fmt.Sprintf(`kubectl apply -f - < storageclass.kubernetes.io/is-default-class=true", + storageClassPrecheckEnvName) + } + + // Sort by creation timestamp, newest first + // Secondary sort by name, ascending order + sort.Slice(defaultClasses, func(i, j int) bool { + if defaultClasses[i].CreationTimestamp.UnixNano() == defaultClasses[j].CreationTimestamp.UnixNano() { + return defaultClasses[i].Name < defaultClasses[j].Name + } + return defaultClasses[i].CreationTimestamp.UnixNano() > defaultClasses[j].CreationTimestamp.UnixNano() + }) + + return nil +} + +// Register StorageClass precheck as common (runs for all tests). +func init() { + RegisterPrecheck(&storageClassPrecheck{}, true) +} diff --git a/test/e2e/internal/precheck/svdm.go b/test/e2e/internal/precheck/svdm.go new file mode 100644 index 0000000000..47f8fe76e0 --- /dev/null +++ b/test/e2e/internal/precheck/svdm.go @@ -0,0 +1,68 @@ +/* +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 precheck + +import ( + "context" + "fmt" + + . "github.com/onsi/ginkgo/v2" + "sigs.k8s.io/controller-runtime/pkg/client" + + dv1alpha1 "github.com/deckhouse/virtualization/test/e2e/internal/api/deckhouse/v1alpha1" + "github.com/deckhouse/virtualization/test/e2e/internal/framework" +) + +const ( + // svdmModuleName is the name of the Storage Volume Data Manager module in Deckhouse. + svdmModuleName = "storage-volume-data-manager" + svdmModuleCheckEnvName = "SVDM_MODULE_PRECHECK" +) + +// svdmPrecheck implements Precheck interface for SVDM module. +type svdmPrecheck struct{} + +func (s *svdmPrecheck) Label() string { + return PrecheckSVDM +} + +func (s *svdmPrecheck) Run(ctx context.Context, f *framework.Framework) error { + if !isCheckEnabled(svdmModuleCheckEnvName) { + _, _ = GinkgoWriter.Write([]byte("Storage Volume Data Manager (SVDM) module check is disabled.\n")) + return nil + } + + if !IsModuleEnabled(ctx, f, svdmModuleName) { + return fmt.Errorf("%s=no to disable this precheck: Storage Volume Data Manager module should be enabled", svdmModuleCheckEnvName) + } + + svdmModule := &dv1alpha1.Module{} + err := f.GenericClient().Get(ctx, client.ObjectKey{Name: svdmModuleName}, svdmModule) + if err != nil { + return fmt.Errorf("%s=no to disable this precheck: failed to check SVDM module status: %w", svdmModuleCheckEnvName, err) + } + if svdmModule.Status.Phase != modulePhaseReady { + return fmt.Errorf("%s=no to disable this precheck: Storage Volume Data Manager module should be ready; current status: %s", svdmModuleCheckEnvName, svdmModule.Status.Phase) + } + + return nil +} + +// Register SVDM precheck (not common - requires explicit label). +func init() { + RegisterPrecheck(&svdmPrecheck{}, false) +} diff --git a/test/e2e/internal/precheck/usb.go b/test/e2e/internal/precheck/usb.go new file mode 100644 index 0000000000..520622a632 --- /dev/null +++ b/test/e2e/internal/precheck/usb.go @@ -0,0 +1,82 @@ +/* +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 precheck + +import ( + "context" + "fmt" + + . "github.com/onsi/ginkgo/v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/deckhouse/virtualization/test/e2e/internal/framework" +) + +const ( + usbPrecheckEnvName = "USB_PRECHECK" + + // dummyHCDVendorID is the VendorID for dummy_hcd USB device. + dummyHCDVendorID = "1d6b" + // dummyHCDProductID is the ProductID for dummy_hcd USB device. + dummyHCDProductID = "0104" +) + +// usbPrecheck implements Precheck interface for USB dummy_hcd. +type usbPrecheck struct{} + +func (u *usbPrecheck) Label() string { + return PrecheckUSB +} + +// checkDummyHCDConfigured checks if dummy_hcd USB device is configured. +// dummy_hcd is a virtual USB device used for testing USB passthrough. +func checkDummyHCDConfigured(ctx context.Context, f *framework.Framework) bool { + virtClient := f.VirtClient() + + nodeUSBList, err := virtClient.NodeUSBDevices().List(ctx, metav1.ListOptions{}) + if err != nil { + _, _ = fmt.Fprintf(GinkgoWriter, "failed to list NodeUSBDevices: %v\n", err) + return false + } + + for _, nodeUSB := range nodeUSBList.Items { + if nodeUSB.Status.Attributes.VendorID == dummyHCDVendorID && nodeUSB.Status.Attributes.ProductID == dummyHCDProductID { + return true + } + } + + return false +} + +func (u *usbPrecheck) Run(ctx context.Context, f *framework.Framework) error { + if !isCheckEnabled(usbPrecheckEnvName) { + _, _ = GinkgoWriter.Write([]byte("USB precheck is disabled.\n")) + return nil + } + + if !checkDummyHCDConfigured(ctx, f) { + return fmt.Errorf("%s=no to disable this precheck: dummy_hcd USB device is not configured. "+ + "Run generate_dummy_hcd_ngc.sh to configure dummy_hcd USB device", usbPrecheckEnvName) + } + + return nil +} + +// Register USB precheck (not common - requires explicit label). +func init() { + RegisterPrecheck(&usbPrecheck{}, false) +} diff --git a/test/e2e/internal/precheck/virtualization.go b/test/e2e/internal/precheck/virtualization.go new file mode 100644 index 0000000000..92a08a3a23 --- /dev/null +++ b/test/e2e/internal/precheck/virtualization.go @@ -0,0 +1,68 @@ +/* +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 precheck + +import ( + "context" + "fmt" + + . "github.com/onsi/ginkgo/v2" + "sigs.k8s.io/controller-runtime/pkg/client" + + dv1alpha1 "github.com/deckhouse/virtualization/test/e2e/internal/api/deckhouse/v1alpha1" + "github.com/deckhouse/virtualization/test/e2e/internal/framework" +) + +const ( + virtualizationModuleName = "virtualization" + virtualizationModuleCheckEnvName = "VIRTUALIZATION_PRECHECK" +) + +// virtualizationPrecheck implements Precheck interface for virtualization module. +type virtualizationPrecheck struct{} + +func (v *virtualizationPrecheck) Label() string { + return PrecheckVirtualization +} + +func (v *virtualizationPrecheck) Run(ctx context.Context, f *framework.Framework) error { + if !isCheckEnabled(virtualizationModuleCheckEnvName) { + _, _ = GinkgoWriter.Write([]byte("virtualization module check is disabled.\n")) + return nil + } + + if !IsModuleEnabled(ctx, f, virtualizationModuleName) { + return fmt.Errorf("%s=no to disable this precheck: virtualization module should be enabled", virtualizationModuleCheckEnvName) + } + + // Check virtualization module status + virtualizationModule := &dv1alpha1.Module{} + err := f.GenericClient().Get(ctx, client.ObjectKey{Name: virtualizationModuleName}, virtualizationModule) + if err != nil { + return fmt.Errorf("%s=no to disable this precheck: failed to check virtualization module status: %w", virtualizationModuleCheckEnvName, err) + } + if virtualizationModule.Status.Phase != modulePhaseReady { + return fmt.Errorf("%s=no to disable this precheck: virtualization module should be ready; current status: %s", virtualizationModuleCheckEnvName, virtualizationModule.Status.Phase) + } + + return nil +} + +// Register virtualization precheck as common (runs for all tests). +func init() { + RegisterPrecheck(&virtualizationPrecheck{}, true) +} diff --git a/test/e2e/internal/precheck/vmc.go b/test/e2e/internal/precheck/vmc.go new file mode 100644 index 0000000000..c48aa8401a --- /dev/null +++ b/test/e2e/internal/precheck/vmc.go @@ -0,0 +1,128 @@ +/* +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 precheck + +import ( + "context" + "fmt" + + . "github.com/onsi/ginkgo/v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/deckhouse/virtualization/test/e2e/internal/framework" +) + +const ( + vmcModuleCheckEnvName = "VMC_PRECHECK" + defaultVMClassName = "generic-for-e2e" + + vmClassVersion = "v1alpha3" +) + +// vmcPrecheck implements Precheck interface for VMC/VMClass. +// This is a common precheck that runs for all tests. +type vmcPrecheck struct{} + +func (c *vmcPrecheck) Label() string { + return PrecheckVMC +} + +func (c *vmcPrecheck) Run(ctx context.Context, f *framework.Framework) error { + if !isCheckEnabled(vmcModuleCheckEnvName) { + _, _ = GinkgoWriter.Write([]byte("VMC precheck is disabled.\n")) + return nil + } + + // Use DynamicClient with v1alpha3 to avoid deprecation warning + gvr := schema.GroupVersionResource{ + Group: "virtualization.deckhouse.io", + Version: vmClassVersion, + Resource: "virtualmachineclasses", + } + + vmClasses, err := f.DynamicClient().Resource(gvr).List(ctx, metav1.ListOptions{}) + if err != nil { + return fmt.Errorf("%s=no to disable this precheck: list VirtualMachineClasses: %w", vmcModuleCheckEnvName, err) + } + + var e2eClass map[string]interface{} + var defaultClass map[string]interface{} + + for i := range vmClasses.Items { + vmClass := vmClasses.Items[i].Object + name, ok := vmClass["metadata"].(map[string]interface{})["name"].(string) + if !ok { + continue + } + + if name == defaultVMClassName { + e2eClass = vmClass + } + + // Check for default annotation + metadata, ok := vmClass["metadata"].(map[string]interface{}) + if !ok { + continue + } + annotations, ok := metadata["annotations"].(map[string]interface{}) + if !ok { + continue + } + if _, ok := annotations["virtualmachineclass.virtualization.deckhouse.io/is-default-class"]; ok { + defaultClass = vmClass + } + } + + // Helper to get name from vmClass + getVMClassName := func(m map[string]interface{}) string { + if m == nil { + return "" + } + metadata, ok := m["metadata"].(map[string]interface{}) + if !ok { + return "" + } + name, _ := metadata["name"].(string) + return name + } + + // Check if default VMClass exists and is correct + switch { + case e2eClass != nil && defaultClass != nil && getVMClassName(defaultClass) == defaultVMClassName: + // OK + case e2eClass != nil && defaultClass != nil: + return fmt.Errorf("%s=no to disable this precheck: cluster has wrong default class %q, e2e tests require %q to be default", + vmcModuleCheckEnvName, getVMClassName(defaultClass), defaultVMClassName) + case e2eClass == nil && defaultClass != nil: + return fmt.Errorf("%s=no to disable this precheck: cluster has wrong default class %q, e2e tests require %q to be default", + vmcModuleCheckEnvName, getVMClassName(defaultClass), defaultVMClassName) + case e2eClass != nil && defaultClass == nil: + return fmt.Errorf("%s=no to disable this precheck: cluster has no default class, e2e tests require %q to be default. Run: kubectl annotate vmclass/%s virtualmachineclass.virtualization.deckhouse.io/is-default-class=true", + vmcModuleCheckEnvName, defaultVMClassName, defaultVMClassName) + case e2eClass == nil && defaultClass == nil: + return fmt.Errorf("%s=no to disable this precheck: cluster has no default class and no %q class. Run: kubectl get vmclass/generic -o json | jq 'del(.status) | .metadata.annotations = {\"virtualmachineclass.virtualization.deckhouse.io/is-default-class\":\"true\"}' | kubectl create -f -", + vmcModuleCheckEnvName, defaultVMClassName) + } + + return nil +} + +// Register VMC precheck as common (runs for all tests). +func init() { + RegisterPrecheck(&vmcPrecheck{}, true) +} diff --git a/test/e2e/internal/util/sdn.go b/test/e2e/internal/util/sdn.go index 753f06f78b..b72fa1756a 100644 --- a/test/e2e/internal/util/sdn.go +++ b/test/e2e/internal/util/sdn.go @@ -54,7 +54,7 @@ EOF`, ClusterNetworkName(vlanID), vlanID) func IsSdnModuleEnabled(f *framework.Framework) bool { GinkgoHelper() - sdnModule, err := f.GetModuleConfig("sdn") + sdnModule, err := f.GetModuleConfig(context.Background(), "sdn") Expect(err).NotTo(HaveOccurred()) enabled := sdnModule.Spec.Enabled diff --git a/test/e2e/internal/util/until.go b/test/e2e/internal/util/until.go index f541f9c69d..422d62a761 100644 --- a/test/e2e/internal/util/until.go +++ b/test/e2e/internal/util/until.go @@ -41,8 +41,8 @@ func UntilObjectPhase(expectedPhase string, timeout time.Duration, objs ...clien } // UntilConditionReason waits for the specified conditionType in status.conditions to have the given reason value for all provided objects. -func UntilConditionReason(conditionType, expectedReason string, timeout time.Duration, objs ...client.Object) { - UntilConditionState(conditionType, timeout, struct { +func UntilConditionReason(ctx context.Context, conditionType, expectedReason string, timeout time.Duration, objs ...client.Object) { + UntilConditionState(ctx, conditionType, timeout, struct { Reason string Status string Message string @@ -56,8 +56,8 @@ func UntilConditionReason(conditionType, expectedReason string, timeout time.Dur } // UntilConditionStatus waits for the specified conditionType in status.conditions to have the given status value for all provided objects. -func UntilConditionStatus(conditionType, expectedStatus string, timeout time.Duration, objs ...client.Object) { - UntilConditionState(conditionType, timeout, struct { +func UntilConditionStatus(ctx context.Context, conditionType, expectedStatus string, timeout time.Duration, objs ...client.Object) { + UntilConditionState(ctx, conditionType, timeout, struct { Reason string Status string Message string @@ -73,6 +73,7 @@ func UntilConditionStatus(conditionType, expectedStatus string, timeout time.Dur // UntilConditionState generalizes condition field checks ("reason", "status", "message") for the specified conditionType. // You can specify which fields to check by setting the corresponding flags to true and providing their expected values. func UntilConditionState( + ctx context.Context, conditionType string, timeout time.Duration, checkOptions struct { @@ -90,7 +91,7 @@ func UntilConditionState( for _, obj := range objs { key := client.ObjectKeyFromObject(obj) u := getTemplateUnstructured(obj).DeepCopy() - err := framework.GetClients().GenericClient().Get(context.Background(), key, u) + err := framework.GetClients().GenericClient().Get(ctx, key, u) g.Expect(err).ShouldNot(HaveOccurred()) conditions, found, err := unstructured.NestedSlice(u.Object, "status", "conditions") diff --git a/test/e2e/legacy/complex.go b/test/e2e/legacy/complex.go index a377a2fd9c..cb3df3a286 100644 --- a/test/e2e/legacy/complex.go +++ b/test/e2e/legacy/complex.go @@ -17,6 +17,7 @@ limitations under the License. package legacy import ( + "context" "fmt" "maps" "strings" @@ -29,6 +30,7 @@ import ( "github.com/deckhouse/virtualization/test/e2e/internal/framework" kc "github.com/deckhouse/virtualization/test/e2e/internal/kubectl" "github.com/deckhouse/virtualization/test/e2e/internal/label" + "github.com/deckhouse/virtualization/test/e2e/internal/precheck" "github.com/deckhouse/virtualization/test/e2e/internal/util" ) @@ -37,12 +39,12 @@ const ( antiAffinityLabel = "anti-affinity" ) -var _ = Describe("ComplexTest", Ordered, label.Legacy(), func() { +var _ = Describe("ComplexTest", Ordered, label.Legacy(), Label(precheck.NoPrecheck), func() { var ( testCaseLabel = map[string]string{"testcase": "complex-test"} hasNoConsumerLabel = map[string]string{"hasNoConsumer": "complex-test"} ns string - phaseByVolumeBindingMode = util.GetExpectedDiskPhaseByVolumeBindingMode() + phaseByVolumeBindingMode string ) AfterEach(func() { @@ -66,6 +68,8 @@ var _ = Describe("ComplexTest", Ordered, label.Legacy(), func() { Expect(err).NotTo(HaveOccurred(), "%w", err) CreateNamespace(ns) + + phaseByVolumeBindingMode = util.GetExpectedDiskPhaseByVolumeBindingMode() }) Context("When virtualization resources are applied", func() { @@ -240,7 +244,7 @@ var _ = Describe("ComplexTest", Ordered, label.Legacy(), func() { }) func AssignIPToVMIP(f *framework.Framework, vmipNamespace, vmipName string) error { - mc, err := f.GetVirtualizationModuleConfig() + mc, err := f.GetVirtualizationModuleConfig(context.Background()) if err != nil { return err } diff --git a/test/e2e/legacy/image_hotplug.go b/test/e2e/legacy/image_hotplug.go index bdcfc65a6f..8444c3eaaf 100644 --- a/test/e2e/legacy/image_hotplug.go +++ b/test/e2e/legacy/image_hotplug.go @@ -34,6 +34,7 @@ import ( kc "github.com/deckhouse/virtualization/test/e2e/internal/kubectl" "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" ) @@ -41,7 +42,7 @@ const unacceptableCount = -1000 var APIVersion = v1alpha2.SchemeGroupVersion.String() -var _ = Describe("ImageHotplug", Ordered, label.Legacy(), func() { +var _ = Describe("ImageHotplug", Ordered, label.Legacy(), Label(precheck.NoPrecheck), func() { const ( viCount = 2 cviCount = 2 diff --git a/test/e2e/legacy/legacy.go b/test/e2e/legacy/legacy.go index a9b264846f..9d80a37267 100644 --- a/test/e2e/legacy/legacy.go +++ b/test/e2e/legacy/legacy.go @@ -58,11 +58,10 @@ var ( namePrefix string ) -func init() { - err := configure() - if err != nil { - panic(fmt.Errorf("failed to configure: %w", err)) - } +// Init configures the legacy package. +// This should be called before using legacy test functions. +func Init() error { + return configure() } func configure() (err error) { @@ -107,10 +106,6 @@ func configure() (err error) { return err } - if err = config.CheckDefaultVMClass(clients.VirtClient()); err != nil { - return err - } - //nolint:staticcheck // It can be used in legacy tests. namePrefix, err = framework.NewFramework("").GetNamePrefix(conf.StorageClass.TemplateStorageClass) if err != nil { @@ -125,6 +120,10 @@ func configure() (err error) { } func NewBeforeProcess1Body() { + if err := Init(); err != nil { + panic(fmt.Errorf("failed to init legacy: %w", err)) + } + var kustomizationFiles []string v := reflect.ValueOf(conf.TestData) t := reflect.TypeOf(conf.TestData) diff --git a/test/e2e/legacy/vd_snapshots.go b/test/e2e/legacy/vd_snapshots.go index fa748c4d93..19175fe6af 100644 --- a/test/e2e/legacy/vd_snapshots.go +++ b/test/e2e/legacy/vd_snapshots.go @@ -34,6 +34,7 @@ import ( "github.com/deckhouse/virtualization/test/e2e/internal/config" kc "github.com/deckhouse/virtualization/test/e2e/internal/kubectl" "github.com/deckhouse/virtualization/test/e2e/internal/label" + "github.com/deckhouse/virtualization/test/e2e/internal/precheck" "github.com/deckhouse/virtualization/test/e2e/internal/util" ) @@ -43,7 +44,7 @@ const ( frozenReasonPollingInterval = 1 * time.Second ) -var _ = Describe("VirtualDiskSnapshots", Ordered, label.Legacy(), func() { +var _ = Describe("VirtualDiskSnapshots", Ordered, label.Legacy(), Label(precheck.PrecheckSnapshot), func() { var ( testCaseLabel = map[string]string{"testcase": "vd-snapshots", "id": namePrefix} attachedVirtualDiskLabel = map[string]string{"attachedVirtualDisk": ""} diff --git a/test/e2e/legacy/vm_disk_resizing.go b/test/e2e/legacy/vm_disk_resizing.go index 2323593271..4a233b9794 100644 --- a/test/e2e/legacy/vm_disk_resizing.go +++ b/test/e2e/legacy/vm_disk_resizing.go @@ -34,9 +34,10 @@ import ( "github.com/deckhouse/virtualization/test/e2e/internal/framework" kc "github.com/deckhouse/virtualization/test/e2e/internal/kubectl" "github.com/deckhouse/virtualization/test/e2e/internal/label" + "github.com/deckhouse/virtualization/test/e2e/internal/precheck" ) -var _ = Describe("VirtualDiskResizing", Ordered, label.Legacy(), func() { +var _ = Describe("VirtualDiskResizing", Ordered, label.Legacy(), Label(precheck.NoPrecheck), func() { const ( vmCount = 1 diskCount = 3 diff --git a/test/e2e/legacy/vm_evacuation.go b/test/e2e/legacy/vm_evacuation.go index dd60b9aafd..9205cfff5b 100644 --- a/test/e2e/legacy/vm_evacuation.go +++ b/test/e2e/legacy/vm_evacuation.go @@ -27,19 +27,23 @@ import ( corev1 "k8s.io/api/core/v1" policyv1 "k8s.io/api/policy/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" "github.com/deckhouse/virtualization/api/core/v1alpha2" "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" "github.com/deckhouse/virtualization/test/e2e/internal/label" + "github.com/deckhouse/virtualization/test/e2e/internal/precheck" ) -var _ = Describe("VirtualMachineEvacuation", Ordered, label.Legacy(), func() { +var _ = Describe("VirtualMachineEvacuation", Ordered, label.Legacy(), Label(precheck.NoPrecheck), func() { testCaseLabel := map[string]string{"testcase": "vm-evacuation"} - kubeClient := framework.GetClients().KubeClient() - var ns string + var ( + ns string + kubeClient kubernetes.Interface + ) BeforeAll(func() { kustomization := fmt.Sprintf("%s/%s", conf.TestData.VMEvacuation, "kustomization.yaml") var err error @@ -47,6 +51,8 @@ var _ = Describe("VirtualMachineEvacuation", Ordered, label.Legacy(), func() { Expect(err).NotTo(HaveOccurred(), "%w", err) CreateNamespace(ns) + + kubeClient = framework.GetClients().KubeClient() }) BeforeEach(func() { diff --git a/test/e2e/legacy/vm_label_annotation.go b/test/e2e/legacy/vm_label_annotation.go index 8e803e2973..d6e3d2983d 100644 --- a/test/e2e/legacy/vm_label_annotation.go +++ b/test/e2e/legacy/vm_label_annotation.go @@ -28,12 +28,13 @@ import ( "github.com/deckhouse/virtualization/test/e2e/internal/config" kc "github.com/deckhouse/virtualization/test/e2e/internal/kubectl" "github.com/deckhouse/virtualization/test/e2e/internal/label" + "github.com/deckhouse/virtualization/test/e2e/internal/precheck" ) // TODO: When this test case is refactored with the new end-to-end test framework, // it should check labels and annotations on all resources: KVVM, KVVMI, and Pod. // KVVM must contain propagated metadata in the spec.template.metadata field. -var _ = Describe("VirtualMachineLabelAndAnnotation", Ordered, label.Legacy(), func() { +var _ = Describe("VirtualMachineLabelAndAnnotation", Ordered, label.Legacy(), Label(precheck.NoPrecheck), func() { const ( specialKey = "specialKey" specialValue = "specialValue" diff --git a/test/e2e/legacy/vm_migration_cancel.go b/test/e2e/legacy/vm_migration_cancel.go index fef7411e2b..162c2c7bd9 100644 --- a/test/e2e/legacy/vm_migration_cancel.go +++ b/test/e2e/legacy/vm_migration_cancel.go @@ -31,9 +31,10 @@ import ( "github.com/deckhouse/virtualization/test/e2e/internal/framework" kc "github.com/deckhouse/virtualization/test/e2e/internal/kubectl" "github.com/deckhouse/virtualization/test/e2e/internal/label" + "github.com/deckhouse/virtualization/test/e2e/internal/precheck" ) -var _ = Describe("VirtualMachineCancelMigration", Ordered, label.Legacy(), func() { +var _ = Describe("VirtualMachineCancelMigration", Ordered, label.Legacy(), Label(precheck.NoPrecheck), func() { testCaseLabel := map[string]string{"testcase": "vm-migration-cancel"} var ns string diff --git a/test/e2e/scripts/precheck-prepare.sh b/test/e2e/scripts/precheck-prepare.sh new file mode 100755 index 0000000000..623d66f15b --- /dev/null +++ b/test/e2e/scripts/precheck-prepare.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +# 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. + +# Generate JSON report via ginkgo dry-run for precheck preparation +# This script suppresses output while preserving error reporting + +set -e + +# Build ginkgo command +CMD="go tool ginkgo --json-report=/tmp/e2e-specs.json --dry-run --no-color" + +# Add label filter based on environment variables +if [ -n "$FOCUS" ]; then + CMD="$CMD --focus=$FOCUS" +elif [ -n "$LABELS" ]; then + CMD="$CMD --label-filter=$LABELS" +fi + +# Run with suppressed stdout, but show stderr +$CMD 2>&1 > /dev/null + +echo "Precheck prepare completed" diff --git a/test/e2e/snapshot/vmsop.go b/test/e2e/snapshot/vmsop.go index b8ec0684cd..90152dfcbf 100644 --- a/test/e2e/snapshot/vmsop.go +++ b/test/e2e/snapshot/vmsop.go @@ -35,10 +35,11 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/test/e2e/internal/framework" "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("VMSOPCreateVirtualMachine", Ordered, func() { +var _ = Describe("VMSOPCreateVirtualMachine", Ordered, Label(precheck.PrecheckSnapshot), func() { var ( vd *v1alpha2.VirtualDisk vdBlank *v1alpha2.VirtualDisk @@ -47,10 +48,11 @@ var _ = Describe("VMSOPCreateVirtualMachine", Ordered, func() { vmsop *v1alpha2.VirtualMachineSnapshotOperation vmbda *v1alpha2.VirtualMachineBlockDeviceAttachment - f = framework.NewFramework("vmsop") + f *framework.Framework ) BeforeAll(func() { + f = framework.NewFramework("vmsop") cfg := framework.GetConfig() if cfg.StorageClass.TemplateStorageClass != nil && cfg.StorageClass.TemplateStorageClass.Provisioner == framework.NFS { Skip("Not working due to bug with VMBDA on NFS right now, skipping") diff --git a/test/e2e/vm/additional_network_interfaces.go b/test/e2e/vm/additional_network_interfaces.go index aebfcd2ad6..4569baead2 100644 --- a/test/e2e/vm/additional_network_interfaces.go +++ b/test/e2e/vm/additional_network_interfaces.go @@ -37,6 +37,7 @@ import ( "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/precheck" "github.com/deckhouse/virtualization/test/e2e/internal/util" ) @@ -56,17 +57,18 @@ func expectClusterNetworkExists(f *framework.Framework, vlanID int) { fmt.Sprintf("Cluster network %s does not exist. Create it first: %s", util.ClusterNetworkName(vlanID), util.ClusterNetworkCreateCommand(vlanID))) } -var _ = Describe("VirtualMachineAdditionalNetworkInterfaces", func() { +var _ = Describe("VirtualMachineAdditionalNetworkInterfaces", Label(precheck.NoPrecheck, precheck.PrecheckSDN), func() { var ( vdFooRoot *v1alpha2.VirtualDisk vdBarRoot *v1alpha2.VirtualDisk vmFoo *v1alpha2.VirtualMachine vmBar *v1alpha2.VirtualMachine - f = framework.NewFramework("vm-additional-network") + f *framework.Framework ) BeforeEach(func() { + f = framework.NewFramework("vm-additional-network") DeferCleanup(f.After) f.Before() @@ -111,7 +113,14 @@ var _ = Describe("VirtualMachineAdditionalNetworkInterfaces", func() { // If test fail due this timeout, rollback in test waiting for agent to be ready. By("Wait for additional network interfaces to be ready", func() { - util.UntilConditionStatus(vmcondition.TypeNetworkReady.String(), "True", framework.LongTimeout, vmFoo, vmBar) + util.UntilConditionStatus( + context.Background(), + vmcondition.TypeNetworkReady.String(), + "True", + framework.LongTimeout, + vmFoo, + vmBar, + ) }) By("Check connectivity between VMs via additional network", func() { @@ -147,7 +156,14 @@ var _ = Describe("VirtualMachineAdditionalNetworkInterfaces", func() { }) By("Wait for additional network interfaces to be ready after migration", func() { - util.UntilConditionStatus(vmcondition.TypeNetworkReady.String(), "True", framework.LongTimeout, vmFoo, vmBar) + util.UntilConditionStatus( + context.Background(), + vmcondition.TypeNetworkReady.String(), + "True", + framework.LongTimeout, + vmFoo, + vmBar, + ) }) By("Check connectivity between VMs via additional network after migration", func() { @@ -189,7 +205,13 @@ var _ = Describe("VirtualMachineAdditionalNetworkInterfaces", func() { util.UntilObjectPhase(string(v1alpha2.MachineRunning), framework.LongTimeout, vm) util.UntilVMAgentReady(crclient.ObjectKeyFromObject(vm), framework.LongTimeout) - util.UntilConditionStatus(vmcondition.TypeNetworkReady.String(), "True", framework.LongTimeout, vm) + util.UntilConditionStatus( + context.Background(), + vmcondition.TypeNetworkReady.String(), + "True", + framework.LongTimeout, + vm, + ) }) By("Get last interface name via SSH", func() { @@ -220,7 +242,13 @@ var _ = Describe("VirtualMachineAdditionalNetworkInterfaces", func() { util.UntilVirtualMachineRebooted(crclient.ObjectKeyFromObject(vm), previousRunningTime, framework.LongTimeout) util.UntilObjectPhase(string(v1alpha2.MachineRunning), framework.LongTimeout, vm) util.UntilVMAgentReady(crclient.ObjectKeyFromObject(vm), framework.LongTimeout) - util.UntilConditionStatus(vmcondition.TypeNetworkReady.String(), "True", framework.LongTimeout, vm) + util.UntilConditionStatus( + context.Background(), + vmcondition.TypeNetworkReady.String(), + "True", + framework.LongTimeout, + vm, + ) }) By("Verify last interface name has not changed", func() { diff --git a/test/e2e/vm/affinity_toleration.go b/test/e2e/vm/affinity_toleration.go index bb42501e99..265b15943f 100644 --- a/test/e2e/vm/affinity_toleration.go +++ b/test/e2e/vm/affinity_toleration.go @@ -35,6 +35,7 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2/vmcondition" "github.com/deckhouse/virtualization/test/e2e/internal/framework" "github.com/deckhouse/virtualization/test/e2e/internal/object" + "github.com/deckhouse/virtualization/test/e2e/internal/precheck" "github.com/deckhouse/virtualization/test/e2e/internal/util" ) @@ -54,7 +55,7 @@ const ( migrationTargetMustDiffer ) -var _ = Describe("VirtualMachineAffinityAndToleration", Ordered, func() { +var _ = Describe("VirtualMachineAffinityAndToleration", Ordered, Label(precheck.NoPrecheck), func() { var ( f *framework.Framework ctx context.Context @@ -158,7 +159,13 @@ var _ = Describe("VirtualMachineAffinityAndToleration", Ordered, func() { }) By("Changing vm-c affinity to anti-affinity and verifying migration to another node", func() { - util.UntilConditionStatus(vmcondition.TypeMigratable.String(), string(metav1.ConditionTrue), framework.LongTimeout, vmC) + util.UntilConditionStatus( + ctx, + vmcondition.TypeMigratable.String(), + string(metav1.ConditionTrue), + framework.LongTimeout, + vmC, + ) vmC = getVirtualMachine(ctx, f, vmC.Name) sourceNode := vmC.Status.Node @@ -168,8 +175,16 @@ var _ = Describe("VirtualMachineAffinityAndToleration", Ordered, func() { err := f.GenericClient().Update(ctx, vmC) Expect(err).NotTo(HaveOccurred()) - waitForStabilizedVMMigration(ctx, f, crclient.ObjectKeyFromObject(vmC), startedAt, sourceNode, nodeA, migrationTargetMustDiffer, framework.MaxTimeout) - util.UntilConditionStatus(vmcondition.TypeMigratable.String(), string(metav1.ConditionTrue), framework.LongTimeout, vmC) + waitForStabilizedVMMigration( + ctx, + f, + crclient.ObjectKeyFromObject(vmC), + startedAt, + sourceNode, + nodeA, + migrationTargetMustDiffer, + framework.MaxTimeout, + ) }) var migratedNodeC string @@ -180,7 +195,7 @@ var _ = Describe("VirtualMachineAffinityAndToleration", Ordered, func() { }) By("Changing vm-c anti-affinity back to affinity and verifying migration back to vm-a node", func() { - util.UntilConditionStatus(vmcondition.TypeMigratable.String(), string(metav1.ConditionTrue), framework.LongTimeout, vmC) + util.UntilConditionStatus(ctx, vmcondition.TypeMigratable.String(), string(metav1.ConditionTrue), framework.LongTimeout, vmC) vmC = getVirtualMachine(ctx, f, vmC.Name) startedAt := time.Now().UTC() @@ -189,8 +204,16 @@ var _ = Describe("VirtualMachineAffinityAndToleration", Ordered, func() { err := f.GenericClient().Update(ctx, vmC) Expect(err).NotTo(HaveOccurred()) - waitForStabilizedVMMigration(ctx, f, crclient.ObjectKeyFromObject(vmC), startedAt, migratedNodeC, nodeA, migrationTargetMustMatch, framework.MaxTimeout) - util.UntilConditionStatus(vmcondition.TypeMigratable.String(), string(metav1.ConditionTrue), framework.LongTimeout, vmC) + waitForStabilizedVMMigration( + ctx, + f, + crclient.ObjectKeyFromObject(vmC), + startedAt, + migratedNodeC, + nodeA, + migrationTargetMustMatch, + framework.MaxTimeout, + ) }) By("Verifying vm-c returned to vm-a node via status.nodeName", func() { @@ -221,7 +244,13 @@ var _ = Describe("VirtualMachineAffinityAndToleration", Ordered, func() { Expect(err).NotTo(HaveOccurred()) util.UntilObjectPhase(string(v1alpha2.MachineRunning), framework.LongTimeout, vmNodeSelector) - util.UntilConditionStatus(vmcondition.TypeMigratable.String(), string(metav1.ConditionTrue), framework.LongTimeout, vmNodeSelector) + util.UntilConditionStatus( + ctx, + vmcondition.TypeMigratable.String(), + string(metav1.ConditionTrue), + framework.LongTimeout, + vmNodeSelector, + ) }) var sourceNode string @@ -249,8 +278,16 @@ var _ = Describe("VirtualMachineAffinityAndToleration", Ordered, func() { err = f.GenericClient().Update(ctx, vmNodeSelector) Expect(err).NotTo(HaveOccurred()) - waitForStabilizedVMMigration(ctx, f, crclient.ObjectKeyFromObject(vmNodeSelector), startedAt, sourceNode, targetNode, migrationTargetMustMatch, framework.MaxTimeout) - util.UntilConditionStatus(vmcondition.TypeMigratable.String(), string(metav1.ConditionTrue), framework.LongTimeout, vmNodeSelector) + waitForStabilizedVMMigration( + ctx, + f, + crclient.ObjectKeyFromObject(vmNodeSelector), + startedAt, + sourceNode, + targetNode, + migrationTargetMustMatch, + framework.MaxTimeout, + ) }) By("Verifying the nodeSelector migration result via status.nodeName", func() { @@ -284,7 +321,13 @@ var _ = Describe("VirtualMachineAffinityAndToleration", Ordered, func() { Expect(err).NotTo(HaveOccurred()) util.UntilObjectPhase(string(v1alpha2.MachineRunning), framework.LongTimeout, vmNodeAffinity) - util.UntilConditionStatus(vmcondition.TypeMigratable.String(), string(metav1.ConditionTrue), framework.LongTimeout, vmNodeAffinity) + util.UntilConditionStatus( + ctx, + vmcondition.TypeMigratable.String(), + string(metav1.ConditionTrue), + framework.LongTimeout, + vmNodeAffinity, + ) }) var sourceNode string @@ -312,8 +355,16 @@ var _ = Describe("VirtualMachineAffinityAndToleration", Ordered, func() { err = f.GenericClient().Update(ctx, vmNodeAffinity) Expect(err).NotTo(HaveOccurred()) - waitForStabilizedVMMigration(ctx, f, crclient.ObjectKeyFromObject(vmNodeAffinity), startedAt, sourceNode, targetNode, migrationTargetMustMatch, framework.MaxTimeout) - util.UntilConditionStatus(vmcondition.TypeMigratable.String(), string(metav1.ConditionTrue), framework.LongTimeout, vmNodeAffinity) + waitForStabilizedVMMigration( + ctx, + f, + crclient.ObjectKeyFromObject(vmNodeAffinity), + startedAt, + sourceNode, + targetNode, + migrationTargetMustMatch, + framework.MaxTimeout, + ) }) By("Verifying the nodeAffinity migration result via status.nodeName", func() { @@ -450,6 +501,13 @@ func waitForStabilizedVMMigration( default: Fail(fmt.Sprintf("unknown migration target expectation: %d", targetExpectation)) } + util.UntilConditionStatus( + ctx, + vmcondition.TypeMigratable.String(), + string(metav1.ConditionTrue), + framework.LongTimeout, + vm, + ) }).WithTimeout(timeout).WithPolling(time.Second).Should(Succeed()) } diff --git a/test/e2e/vm/configuration.go b/test/e2e/vm/configuration.go index 7f5eab2a90..5a28e88ec0 100644 --- a/test/e2e/vm/configuration.go +++ b/test/e2e/vm/configuration.go @@ -35,6 +35,7 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2/vmcondition" "github.com/deckhouse/virtualization/test/e2e/internal/framework" "github.com/deckhouse/virtualization/test/e2e/internal/object" + "github.com/deckhouse/virtualization/test/e2e/internal/precheck" "github.com/deckhouse/virtualization/test/e2e/internal/util" ) @@ -47,7 +48,7 @@ const ( changedCoreFraction = "50%" ) -var _ = Describe("VirtualMachineConfiguration", func() { +var _ = Describe("VirtualMachineConfiguration", Label(precheck.NoPrecheck), func() { DescribeTable("the configuration should be applied", func(restartApprovalMode v1alpha2.RestartApprovalMode) { f := framework.NewFramework(fmt.Sprintf("vm-configuration-%s", strings.ToLower(string(restartApprovalMode)))) t := NewConfigurationTest(f) diff --git a/test/e2e/vm/connectivity.go b/test/e2e/vm/connectivity.go index af776e520f..2d818ff671 100644 --- a/test/e2e/vm/connectivity.go +++ b/test/e2e/vm/connectivity.go @@ -37,10 +37,11 @@ import ( "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/precheck" "github.com/deckhouse/virtualization/test/e2e/internal/util" ) -var _ = Describe("VirtualMachineConnectivity", func() { +var _ = Describe("VirtualMachineConnectivity", Label(precheck.NoPrecheck), func() { var ( f *framework.Framework t *VMConnectivityTest diff --git a/test/e2e/vm/disk_attachment.go b/test/e2e/vm/disk_attachment.go index 468747b0c6..d29e5ddf87 100644 --- a/test/e2e/vm/disk_attachment.go +++ b/test/e2e/vm/disk_attachment.go @@ -31,10 +31,11 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/test/e2e/internal/framework" "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("DiskAttachment", func() { +var _ = Describe("DiskAttachment", Label(precheck.NoPrecheck), func() { var ( f *framework.Framework vdRoot *v1alpha2.VirtualDisk diff --git a/test/e2e/vm/hotplug_cpu.go b/test/e2e/vm/hotplug_cpu.go index bc122c2344..426b484ce4 100644 --- a/test/e2e/vm/hotplug_cpu.go +++ b/test/e2e/vm/hotplug_cpu.go @@ -34,10 +34,11 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/test/e2e/internal/framework" "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("HotplugCPU", func() { +var _ = Describe("HotplugCPU", Label(precheck.NoPrecheck), func() { var ( f *framework.Framework t *cpuHotplugTest diff --git a/test/e2e/vm/hotplug_memory.go b/test/e2e/vm/hotplug_memory.go index 8cfe036263..190b7637d1 100644 --- a/test/e2e/vm/hotplug_memory.go +++ b/test/e2e/vm/hotplug_memory.go @@ -36,10 +36,11 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/test/e2e/internal/framework" "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("HotplugMemory", func() { +var _ = Describe("HotplugMemory", Label(precheck.NoPrecheck), func() { var ( f *framework.Framework t *memoryHotplugTest diff --git a/test/e2e/vm/hotplug_pod.go b/test/e2e/vm/hotplug_pod.go index aca6c64fa7..98049e5a93 100644 --- a/test/e2e/vm/hotplug_pod.go +++ b/test/e2e/vm/hotplug_pod.go @@ -32,16 +32,18 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/test/e2e/internal/framework" "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("HotplugPod", func() { +var _ = Describe("HotplugPod", Label(precheck.NoPrecheck), func() { var ( - f = framework.NewFramework("hotplug-pod") + f *framework.Framework vi *v1alpha2.VirtualImage ) BeforeEach(func() { + f = framework.NewFramework("hotplug-pod") f.Before() DeferCleanup(f.After) diff --git a/test/e2e/vm/ipam.go b/test/e2e/vm/ipam.go index da19345e52..9e9d15dc3f 100644 --- a/test/e2e/vm/ipam.go +++ b/test/e2e/vm/ipam.go @@ -34,15 +34,17 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2/vmiplcondition" "github.com/deckhouse/virtualization/test/e2e/internal/framework" "github.com/deckhouse/virtualization/test/e2e/internal/object" + "github.com/deckhouse/virtualization/test/e2e/internal/precheck" ) -var _ = Describe("IPAM", func() { +var _ = Describe("IPAM", Label(precheck.NoPrecheck), func() { var ( - f = framework.NewFramework("ipam") + f *framework.Framework ctx context.Context ) BeforeEach(func() { + f = framework.NewFramework("ipam") f.Before() ctx = context.Background() }) diff --git a/test/e2e/vm/live_migration_tcp_session.go b/test/e2e/vm/live_migration_tcp_session.go index 665dd97486..5472bc5188 100644 --- a/test/e2e/vm/live_migration_tcp_session.go +++ b/test/e2e/vm/live_migration_tcp_session.go @@ -27,6 +27,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + storagev1 "k8s.io/api/storage/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" @@ -37,10 +38,11 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/test/e2e/internal/framework" "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("VirtualMachineLiveMigrationTCPSession", func() { +var _ = Describe("VirtualMachineLiveMigrationTCPSession", Label(precheck.NoPrecheck), func() { var ( iperfServer *v1alpha2.VirtualMachine iperfClient *v1alpha2.VirtualMachine @@ -50,11 +52,14 @@ var _ = Describe("VirtualMachineLiveMigrationTCPSession", func() { iperfServerName = "iperf-server" iperfClientName = "iperf-client" - f = framework.NewFramework("vm-live-migration-tcp-session") - storageClass = framework.GetConfig().StorageClass.TemplateStorageClass + f *framework.Framework + storageClass *storagev1.StorageClass ) BeforeEach(func() { + f = framework.NewFramework("vm-live-migration-tcp-session") + storageClass = framework.GetConfig().StorageClass.TemplateStorageClass + DeferCleanup(f.After) f.Before() diff --git a/test/e2e/vm/migration.go b/test/e2e/vm/migration.go index 68dd2ccb14..9cb3b65f20 100644 --- a/test/e2e/vm/migration.go +++ b/test/e2e/vm/migration.go @@ -37,12 +37,13 @@ import ( "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/precheck" "github.com/deckhouse/virtualization/test/e2e/internal/util" ) const lsblkCommand = "lsblk -dn | wc -l" -var _ = Describe("VirtualMachineMigration", func() { +var _ = Describe("VirtualMachineMigration", Label(precheck.NoPrecheck), func() { var ( // Core: VMs and their root/blank disks vdRootBIOS *v1alpha2.VirtualDisk @@ -63,12 +64,13 @@ var _ = Describe("VirtualMachineMigration", func() { vmopMigrateBIOS *v1alpha2.VirtualMachineOperation vmopMigrateUEFI *v1alpha2.VirtualMachineOperation - f = framework.NewFramework("vm-migration") + f *framework.Framework biosDiskCountOriginal string uefiDiskCountOriginal string ) BeforeEach(func() { + f = framework.NewFramework("vm-migration") DeferCleanup(f.After) f.Before() diff --git a/test/e2e/vm/power_state.go b/test/e2e/vm/power_state.go index 9545a54e3f..0ddc5d03d2 100644 --- a/test/e2e/vm/power_state.go +++ b/test/e2e/vm/power_state.go @@ -37,10 +37,11 @@ import ( "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/precheck" "github.com/deckhouse/virtualization/test/e2e/internal/util" ) -var _ = Describe("PowerState", func() { +var _ = Describe("PowerState", Label(precheck.NoPrecheck), func() { DescribeTable("manages power state of a virtual machine", func(runPolicy v1alpha2.RunPolicy) { var namespaceSuffix string switch runPolicy { diff --git a/test/e2e/vm/sizing_policy.go b/test/e2e/vm/sizing_policy.go index 59171a2767..6b3c24dd92 100644 --- a/test/e2e/vm/sizing_policy.go +++ b/test/e2e/vm/sizing_policy.go @@ -38,17 +38,24 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha3" "github.com/deckhouse/virtualization/test/e2e/internal/framework" "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("SizingPolicy", func() { - var t *sizingPolicyTest - f := framework.NewFramework("sizing-policy") +var _ = Describe("SizingPolicy", Label(precheck.NoPrecheck), func() { + var ( + t *sizingPolicyTest + f *framework.Framework + ctx context.Context + ) BeforeEach(func() { + f = framework.NewFramework("sizing-policy") f.Before() DeferCleanup(f.After) t = newSizingPolicyTest(f) + + ctx = context.Background() }) It("should start VM normally with existing VMClass", func() { @@ -56,10 +63,10 @@ var _ = Describe("SizingPolicy", func() { vmClassName := fmt.Sprintf("%s-vmclass", f.Namespace().Name) t.GenerateSizingPolicyResources(vmClassName, vmClassName) - err := f.CreateWithDeferredDeletion(context.Background(), t.VMClass) + err := f.CreateWithDeferredDeletion(ctx, t.VMClass) Expect(err).NotTo(HaveOccurred()) util.UntilObjectPhase(string(v1alpha2.ClassPhaseReady), framework.ShortTimeout, t.VMClass) - err = f.CreateWithDeferredDeletion(context.Background(), t.VD, t.VM) + err = f.CreateWithDeferredDeletion(ctx, t.VD, t.VM) Expect(err).NotTo(HaveOccurred()) By("Waiting for VM agent to be ready") @@ -74,14 +81,20 @@ var _ = Describe("SizingPolicy", func() { vmClassName := fmt.Sprintf("%s-existing-vmclass", f.Namespace().Name) t.GenerateSizingPolicyResources(vmClassName, vmClassName) - err := f.CreateWithDeferredDeletion(context.Background(), t.VD, t.VM) + err := f.CreateWithDeferredDeletion(ctx, t.VD, t.VM) Expect(err).NotTo(HaveOccurred()) By("Waiting for SizingPolicyMatched condition reason to be VirtualMachineClassNotExists") - util.UntilConditionReason(vmcondition.TypeSizingPolicyMatched.String(), vmcondition.ReasonVirtualMachineClassNotFound.String(), framework.LongTimeout, t.VM) + util.UntilConditionReason( + ctx, + vmcondition.TypeSizingPolicyMatched.String(), + vmcondition.ReasonVirtualMachineClassNotFound.String(), + framework.LongTimeout, + t.VM, + ) By("Creating VMClass") - err = f.CreateWithDeferredDeletion(context.Background(), t.VMClass) + err = f.CreateWithDeferredDeletion(ctx, t.VMClass) Expect(err).NotTo(HaveOccurred()) By("Waiting for VM to be ready") @@ -97,11 +110,17 @@ var _ = Describe("SizingPolicy", func() { vmClassNameInVM := fmt.Sprintf("%s-fake-vmclass", f.Namespace().Name) t.GenerateSizingPolicyResources(vmClassName, vmClassNameInVM) - err := f.CreateWithDeferredDeletion(context.Background(), t.VMClass, t.VD, t.VM) + err := f.CreateWithDeferredDeletion(ctx, t.VMClass, t.VD, t.VM) Expect(err).NotTo(HaveOccurred()) By("Waiting for SizingPolicyMatched condition reason to be VirtualMachineClassNotExists") - util.UntilConditionReason(vmcondition.TypeSizingPolicyMatched.String(), vmcondition.ReasonVirtualMachineClassNotFound.String(), framework.LongTimeout, t.VM) + util.UntilConditionReason( + ctx, + vmcondition.TypeSizingPolicyMatched.String(), + vmcondition.ReasonVirtualMachineClassNotFound.String(), + framework.LongTimeout, + t.VM, + ) By("Changing VMClass") patch, err := json.Marshal([]map[string]interface{}{{ @@ -110,7 +129,7 @@ var _ = Describe("SizingPolicy", func() { "value": vmClassName, }}) Expect(err).NotTo(HaveOccurred()) - err = f.GenericClient().Patch(context.Background(), t.VM, client.RawPatch(types.JSONPatchType, patch)) + err = f.GenericClient().Patch(ctx, t.VM, client.RawPatch(types.JSONPatchType, patch)) Expect(err).NotTo(HaveOccurred()) By("Waiting for VM to be ready") diff --git a/test/e2e/vm/target_migration.go b/test/e2e/vm/target_migration.go index 6c884ee9e6..0ca4ef4511 100644 --- a/test/e2e/vm/target_migration.go +++ b/test/e2e/vm/target_migration.go @@ -35,13 +35,14 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/test/e2e/internal/framework" "github.com/deckhouse/virtualization/test/e2e/internal/object" + "github.com/deckhouse/virtualization/test/e2e/internal/precheck" "github.com/deckhouse/virtualization/test/e2e/internal/rewrite" "github.com/deckhouse/virtualization/test/e2e/internal/util" ) const hostnameLabelKey = "kubernetes.io/hostname" -var _ = Describe("TargetMigration", func() { +var _ = Describe("TargetMigration", Label(precheck.NoPrecheck), func() { var ( virtualMachine *v1alpha2.VirtualMachine targetMigrationVMOP *v1alpha2.VirtualMachineOperation @@ -49,10 +50,11 @@ var _ = Describe("TargetMigration", func() { initialNodeName string targetNodeSelector map[string]string - f = framework.NewFramework("vm-target-migration") + f *framework.Framework ) BeforeEach(func() { + f = framework.NewFramework("vm-target-migration") DeferCleanup(f.After) f.Before() }) diff --git a/test/e2e/vm/tpm.go b/test/e2e/vm/tpm.go index 31fcface2f..0b224f3c9b 100644 --- a/test/e2e/vm/tpm.go +++ b/test/e2e/vm/tpm.go @@ -31,13 +31,15 @@ import ( "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("VMCheckTPM", label.TPM(), func() { - f := framework.NewFramework("vm-tpm-check") +var _ = Describe("VMCheckTPM", label.TPM(), Label(precheck.NoPrecheck), func() { + var f *framework.Framework BeforeEach(func() { + f = framework.NewFramework("vm-tpm-check") DeferCleanup(f.After) f.Before() diff --git a/test/e2e/vm/usb.go b/test/e2e/vm/usb.go index c3b69214e0..a10e1b2eed 100644 --- a/test/e2e/vm/usb.go +++ b/test/e2e/vm/usb.go @@ -34,10 +34,11 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2/nodeusbdevicecondition" "github.com/deckhouse/virtualization/test/e2e/internal/framework" "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("VirtualMachineUSB", func() { +var _ = Describe("VirtualMachineUSB", Label(precheck.PrecheckUSB), func() { var ( f *framework.Framework t *VMUSBTest diff --git a/test/e2e/vm/version.go b/test/e2e/vm/version.go index 9635da985d..b7787551f7 100644 --- a/test/e2e/vm/version.go +++ b/test/e2e/vm/version.go @@ -30,10 +30,11 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/test/e2e/internal/framework" "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("VirtualMachineVersions", func() { +var _ = Describe("VirtualMachineVersions", Label(precheck.NoPrecheck), func() { var f *framework.Framework BeforeEach(func() { diff --git a/test/e2e/vm/volume_migration_local_disks.go b/test/e2e/vm/volume_migration_local_disks.go index 779a0ca0fa..d34f7c124e 100644 --- a/test/e2e/vm/volume_migration_local_disks.go +++ b/test/e2e/vm/volume_migration_local_disks.go @@ -41,6 +41,7 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2/vmopcondition" "github.com/deckhouse/virtualization/test/e2e/internal/framework" "github.com/deckhouse/virtualization/test/e2e/internal/object" + "github.com/deckhouse/virtualization/test/e2e/internal/precheck" "github.com/deckhouse/virtualization/test/e2e/internal/util" ) @@ -53,14 +54,15 @@ func decoratorsForVolumeMigrations() []interface{} { // Ordered is required due to concurrent migration limitations in the cluster to prevent test interference. // ContinueOnFailure ensures all independent tests run even if one fails. -var _ = Describe("RWOVirtualDiskMigration", decoratorsForVolumeMigrations(), func() { +var _ = Describe("RWOVirtualDiskMigration", decoratorsForVolumeMigrations(), Label(precheck.NoPrecheck), func() { var ( - f = framework.NewFramework("volume-migration-local-disks") + f *framework.Framework storageClass *storagev1.StorageClass vi *v1alpha2.VirtualImage ) BeforeEach(func() { + f = framework.NewFramework("volume-migration-local-disks") storageClass = framework.GetConfig().StorageClass.TemplateStorageClass if storageClass == nil { Skip("TemplateStorageClass is not set.") diff --git a/test/e2e/vm/volume_migration_storage_class_changed.go b/test/e2e/vm/volume_migration_storage_class_changed.go index fc8aab32e8..497004fb1c 100644 --- a/test/e2e/vm/volume_migration_storage_class_changed.go +++ b/test/e2e/vm/volume_migration_storage_class_changed.go @@ -37,21 +37,23 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/test/e2e/internal/framework" "github.com/deckhouse/virtualization/test/e2e/internal/object" + "github.com/deckhouse/virtualization/test/e2e/internal/precheck" "github.com/deckhouse/virtualization/test/e2e/internal/rewrite" "github.com/deckhouse/virtualization/test/e2e/internal/util" ) // Ordered is required due to concurrent migration limitations in the cluster to prevent test interference. // ContinueOnFailure ensures all independent tests run even if one fails. -var _ = Describe("StorageClassMigration", decoratorsForVolumeMigrations(), func() { +var _ = Describe("StorageClassMigration", decoratorsForVolumeMigrations(), Label(precheck.NoPrecheck), func() { var ( - f = framework.NewFramework("volume-migration-storage-class-changed") + f *framework.Framework storageClass *storagev1.StorageClass vi *v1alpha2.VirtualImage targetStorageClassName string ) BeforeEach(func() { + f = framework.NewFramework("volume-migration-storage-class-changed") storageClass = framework.GetConfig().StorageClass.TemplateStorageClass if storageClass == nil { Skip("TemplateStorageClass is not set.") diff --git a/test/e2e/vmop/restore.go b/test/e2e/vmop/restore.go index 5fbae9cf5b..58e0d85c6d 100644 --- a/test/e2e/vmop/restore.go +++ b/test/e2e/vmop/restore.go @@ -42,6 +42,7 @@ import ( "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" "github.com/deckhouse/virtualization/test/e2e/legacy" ) @@ -69,7 +70,7 @@ const ( additionalInterfaceVLANID = 4006 ) -var _ = Describe("VirtualMachineOperationRestore", label.Slow(), func() { +var _ = Describe("VirtualMachineOperationRestore", label.Slow(), Label(precheck.PrecheckSnapshot, precheck.PrecheckSDN), func() { DescribeTable("restores a virtual machine from a snapshot", func(restoreMode v1alpha2.SnapshotOperationMode, restartApprovalMode v1alpha2.RestartApprovalMode, runPolicy v1alpha2.RunPolicy, removeRecoverableResources bool) { f := framework.NewFramework(fmt.Sprintf("vmop-restore-%s", strings.ToLower(string(restoreMode)))) DeferCleanup(f.After)