diff --git a/images/virtualization-artifact/pkg/controller/kvbuilder/kvvm.go b/images/virtualization-artifact/pkg/controller/kvbuilder/kvvm.go index 68cb1793a6..dca2a49b7f 100644 --- a/images/virtualization-artifact/pkg/controller/kvbuilder/kvvm.go +++ b/images/virtualization-artifact/pkg/controller/kvbuilder/kvvm.go @@ -50,6 +50,7 @@ const ( // GenericCPUModel specifies the base CPU model for Features and Discovery CPU model types. GenericCPUModel = "qemu64" + HTCPUFeature = "ht" MaxMemorySizeForHotplug = 256 * 1024 * 1024 * 1024 // 256 Gi (safely limit to not overlap somewhat conservative 38 bit physical address space) EnableMemoryHotplugThreshold = 1 * 1024 * 1024 * 1024 // 1 Gi (no hotplug for VMs with less than 1Gi) @@ -162,12 +163,13 @@ func (b *KVVM) SetCPUModel(class *v1alpha2.VirtualMachineClass) error { cpu.Model = virtv1.CPUModeHostPassthrough case v1alpha2.CPUTypeModel: cpu.Model = class.Spec.CPU.Model + cpu.Features = []virtv1.CPUFeature{{Name: HTCPUFeature, Policy: "optional"}} case v1alpha2.CPUTypeDiscovery, v1alpha2.CPUTypeFeatures: cpu.Model = GenericCPUModel - l := len(class.Status.CpuFeatures.Enabled) - features := make([]virtv1.CPUFeature, l, l+1) + features := make([]virtv1.CPUFeature, 0, len(class.Status.CpuFeatures.Enabled)+2) hasSvm := false - for i, feature := range class.Status.CpuFeatures.Enabled { + hasHT := false + for _, feature := range class.Status.CpuFeatures.Enabled { policy := "require" if feature == "invtsc" { policy = "optional" @@ -175,14 +177,20 @@ func (b *KVVM) SetCPUModel(class *v1alpha2.VirtualMachineClass) error { if feature == "svm" { hasSvm = true } - features[i] = virtv1.CPUFeature{ + if feature == HTCPUFeature { + hasHT = true + } + features = append(features, virtv1.CPUFeature{ Name: feature, Policy: policy, - } + }) } if !hasSvm { features = append(features, virtv1.CPUFeature{Name: "svm", Policy: "optional"}) } + if !hasHT { + features = append(features, virtv1.CPUFeature{Name: HTCPUFeature, Policy: "optional"}) + } cpu.Features = features default: return fmt.Errorf("unexpected cpu type: %q", class.Spec.CPU.Type) diff --git a/images/virtualization-artifact/pkg/controller/kvbuilder/kvvm_test.go b/images/virtualization-artifact/pkg/controller/kvbuilder/kvvm_test.go index 8e18bf2031..17ede1cc9d 100644 --- a/images/virtualization-artifact/pkg/controller/kvbuilder/kvvm_test.go +++ b/images/virtualization-artifact/pkg/controller/kvbuilder/kvvm_test.go @@ -22,6 +22,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + virtv1 "kubevirt.io/api/core/v1" "github.com/deckhouse/virtualization/api/core/v1alpha2" ) @@ -224,6 +225,89 @@ func TestApplyPVNodeAffinity(t *testing.T) { }) } +func TestSetCPUModel(t *testing.T) { + name := "test-name" + namespace := "test-namespace" + + t.Run("should add required ht feature for model cpu", func(t *testing.T) { + builder := NewEmptyKVVM(types.NamespacedName{Name: name, Namespace: namespace}, KVVMOptions{}) + class := &v1alpha2.VirtualMachineClass{ + Spec: v1alpha2.VirtualMachineClassSpec{ + CPU: v1alpha2.CPU{Type: v1alpha2.CPUTypeModel, Model: "Nehalem"}, + }, + } + + if err := builder.SetCPUModel(class); err != nil { + t.Fatalf("SetCPUModel() failed: %v", err) + } + + features := builder.Resource.Spec.Template.Spec.Domain.CPU.Features + if !containsCPUFeature(features, virtv1.CPUFeature{Name: HTCPUFeature, Policy: "optional"}) { + t.Fatalf("expected optional ht feature to be added for model cpu, got %#v", features) + } + }) + + t.Run("should add required ht feature for discovery cpu", func(t *testing.T) { + builder := NewEmptyKVVM(types.NamespacedName{Name: name, Namespace: namespace}, KVVMOptions{}) + class := &v1alpha2.VirtualMachineClass{ + Spec: v1alpha2.VirtualMachineClassSpec{ + CPU: v1alpha2.CPU{Type: v1alpha2.CPUTypeDiscovery}, + }, + Status: v1alpha2.VirtualMachineClassStatus{ + CpuFeatures: v1alpha2.CpuFeatures{Enabled: []string{"aes"}}, + }, + } + + if err := builder.SetCPUModel(class); err != nil { + t.Fatalf("SetCPUModel() failed: %v", err) + } + + features := builder.Resource.Spec.Template.Spec.Domain.CPU.Features + if !containsCPUFeature(features, virtv1.CPUFeature{Name: HTCPUFeature, Policy: "require"}) { + t.Fatalf("expected required ht feature to be added, got %#v", features) + } + }) + + t.Run("should keep existing ht feature policy when already present", func(t *testing.T) { + builder := NewEmptyKVVM(types.NamespacedName{Name: name, Namespace: namespace}, KVVMOptions{}) + class := &v1alpha2.VirtualMachineClass{ + Spec: v1alpha2.VirtualMachineClassSpec{ + CPU: v1alpha2.CPU{Type: v1alpha2.CPUTypeFeatures}, + }, + Status: v1alpha2.VirtualMachineClassStatus{ + CpuFeatures: v1alpha2.CpuFeatures{Enabled: []string{"vmx", HTCPUFeature}}, + }, + } + + if err := builder.SetCPUModel(class); err != nil { + t.Fatalf("SetCPUModel() failed: %v", err) + } + + features := builder.Resource.Spec.Template.Spec.Domain.CPU.Features + htCount := 0 + for _, feature := range features { + if feature.Name == HTCPUFeature { + htCount++ + if feature.Policy != "require" { + t.Fatalf("expected existing ht policy to stay require, got %#v", feature) + } + } + } + if htCount != 1 { + t.Fatalf("expected exactly one ht feature, got %#v", features) + } + }) +} + +func containsCPUFeature(features []virtv1.CPUFeature, expected virtv1.CPUFeature) bool { + for _, feature := range features { + if feature == expected { + return true + } + } + return false +} + func TestSetOsType(t *testing.T) { name := "test-name" namespace := "test-namespace"