From df0e0eaee777d383a241631413db62221bb309f4 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Mon, 27 Apr 2026 02:15:14 +0300 Subject: [PATCH 01/11] test(e2e): add precheck-prepare.sh script for JSON report generation This script runs ginkgo dry-run with --json-report flag to generate a JSON report containing all test specs with their labels. The report is used by the precheck system in SynchronizedBeforeSuite to determine which prechecks to run based on test labels. Changes: - Add precheck-prepare.sh script that runs 'go tool ginkgo --dry-run --json-report=/tmp/e2e-specs.json' with FOCUS and LABELS filters Signed-off-by: Roman Sysoev --- test/e2e/scripts/precheck-prepare.sh | 35 ++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100755 test/e2e/scripts/precheck-prepare.sh 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" From 6aaa4be95c9fe37c7ce05717559f132954296106 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Mon, 27 Apr 2026 02:15:23 +0300 Subject: [PATCH 02/11] test(e2e): add Module API type for module status checks Add Module CRD type definition to check module status in prechecks. The precheck system needs to verify that modules like virtualization, sdn, snapshot-controller, and svdm are enabled and ready before running tests that depend on them. Changes: - Add module.go with Module struct definition and deep copy methods - Update register.go to register the new Module type in the scheme - Regenerate deep_copy.go to include the new type Signed-off-by: Roman Sysoev --- .../api/deckhouse/v1alpha1/deep_copy.go | 65 +++++++++++++++++++ .../internal/api/deckhouse/v1alpha1/module.go | 61 +++++++++++++++++ .../api/deckhouse/v1alpha1/register.go | 1 + 3 files changed, 127 insertions(+) create mode 100644 test/e2e/internal/api/deckhouse/v1alpha1/module.go 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) From 08a30130d79c98bba046ce313f47580ee30f29a1 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Mon, 27 Apr 2026 02:15:31 +0300 Subject: [PATCH 03/11] test(e2e): add precheck package for cluster configuration validation Add precheck system that validates cluster configuration before running e2e tests. This prevents test failures due to missing prerequisites. Components: - common.go: core logic for loading spec labels from JSON report, filtering by FOCUS/LABELS, and running prechecks. Includes common IsModuleEnabled() function to check if a module is enabled. - labels.go: precheck label constants (precheck.USB, precheck.SDN, etc.) - context.go: context helper for precheck execution - Individual prechecks: * storageclass.go: checks default StorageClass exists * virtualization.go: checks virtualization module is enabled and ready * vmc.go: checks VMClass exists and is set as default * sdn.go: checks SDN module is enabled and ClusterNetworks exist * svdm.go: checks SVDM module is enabled and ready * snapshot.go: checks snapshot-controller is enabled and VolumeSnapshotClasses exist * usb.go: checks dummy_hcd USB device is configured Precheck types: - Common prechecks: run for all tests (storageclass, virtualization, vmc) - Specific prechecks: run only for tests with matching label (sdn, svdm, snapshot, usb) Features: - Prechecks can be disabled via environment variables (e.g., USB_PRECHECK=no) - All prechecks can be disabled with PRECHECK=no - Each precheck provides clear error messages with instructions Signed-off-by: Roman Sysoev --- test/e2e/internal/precheck/common.go | 356 +++++++++++++++++++ test/e2e/internal/precheck/context.go | 24 ++ test/e2e/internal/precheck/labels.go | 72 ++++ test/e2e/internal/precheck/sdn.go | 125 +++++++ test/e2e/internal/precheck/snapshot.go | 86 +++++ test/e2e/internal/precheck/storageclass.go | 84 +++++ test/e2e/internal/precheck/svdm.go | 69 ++++ test/e2e/internal/precheck/usb.go | 83 +++++ test/e2e/internal/precheck/virtualization.go | 69 ++++ test/e2e/internal/precheck/vmc.go | 128 +++++++ 10 files changed, 1096 insertions(+) create mode 100644 test/e2e/internal/precheck/common.go create mode 100644 test/e2e/internal/precheck/context.go create mode 100644 test/e2e/internal/precheck/labels.go create mode 100644 test/e2e/internal/precheck/sdn.go create mode 100644 test/e2e/internal/precheck/snapshot.go create mode 100644 test/e2e/internal/precheck/storageclass.go create mode 100644 test/e2e/internal/precheck/svdm.go create mode 100644 test/e2e/internal/precheck/usb.go create mode 100644 test/e2e/internal/precheck/virtualization.go create mode 100644 test/e2e/internal/precheck/vmc.go diff --git a/test/e2e/internal/precheck/common.go b/test/e2e/internal/precheck/common.go new file mode 100644 index 0000000000..3b0a00a163 --- /dev/null +++ b/test/e2e/internal/precheck/common.go @@ -0,0 +1,356 @@ +/* +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/deckhouse/virtualization/test/e2e/internal/framework" + . "github.com/onsi/ginkgo/v2" +) + +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 string, 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. + focusRegex := os.Getenv("FOCUS") + 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) { + // Run common prechecks first (always run) + for _, p := range commonPrechecks { + GinkgoWriter.Write([]byte("Running common precheck: " + p.Label() + "\n")) + if err := p.Run(NewContext(), 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(NewContext(), 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(f *framework.Framework, moduleName string) bool { + module, err := f.GetModuleConfig(moduleName) + if err != nil { + GinkgoWriter.Write([]byte(fmt.Sprintf("failed to get %s module config: %v\n", moduleName, err))) + return false + } + enabled := module.Spec.Enabled + return enabled != nil && *enabled +} \ No newline at end of file diff --git a/test/e2e/internal/precheck/context.go b/test/e2e/internal/precheck/context.go new file mode 100644 index 0000000000..58058b6513 --- /dev/null +++ b/test/e2e/internal/precheck/context.go @@ -0,0 +1,24 @@ +/* +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" + +// NewContext creates a background context for precheck execution. +func NewContext() context.Context { + return context.Background() +} \ No newline at end of file diff --git a/test/e2e/internal/precheck/labels.go b/test/e2e/internal/precheck/labels.go new file mode 100644 index 0000000000..0d02d472c1 --- /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 +} \ No newline at end of file diff --git a/test/e2e/internal/precheck/sdn.go b/test/e2e/internal/precheck/sdn.go new file mode 100644 index 0000000000..cc496d840d --- /dev/null +++ b/test/e2e/internal/precheck/sdn.go @@ -0,0 +1,125 @@ +/* +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" + + "github.com/deckhouse/virtualization/test/e2e/internal/framework" + "sigs.k8s.io/controller-runtime/pkg/client" + + dv1alpha1 "github.com/deckhouse/virtualization/test/e2e/internal/api/deckhouse/v1alpha1" +) + +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) +} \ No newline at end of file diff --git a/test/e2e/internal/precheck/svdm.go b/test/e2e/internal/precheck/svdm.go new file mode 100644 index 0000000000..166627e7c5 --- /dev/null +++ b/test/e2e/internal/precheck/svdm.go @@ -0,0 +1,69 @@ +/* +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(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) +} \ No newline at end of file diff --git a/test/e2e/internal/precheck/usb.go b/test/e2e/internal/precheck/usb.go new file mode 100644 index 0000000000..5eba0535cb --- /dev/null +++ b/test/e2e/internal/precheck/usb.go @@ -0,0 +1,83 @@ +/* +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(f *framework.Framework) bool { + ctx := context.Background() + virtClient := f.VirtClient() + + nodeUSBList, err := virtClient.NodeUSBDevices().List(ctx, metav1.ListOptions{}) + if err != nil { + GinkgoWriter.Write([]byte(fmt.Sprintf("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(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..244e3bccb7 --- /dev/null +++ b/test/e2e/internal/precheck/virtualization.go @@ -0,0 +1,69 @@ +/* +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" + + "github.com/deckhouse/virtualization/test/e2e/internal/framework" + "sigs.k8s.io/controller-runtime/pkg/client" + + dv1alpha1 "github.com/deckhouse/virtualization/test/e2e/internal/api/deckhouse/v1alpha1" +) + +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(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) +} \ No newline at end of file diff --git a/test/e2e/internal/precheck/vmc.go b/test/e2e/internal/precheck/vmc.go new file mode 100644 index 0000000000..8afe2337a8 --- /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) +} From 8040cda7692a3fa4b97d0ce21e8ee803801d1185 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Mon, 27 Apr 2026 02:15:35 +0300 Subject: [PATCH 04/11] test(e2e): add precheck:prepare task to Taskfile.yaml Add precheck:prepare task that runs before e2e tests to generate JSON report with spec labels. This task is added as a dependency to the run task so it executes automatically. Changes: - Add precheck:prepare task that runs precheck-prepare.sh script - Add precheck:prepare as dependency to run task Signed-off-by: Roman Sysoev --- test/e2e/Taskfile.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) 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 \ From 82934a10550bb58b365cdec37316f88559ca3797 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Mon, 27 Apr 2026 02:15:41 +0300 Subject: [PATCH 05/11] test(e2e): integrate precheck system in SynchronizedBeforeSuite Add precheck validation and execution in SynchronizedBeforeSuite. The execution order is: 1. First initialize test resources (controller.NewBeforeProcess1Body, legacy.NewBeforeProcess1Body) - must run before prechecks 2. Validate all specs have precheck labels (fail fast if missing) 3. Load spec labels from JSON report (generated by precheck:prepare) 4. Run prechecks based on loaded labels before tests start This ensures cluster is properly configured before any test runs. Changes: - Import precheck package - Add precheck.ValidateFromJSONFile() call to validate labels - Add precheck.LoadSpecLabelsFromFile() to load labels from report - Add precheck.Run() to execute required prechecks Signed-off-by: Roman Sysoev --- test/e2e/e2e_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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() {}) From cb770081752277a2556ff1713489f0c76cb802da Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Mon, 27 Apr 2026 02:15:46 +0300 Subject: [PATCH 06/11] test(e2e): add GetModuleConfig method to framework Add GetModuleConfig method to retrieve Deckhouse module configurations. This is used by prechecks to verify modules are enabled and ready. Changes: - Add GetModuleConfig(moduleName string) (*Module, error) method to Framework - The method retrieves module configuration from the deckhouse API group - Used by prechecks to check if modules like sdn, svdm, virtualization, snapshot are enabled Signed-off-by: Roman Sysoev --- test/e2e/internal/framework/client.go | 106 ++++++++++++++------------ test/e2e/internal/framework/config.go | 11 ++- 2 files changed, 61 insertions(+), 56 deletions(-) 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() -} From 453860b10d9663feb6c05f1f82d67d0f68dcb4c7 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Mon, 27 Apr 2026 02:15:53 +0300 Subject: [PATCH 07/11] test(e2e): add precheck labels to tests Add required precheck labels to all e2e tests. Each test must declare its dependencies using Label(precheck.XXX) or Label(precheck.NoPrecheck). This ensures precheck system can validate cluster configuration before running tests that require specific modules or resources. Changes: - Add Label(precheck.NoPrecheck) to tests that don't require any prechecks - Add Label(precheck.PrecheckXXX) to tests that require specific prechecks: * precheck.PrecheckSDN - requires SDN module * precheck.PrecheckSVDM - requires SVDM module * precheck.PrecheckSnapshot - requires snapshot-controller * precheck.PrecheckUSB - requires dummy_hcd USB device Signed-off-by: Roman Sysoev --- test/e2e/blockdevice/data_exports.go | 6 ++++-- test/e2e/blockdevice/importer_network_policy.go | 7 +++++-- .../blockdevice/virtual_disk_provisioning.go | 6 ++++-- test/e2e/blockdevice/virtual_image_creation.go | 6 ++++-- test/e2e/legacy/complex.go | 7 +++++-- test/e2e/legacy/image_hotplug.go | 3 ++- test/e2e/legacy/legacy.go | 17 ++++++++--------- test/e2e/legacy/vd_snapshots.go | 3 ++- test/e2e/legacy/vm_disk_resizing.go | 3 ++- test/e2e/legacy/vm_evacuation.go | 12 +++++++++--- test/e2e/legacy/vm_label_annotation.go | 3 ++- test/e2e/legacy/vm_migration_cancel.go | 3 ++- test/e2e/snapshot/vmsop.go | 6 ++++-- test/e2e/vm/additional_network_interfaces.go | 6 ++++-- test/e2e/vm/affinity_toleration.go | 3 ++- test/e2e/vm/configuration.go | 3 ++- test/e2e/vm/connectivity.go | 3 ++- test/e2e/vm/hotplug_pod.go | 6 ++++-- test/e2e/vm/ipam.go | 6 ++++-- test/e2e/vm/live_migration_tcp_session.go | 11 ++++++++--- test/e2e/vm/migration.go | 6 ++++-- test/e2e/vm/power_state.go | 3 ++- test/e2e/vm/sizing_policy.go | 10 +++++++--- test/e2e/vm/target_migration.go | 6 ++++-- test/e2e/vm/tpm.go | 6 ++++-- test/e2e/vm/usb.go | 3 ++- test/e2e/vm/volume_migration_local_disks.go | 6 ++++-- .../volume_migration_storage_class_changed.go | 6 ++++-- test/e2e/vmop/restore.go | 3 ++- 29 files changed, 112 insertions(+), 57 deletions(-) diff --git a/test/e2e/blockdevice/data_exports.go b/test/e2e/blockdevice/data_exports.go index 80d1117644..b0cfa0bf01 100644 --- a/test/e2e/blockdevice/data_exports.go +++ b/test/e2e/blockdevice/data_exports.go @@ -43,6 +43,7 @@ import ( "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/precheck" "github.com/deckhouse/virtualization/test/e2e/internal/object" "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 { 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/legacy/complex.go b/test/e2e/legacy/complex.go index a377a2fd9c..5969bb36b0 100644 --- a/test/e2e/legacy/complex.go +++ b/test/e2e/legacy/complex.go @@ -29,6 +29,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 +38,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 +67,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() { 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/snapshot/vmsop.go b/test/e2e/snapshot/vmsop.go index b8ec0684cd..091ebd121a 100644 --- a/test/e2e/snapshot/vmsop.go +++ b/test/e2e/snapshot/vmsop.go @@ -34,11 +34,12 @@ import ( vmsopbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vmsop" "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/test/e2e/internal/framework" + "github.com/deckhouse/virtualization/test/e2e/internal/precheck" "github.com/deckhouse/virtualization/test/e2e/internal/object" "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..c19ed748b8 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() diff --git a/test/e2e/vm/affinity_toleration.go b/test/e2e/vm/affinity_toleration.go index bb42501e99..43d52567ed 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 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/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..e3796d6e3d 100644 --- a/test/e2e/vm/sizing_policy.go +++ b/test/e2e/vm/sizing_policy.go @@ -38,14 +38,18 @@ 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 + ) BeforeEach(func() { + f = framework.NewFramework("sizing-policy") f.Before() DeferCleanup(f.After) t = newSizingPolicyTest(f) diff --git a/test/e2e/vm/target_migration.go b/test/e2e/vm/target_migration.go index 6c884ee9e6..a8b8aaf706 100644 --- a/test/e2e/vm/target_migration.go +++ b/test/e2e/vm/target_migration.go @@ -36,12 +36,13 @@ import ( "github.com/deckhouse/virtualization/test/e2e/internal/framework" "github.com/deckhouse/virtualization/test/e2e/internal/object" "github.com/deckhouse/virtualization/test/e2e/internal/rewrite" + "github.com/deckhouse/virtualization/test/e2e/internal/precheck" "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/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) From e30babc16272d4ad1c8d7883a689feb91807eb77 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Mon, 27 Apr 2026 02:32:28 +0300 Subject: [PATCH 08/11] test(e2e): fix lint issues in precheck package Fix golangci-lint warnings: - errcheck: ignore return value of GinkgoWriter.Write() - SA4009: fix labelFilter parameter shadowing issue - ST1005: remove trailing punctuation from error messages - QF1012: use fmt.Fprintf instead of Write with fmt.Sprintf Changes: - Add _, _ = prefix to GinkgoWriter.Write() calls - Rename parameter to avoid shadowing in LoadSpecLabelsFromFile() - Fix error message in usb.go (remove trailing period) - Revert to fmt.Fprintf for GinkgoWriter (standard pattern in Ginkgo) Signed-off-by: Roman Sysoev --- test/e2e/blockdevice/data_exports.go | 2 +- test/e2e/internal/precheck/common.go | 24 ++++++++++++-------- test/e2e/internal/precheck/context.go | 2 +- test/e2e/internal/precheck/labels.go | 2 +- test/e2e/internal/precheck/sdn.go | 9 ++++---- test/e2e/internal/precheck/snapshot.go | 11 ++++----- test/e2e/internal/precheck/storageclass.go | 4 ++-- test/e2e/internal/precheck/svdm.go | 5 ++-- test/e2e/internal/precheck/usb.go | 6 ++--- test/e2e/internal/precheck/virtualization.go | 7 +++--- test/e2e/internal/precheck/vmc.go | 2 +- test/e2e/snapshot/vmsop.go | 2 +- test/e2e/vm/target_migration.go | 2 +- 13 files changed, 39 insertions(+), 39 deletions(-) diff --git a/test/e2e/blockdevice/data_exports.go b/test/e2e/blockdevice/data_exports.go index b0cfa0bf01..a126cbc292 100644 --- a/test/e2e/blockdevice/data_exports.go +++ b/test/e2e/blockdevice/data_exports.go @@ -43,8 +43,8 @@ import ( "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/precheck" "github.com/deckhouse/virtualization/test/e2e/internal/object" + "github.com/deckhouse/virtualization/test/e2e/internal/precheck" "github.com/deckhouse/virtualization/test/e2e/internal/util" ) diff --git a/test/e2e/internal/precheck/common.go b/test/e2e/internal/precheck/common.go index 3b0a00a163..99f0c0e65f 100644 --- a/test/e2e/internal/precheck/common.go +++ b/test/e2e/internal/precheck/common.go @@ -24,8 +24,9 @@ import ( "regexp" "strings" - "github.com/deckhouse/virtualization/test/e2e/internal/framework" . "github.com/onsi/ginkgo/v2" + + "github.com/deckhouse/virtualization/test/e2e/internal/framework" ) const ( @@ -49,10 +50,10 @@ type ginkgoReport struct { // specReport represents a spec in Ginkgo JSON report. type specReport struct { - ContainerHierarchyTexts []string `json:"ContainerHierarchyTexts"` + ContainerHierarchyTexts []string `json:"ContainerHierarchyTexts"` ContainerHierarchyLabels [][]string `json:"ContainerHierarchyLabels"` - LeafNodeText string `json:"LeafNodeText"` - LeafNodeType string `json:"LeafNodeType"` + LeafNodeText string `json:"LeafNodeText"` + LeafNodeType string `json:"LeafNodeType"` } // Precheck defines interface for precheck implementations. @@ -85,7 +86,7 @@ func RegisterPrecheck(p Precheck, isCommon bool) { // LoadSpecLabelsFromFile loads spec labels from file and filters by labelFilter. // Called from SynchronizedBeforeSuite. -func LoadSpecLabelsFromFile(filename string, labelFilter string) { +func LoadSpecLabelsFromFile(filename, labelFilter string) { data, err := os.ReadFile(filename) if err != nil { return @@ -134,8 +135,11 @@ func LoadSpecLabelsFromFile(filename string, labelFilter string) { // 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") - labelFilter = os.Getenv("LABELS") + if labelFilter == "" { + labelFilter = os.Getenv("LABELS") + } filteredSpecs := allSpecs if focusRegex != "" || labelFilter != "" { @@ -316,7 +320,7 @@ func validateSpecs(specs []specInfo) error { func Run(f *framework.Framework, labelFilter string) { // Run common prechecks first (always run) for _, p := range commonPrechecks { - GinkgoWriter.Write([]byte("Running common precheck: " + p.Label() + "\n")) + _, _ = GinkgoWriter.Write([]byte("Running common precheck: " + p.Label() + "\n")) if err := p.Run(NewContext(), f); err != nil { Fail("common precheck " + p.Label() + " failed: " + err.Error()) } @@ -328,7 +332,7 @@ func Run(f *framework.Framework, labelFilter string) { if p == nil { continue } - GinkgoWriter.Write([]byte("Running precheck: " + label + "\n")) + _, _ = GinkgoWriter.Write([]byte("Running precheck: " + label + "\n")) if err := p.Run(NewContext(), f); err != nil { Fail("precheck " + label + " failed: " + err.Error()) } @@ -348,9 +352,9 @@ func isCheckEnabled(envName string) bool { func IsModuleEnabled(f *framework.Framework, moduleName string) bool { module, err := f.GetModuleConfig(moduleName) if err != nil { - GinkgoWriter.Write([]byte(fmt.Sprintf("failed to get %s module config: %v\n", moduleName, err))) + _, _ = fmt.Fprintf(GinkgoWriter, "failed to get %s module config: %v\n", moduleName, err) return false } enabled := module.Spec.Enabled return enabled != nil && *enabled -} \ No newline at end of file +} diff --git a/test/e2e/internal/precheck/context.go b/test/e2e/internal/precheck/context.go index 58058b6513..31e22148d9 100644 --- a/test/e2e/internal/precheck/context.go +++ b/test/e2e/internal/precheck/context.go @@ -21,4 +21,4 @@ import "context" // NewContext creates a background context for precheck execution. func NewContext() context.Context { return context.Background() -} \ No newline at end of file +} diff --git a/test/e2e/internal/precheck/labels.go b/test/e2e/internal/precheck/labels.go index 0d02d472c1..69c13a7ca6 100644 --- a/test/e2e/internal/precheck/labels.go +++ b/test/e2e/internal/precheck/labels.go @@ -69,4 +69,4 @@ func IsPrecheckLabel(label string) bool { } } return false -} \ No newline at end of file +} diff --git a/test/e2e/internal/precheck/sdn.go b/test/e2e/internal/precheck/sdn.go index cc496d840d..513f2234a3 100644 --- a/test/e2e/internal/precheck/sdn.go +++ b/test/e2e/internal/precheck/sdn.go @@ -24,11 +24,10 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - - "github.com/deckhouse/virtualization/test/e2e/internal/framework" "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 ( @@ -76,7 +75,7 @@ func IsClusterNetworkExists(f *framework.Framework, vlanID int) bool { _, err := f.DynamicClient().Resource(gvr).Get(context.Background(), ClusterNetworkName(vlanID), metav1.GetOptions{}) if err != nil && !k8serrors.IsNotFound(err) { - GinkgoWriter.Write([]byte(fmt.Sprintf("error checking ClusterNetwork %s: %v\n", ClusterNetworkName(vlanID), err))) + _, _ = fmt.Fprintf(GinkgoWriter, "error checking ClusterNetwork %s: %v\n", ClusterNetworkName(vlanID), err) } return err == nil || !k8serrors.IsNotFound(err) @@ -91,7 +90,7 @@ func (s *sdnPrecheck) Label() string { func (s *sdnPrecheck) Run(ctx context.Context, f *framework.Framework) error { if !isCheckEnabled(sdnModuleCheckEnvName) { - GinkgoWriter.Write([]byte("SDN module check is disabled.\n")) + _, _ = GinkgoWriter.Write([]byte("SDN module check is disabled.\n")) return nil } @@ -122,4 +121,4 @@ func (s *sdnPrecheck) Run(ctx context.Context, f *framework.Framework) error { // Register SDN precheck (not common - requires explicit label). func init() { RegisterPrecheck(&sdnPrecheck{}, false) -} \ No newline at end of file +} diff --git a/test/e2e/internal/precheck/snapshot.go b/test/e2e/internal/precheck/snapshot.go index 37b3947cb0..ec8f4d7e63 100644 --- a/test/e2e/internal/precheck/snapshot.go +++ b/test/e2e/internal/precheck/snapshot.go @@ -23,11 +23,10 @@ import ( . "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" "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 ( @@ -44,7 +43,7 @@ func (s *snapshotPrecheck) Label() string { func (s *snapshotPrecheck) Run(ctx context.Context, f *framework.Framework) error { if !isCheckEnabled(snapshotModuleCheckEnvName) { - GinkgoWriter.Write([]byte("snapshot-controller module check is disabled.\n")) + _, _ = GinkgoWriter.Write([]byte("snapshot-controller module check is disabled.\n")) return nil } @@ -64,8 +63,8 @@ func (s *snapshotPrecheck) Run(ctx context.Context, f *framework.Framework) erro // Check that at least one VolumeSnapshotClass exists gvr := schema.GroupVersionResource{ - Group: "snapshot.storage.k8s.io", - Version: "v1", + Group: "snapshot.storage.k8s.io", + Version: "v1", Resource: "volumesnapshotclasses", } @@ -83,4 +82,4 @@ func (s *snapshotPrecheck) Run(ctx context.Context, f *framework.Framework) erro // Register Snapshot precheck (not common - requires explicit label). func init() { RegisterPrecheck(&snapshotPrecheck{}, false) -} \ No newline at end of file +} diff --git a/test/e2e/internal/precheck/storageclass.go b/test/e2e/internal/precheck/storageclass.go index 6c5a72a420..8ae9eb3ce2 100644 --- a/test/e2e/internal/precheck/storageclass.go +++ b/test/e2e/internal/precheck/storageclass.go @@ -41,7 +41,7 @@ func (c *storageClassPrecheck) Label() string { func (c *storageClassPrecheck) Run(ctx context.Context, f *framework.Framework) error { if !isCheckEnabled(storageClassPrecheckEnvName) { - GinkgoWriter.Write([]byte("StorageClass precheck is disabled.\n")) + _, _ = GinkgoWriter.Write([]byte("StorageClass precheck is disabled.\n")) return nil } @@ -81,4 +81,4 @@ func (c *storageClassPrecheck) Run(ctx context.Context, f *framework.Framework) // Register StorageClass precheck as common (runs for all tests). func init() { RegisterPrecheck(&storageClassPrecheck{}, true) -} \ No newline at end of file +} diff --git a/test/e2e/internal/precheck/svdm.go b/test/e2e/internal/precheck/svdm.go index 166627e7c5..ac31c8b914 100644 --- a/test/e2e/internal/precheck/svdm.go +++ b/test/e2e/internal/precheck/svdm.go @@ -24,7 +24,6 @@ import ( "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" ) @@ -43,7 +42,7 @@ func (s *svdmPrecheck) Label() string { 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")) + _, _ = GinkgoWriter.Write([]byte("Storage Volume Data Manager (SVDM) module check is disabled.\n")) return nil } @@ -66,4 +65,4 @@ func (s *svdmPrecheck) Run(ctx context.Context, f *framework.Framework) error { // Register SVDM precheck (not common - requires explicit label). func init() { RegisterPrecheck(&svdmPrecheck{}, false) -} \ No newline at end of file +} diff --git a/test/e2e/internal/precheck/usb.go b/test/e2e/internal/precheck/usb.go index 5eba0535cb..46006ecd46 100644 --- a/test/e2e/internal/precheck/usb.go +++ b/test/e2e/internal/precheck/usb.go @@ -50,7 +50,7 @@ func checkDummyHCDConfigured(f *framework.Framework) bool { nodeUSBList, err := virtClient.NodeUSBDevices().List(ctx, metav1.ListOptions{}) if err != nil { - GinkgoWriter.Write([]byte(fmt.Sprintf("failed to list NodeUSBDevices: %v\n", err))) + _, _ = fmt.Fprintf(GinkgoWriter, "failed to list NodeUSBDevices: %v\n", err) return false } @@ -65,13 +65,13 @@ func checkDummyHCDConfigured(f *framework.Framework) bool { func (u *usbPrecheck) Run(ctx context.Context, f *framework.Framework) error { if !isCheckEnabled(usbPrecheckEnvName) { - GinkgoWriter.Write([]byte("USB precheck is disabled.\n")) + _, _ = GinkgoWriter.Write([]byte("USB precheck is disabled.\n")) return nil } if !checkDummyHCDConfigured(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) + "Run generate_dummy_hcd_ngc.sh to configure dummy_hcd USB device", usbPrecheckEnvName) } return nil diff --git a/test/e2e/internal/precheck/virtualization.go b/test/e2e/internal/precheck/virtualization.go index 244e3bccb7..246ffc900c 100644 --- a/test/e2e/internal/precheck/virtualization.go +++ b/test/e2e/internal/precheck/virtualization.go @@ -21,11 +21,10 @@ import ( "fmt" . "github.com/onsi/ginkgo/v2" - - "github.com/deckhouse/virtualization/test/e2e/internal/framework" "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 ( @@ -42,7 +41,7 @@ func (v *virtualizationPrecheck) Label() string { func (v *virtualizationPrecheck) Run(ctx context.Context, f *framework.Framework) error { if !isCheckEnabled(virtualizationModuleCheckEnvName) { - GinkgoWriter.Write([]byte("virtualization module check is disabled.\n")) + _, _ = GinkgoWriter.Write([]byte("virtualization module check is disabled.\n")) return nil } @@ -66,4 +65,4 @@ func (v *virtualizationPrecheck) Run(ctx context.Context, f *framework.Framework // Register virtualization precheck as common (runs for all tests). func init() { RegisterPrecheck(&virtualizationPrecheck{}, true) -} \ No newline at end of file +} diff --git a/test/e2e/internal/precheck/vmc.go b/test/e2e/internal/precheck/vmc.go index 8afe2337a8..c48aa8401a 100644 --- a/test/e2e/internal/precheck/vmc.go +++ b/test/e2e/internal/precheck/vmc.go @@ -44,7 +44,7 @@ func (c *vmcPrecheck) Label() string { func (c *vmcPrecheck) Run(ctx context.Context, f *framework.Framework) error { if !isCheckEnabled(vmcModuleCheckEnvName) { - GinkgoWriter.Write([]byte("VMC precheck is disabled.\n")) + _, _ = GinkgoWriter.Write([]byte("VMC precheck is disabled.\n")) return nil } diff --git a/test/e2e/snapshot/vmsop.go b/test/e2e/snapshot/vmsop.go index 091ebd121a..90152dfcbf 100644 --- a/test/e2e/snapshot/vmsop.go +++ b/test/e2e/snapshot/vmsop.go @@ -34,8 +34,8 @@ import ( vmsopbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vmsop" "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/test/e2e/internal/framework" - "github.com/deckhouse/virtualization/test/e2e/internal/precheck" "github.com/deckhouse/virtualization/test/e2e/internal/object" + "github.com/deckhouse/virtualization/test/e2e/internal/precheck" "github.com/deckhouse/virtualization/test/e2e/internal/util" ) diff --git a/test/e2e/vm/target_migration.go b/test/e2e/vm/target_migration.go index a8b8aaf706..0ca4ef4511 100644 --- a/test/e2e/vm/target_migration.go +++ b/test/e2e/vm/target_migration.go @@ -35,8 +35,8 @@ 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/rewrite" "github.com/deckhouse/virtualization/test/e2e/internal/precheck" + "github.com/deckhouse/virtualization/test/e2e/internal/rewrite" "github.com/deckhouse/virtualization/test/e2e/internal/util" ) From 6bf1e2c8a0dc0e7934184a9682cb95dec2e3cab2 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Mon, 27 Apr 2026 11:40:12 +0300 Subject: [PATCH 09/11] test(e2e): add context parameter to precheck functions Fix contextcheck lint warnings by adding context.Context parameter to functions that use it internally. Changes: - Add ctx parameter to IsModuleEnabled(f, moduleName) -> IsModuleEnabled(ctx, f, moduleName) - Add ctx parameter to IsClusterNetworkExists(f, vlanID) -> IsClusterNetworkExists(ctx, f, vlanID) - Add ctx parameter to checkDummyHCDConfigured(f) -> checkDummyHCDConfigured(ctx, f) - Add ctx parameter to GetModuleConfig(name) -> GetModuleConfig(ctx, name) - Add ctx parameter to GetVirtualizationModuleConfig() -> GetVirtualizationModuleConfig(ctx) - Add ctx parameter to UntilConditionReason, UntilConditionStatus, UntilConditionState - Update all call sites to pass context Signed-off-by: Roman Sysoev --- test/e2e/blockdevice/data_exports.go | 2 +- test/e2e/internal/framework/mc.go | 8 +- test/e2e/internal/precheck/common.go | 4 +- test/e2e/internal/precheck/sdn.go | 8 +- test/e2e/internal/precheck/snapshot.go | 2 +- test/e2e/internal/precheck/svdm.go | 2 +- test/e2e/internal/precheck/usb.go | 5 +- test/e2e/internal/precheck/virtualization.go | 2 +- test/e2e/internal/util/sdn.go | 2 +- test/e2e/internal/util/until.go | 11 +-- test/e2e/legacy/complex.go | 3 +- test/e2e/vm/additional_network_interfaces.go | 34 +++++++- test/e2e/vm/affinity_toleration.go | 81 +++++++++++++++++--- test/e2e/vm/sizing_policy.go | 35 ++++++--- 14 files changed, 149 insertions(+), 50 deletions(-) diff --git a/test/e2e/blockdevice/data_exports.go b/test/e2e/blockdevice/data_exports.go index a126cbc292..acf58fc293 100644 --- a/test/e2e/blockdevice/data_exports.go +++ b/test/e2e/blockdevice/data_exports.go @@ -367,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/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 index 99f0c0e65f..25a0bf7d27 100644 --- a/test/e2e/internal/precheck/common.go +++ b/test/e2e/internal/precheck/common.go @@ -349,8 +349,8 @@ func isCheckEnabled(envName string) bool { // IsModuleEnabled checks if a Deckhouse module is enabled. // Returns true if the module exists and is enabled (Spec.Enabled = true). -func IsModuleEnabled(f *framework.Framework, moduleName string) bool { - module, err := f.GetModuleConfig(moduleName) +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 diff --git a/test/e2e/internal/precheck/sdn.go b/test/e2e/internal/precheck/sdn.go index 513f2234a3..66b61c5ea6 100644 --- a/test/e2e/internal/precheck/sdn.go +++ b/test/e2e/internal/precheck/sdn.go @@ -64,7 +64,7 @@ EOF`, ClusterNetworkName(vlanID), vlanID) } // IsClusterNetworkExists checks if ClusterNetwork with given VLAN ID exists. -func IsClusterNetworkExists(f *framework.Framework, vlanID int) bool { +func IsClusterNetworkExists(ctx context.Context, f *framework.Framework, vlanID int) bool { GinkgoHelper() gvr := schema.GroupVersionResource{ @@ -73,7 +73,7 @@ func IsClusterNetworkExists(f *framework.Framework, vlanID int) bool { Resource: "clusternetworks", } - _, err := f.DynamicClient().Resource(gvr).Get(context.Background(), ClusterNetworkName(vlanID), metav1.GetOptions{}) + _, err := f.DynamicClient().Resource(gvr).Get(ctx, ClusterNetworkName(vlanID), metav1.GetOptions{}) if err != nil && !k8serrors.IsNotFound(err) { _, _ = fmt.Fprintf(GinkgoWriter, "error checking ClusterNetwork %s: %v\n", ClusterNetworkName(vlanID), err) } @@ -94,7 +94,7 @@ func (s *sdnPrecheck) Run(ctx context.Context, f *framework.Framework) error { return nil } - if !IsModuleEnabled(f, sdnModuleName) { + if !IsModuleEnabled(ctx, f, sdnModuleName) { return fmt.Errorf("%s=no to disable this precheck: SDN module should be enabled", sdnModuleCheckEnvName) } @@ -109,7 +109,7 @@ func (s *sdnPrecheck) Run(ctx context.Context, f *framework.Framework) error { // Check required ClusterNetworks for e2e tests for _, vlanID := range []int{additionalInterfaceVLANID, secondAdditionalInterfaceVLANID} { - if !IsClusterNetworkExists(f, vlanID) { + if !IsClusterNetworkExists(ctx, f, vlanID) { return fmt.Errorf("%s=no to disable this precheck: ClusterNetwork %q does not exist. Create it first: %s", sdnModuleCheckEnvName, ClusterNetworkName(vlanID), ClusterNetworkCreateCommand(vlanID)) } diff --git a/test/e2e/internal/precheck/snapshot.go b/test/e2e/internal/precheck/snapshot.go index ec8f4d7e63..b5de73ceef 100644 --- a/test/e2e/internal/precheck/snapshot.go +++ b/test/e2e/internal/precheck/snapshot.go @@ -47,7 +47,7 @@ func (s *snapshotPrecheck) Run(ctx context.Context, f *framework.Framework) erro return nil } - if !IsModuleEnabled(f, snapshotModuleName) { + if !IsModuleEnabled(ctx, f, snapshotModuleName) { return fmt.Errorf("%s=no to disable this precheck: snapshot-controller module should be enabled", snapshotModuleCheckEnvName) } diff --git a/test/e2e/internal/precheck/svdm.go b/test/e2e/internal/precheck/svdm.go index ac31c8b914..47f8fe76e0 100644 --- a/test/e2e/internal/precheck/svdm.go +++ b/test/e2e/internal/precheck/svdm.go @@ -46,7 +46,7 @@ func (s *svdmPrecheck) Run(ctx context.Context, f *framework.Framework) error { return nil } - if !IsModuleEnabled(f, svdmModuleName) { + if !IsModuleEnabled(ctx, f, svdmModuleName) { return fmt.Errorf("%s=no to disable this precheck: Storage Volume Data Manager module should be enabled", svdmModuleCheckEnvName) } diff --git a/test/e2e/internal/precheck/usb.go b/test/e2e/internal/precheck/usb.go index 46006ecd46..520622a632 100644 --- a/test/e2e/internal/precheck/usb.go +++ b/test/e2e/internal/precheck/usb.go @@ -44,8 +44,7 @@ func (u *usbPrecheck) Label() string { // checkDummyHCDConfigured checks if dummy_hcd USB device is configured. // dummy_hcd is a virtual USB device used for testing USB passthrough. -func checkDummyHCDConfigured(f *framework.Framework) bool { - ctx := context.Background() +func checkDummyHCDConfigured(ctx context.Context, f *framework.Framework) bool { virtClient := f.VirtClient() nodeUSBList, err := virtClient.NodeUSBDevices().List(ctx, metav1.ListOptions{}) @@ -69,7 +68,7 @@ func (u *usbPrecheck) Run(ctx context.Context, f *framework.Framework) error { return nil } - if !checkDummyHCDConfigured(f) { + 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) } diff --git a/test/e2e/internal/precheck/virtualization.go b/test/e2e/internal/precheck/virtualization.go index 246ffc900c..92a08a3a23 100644 --- a/test/e2e/internal/precheck/virtualization.go +++ b/test/e2e/internal/precheck/virtualization.go @@ -45,7 +45,7 @@ func (v *virtualizationPrecheck) Run(ctx context.Context, f *framework.Framework return nil } - if !IsModuleEnabled(f, virtualizationModuleName) { + if !IsModuleEnabled(ctx, f, virtualizationModuleName) { return fmt.Errorf("%s=no to disable this precheck: virtualization module should be enabled", virtualizationModuleCheckEnvName) } 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 5969bb36b0..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" @@ -243,7 +244,7 @@ var _ = Describe("ComplexTest", Ordered, label.Legacy(), Label(precheck.NoPreche }) 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/vm/additional_network_interfaces.go b/test/e2e/vm/additional_network_interfaces.go index c19ed748b8..4569baead2 100644 --- a/test/e2e/vm/additional_network_interfaces.go +++ b/test/e2e/vm/additional_network_interfaces.go @@ -113,7 +113,14 @@ var _ = Describe("VirtualMachineAdditionalNetworkInterfaces", Label(precheck.NoP // 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() { @@ -149,7 +156,14 @@ var _ = Describe("VirtualMachineAdditionalNetworkInterfaces", Label(precheck.NoP }) 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() { @@ -191,7 +205,13 @@ var _ = Describe("VirtualMachineAdditionalNetworkInterfaces", Label(precheck.NoP 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() { @@ -222,7 +242,13 @@ var _ = Describe("VirtualMachineAdditionalNetworkInterfaces", Label(precheck.NoP 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 43d52567ed..265b15943f 100644 --- a/test/e2e/vm/affinity_toleration.go +++ b/test/e2e/vm/affinity_toleration.go @@ -159,7 +159,13 @@ var _ = Describe("VirtualMachineAffinityAndToleration", Ordered, Label(precheck. }) 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 @@ -169,8 +175,16 @@ var _ = Describe("VirtualMachineAffinityAndToleration", Ordered, Label(precheck. 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 @@ -181,7 +195,7 @@ var _ = Describe("VirtualMachineAffinityAndToleration", Ordered, Label(precheck. }) 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() @@ -190,8 +204,16 @@ var _ = Describe("VirtualMachineAffinityAndToleration", Ordered, Label(precheck. 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() { @@ -222,7 +244,13 @@ var _ = Describe("VirtualMachineAffinityAndToleration", Ordered, Label(precheck. 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 @@ -250,8 +278,16 @@ var _ = Describe("VirtualMachineAffinityAndToleration", Ordered, Label(precheck. 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() { @@ -285,7 +321,13 @@ var _ = Describe("VirtualMachineAffinityAndToleration", Ordered, Label(precheck. 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 @@ -313,8 +355,16 @@ var _ = Describe("VirtualMachineAffinityAndToleration", Ordered, Label(precheck. 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() { @@ -451,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/sizing_policy.go b/test/e2e/vm/sizing_policy.go index e3796d6e3d..6b3c24dd92 100644 --- a/test/e2e/vm/sizing_policy.go +++ b/test/e2e/vm/sizing_policy.go @@ -44,8 +44,9 @@ import ( var _ = Describe("SizingPolicy", Label(precheck.NoPrecheck), func() { var ( - t *sizingPolicyTest - f *framework.Framework + t *sizingPolicyTest + f *framework.Framework + ctx context.Context ) BeforeEach(func() { @@ -53,6 +54,8 @@ var _ = Describe("SizingPolicy", Label(precheck.NoPrecheck), func() { f.Before() DeferCleanup(f.After) t = newSizingPolicyTest(f) + + ctx = context.Background() }) It("should start VM normally with existing VMClass", func() { @@ -60,10 +63,10 @@ var _ = Describe("SizingPolicy", Label(precheck.NoPrecheck), 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") @@ -78,14 +81,20 @@ var _ = Describe("SizingPolicy", Label(precheck.NoPrecheck), 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") @@ -101,11 +110,17 @@ var _ = Describe("SizingPolicy", Label(precheck.NoPrecheck), 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{}{{ @@ -114,7 +129,7 @@ var _ = Describe("SizingPolicy", Label(precheck.NoPrecheck), 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") From cfb37e408140a1ceea9e28b43ea120a3ea43d013 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Wed, 29 Apr 2026 19:41:35 +0300 Subject: [PATCH 10/11] test: use context package directly Signed-off-by: Roman Sysoev --- test/e2e/internal/precheck/common.go | 5 +++-- test/e2e/internal/precheck/context.go | 24 ------------------------ 2 files changed, 3 insertions(+), 26 deletions(-) delete mode 100644 test/e2e/internal/precheck/context.go diff --git a/test/e2e/internal/precheck/common.go b/test/e2e/internal/precheck/common.go index 25a0bf7d27..606dff9b19 100644 --- a/test/e2e/internal/precheck/common.go +++ b/test/e2e/internal/precheck/common.go @@ -318,10 +318,11 @@ func validateSpecs(specs []specInfo) error { // 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(NewContext(), f); err != nil { + if err := p.Run(ctx, f); err != nil { Fail("common precheck " + p.Label() + " failed: " + err.Error()) } } @@ -333,7 +334,7 @@ func Run(f *framework.Framework, labelFilter string) { continue } _, _ = GinkgoWriter.Write([]byte("Running precheck: " + label + "\n")) - if err := p.Run(NewContext(), f); err != nil { + if err := p.Run(ctx, f); err != nil { Fail("precheck " + label + " failed: " + err.Error()) } } diff --git a/test/e2e/internal/precheck/context.go b/test/e2e/internal/precheck/context.go deleted file mode 100644 index 31e22148d9..0000000000 --- a/test/e2e/internal/precheck/context.go +++ /dev/null @@ -1,24 +0,0 @@ -/* -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" - -// NewContext creates a background context for precheck execution. -func NewContext() context.Context { - return context.Background() -} From 6fb39689ead8873779de8735a7aadfc2d9e5f75b Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Wed, 29 Apr 2026 20:06:49 +0300 Subject: [PATCH 11/11] test: fix merge conflicts Signed-off-by: Roman Sysoev --- test/e2e/internal/config/config.go | 1 - test/e2e/vm/disk_attachment.go | 3 ++- test/e2e/vm/hotplug_cpu.go | 3 ++- test/e2e/vm/hotplug_memory.go | 3 ++- test/e2e/vm/version.go | 3 ++- 5 files changed, 8 insertions(+), 5 deletions(-) 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/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/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() {