diff --git a/neonvm/apis/neonvm/v1/virtualmachine_types.go b/neonvm/apis/neonvm/v1/virtualmachine_types.go index 004b14867..b51b4de09 100644 --- a/neonvm/apis/neonvm/v1/virtualmachine_types.go +++ b/neonvm/apis/neonvm/v1/virtualmachine_types.go @@ -52,6 +52,14 @@ const ( // // The value of this annotation is always a JSON-encoded VirtualMachineResources object. VirtualMachineResourcesAnnotation string = "vm.neon.tech/resources" + + // CpuScalingModeQMP is the value of the VirtualMachineSpec.CpuScalingMode field that indicates + // that the VM should use QMP to scale CPUs. + CpuScalingModeQMP string = "qmpScaling" + + // CpuScalingModeCpuSysfsState is the value of the VirtualMachineSpec.CpuScalingMode field that + // indicates that the VM should use the CPU sysfs state interface to scale CPUs. + CpuScalingModeCpuSysfsState string = "cpuSysfsStateScaling" ) // VirtualMachineUsage provides information about a VM's current usage. This is the type of the @@ -150,6 +158,11 @@ type VirtualMachineSpec struct { // propagated to the VM. // +optional TargetRevision *RevisionWithTime `json:"targetRevision,omitempty"` + + // Use QMP scaling mode for CPU or online/offline CPU states. + // +kubebuilder:validation:Enum=qmpScaling;cpuSysfsStateScaling + // +optional + CpuScalingMode *string `json:"cpuScalingMode,omitempty"` } func (spec *VirtualMachineSpec) Resources() VirtualMachineResources { @@ -605,6 +618,7 @@ func (p VmPhase) IsAlive() bool { // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // +kubebuilder:printcolumn:name="Node",type=string,priority=1,JSONPath=`.status.node` // +kubebuilder:printcolumn:name="Image",type=string,priority=1,JSONPath=`.spec.guest.rootDisk.image` +// +kubebuilder:printcolumn:name="CPUScalingMode",type=string,priority=1,JSONPath=`.spec.cpuScalingMode` type VirtualMachine struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/neonvm/apis/neonvm/v1/zz_generated.deepcopy.go b/neonvm/apis/neonvm/v1/zz_generated.deepcopy.go index b79097248..7fea8669d 100644 --- a/neonvm/apis/neonvm/v1/zz_generated.deepcopy.go +++ b/neonvm/apis/neonvm/v1/zz_generated.deepcopy.go @@ -733,6 +733,11 @@ func (in *VirtualMachineSpec) DeepCopyInto(out *VirtualMachineSpec) { *out = new(RevisionWithTime) (*in).DeepCopyInto(*out) } + if in.CpuScalingMode != nil { + in, out := &in.CpuScalingMode, &out.CpuScalingMode + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachineSpec. diff --git a/neonvm/config/crd/bases/vm.neon.tech_virtualmachines.yaml b/neonvm/config/crd/bases/vm.neon.tech_virtualmachines.yaml index 39e9b06e1..38653bb69 100644 --- a/neonvm/config/crd/bases/vm.neon.tech_virtualmachines.yaml +++ b/neonvm/config/crd/bases/vm.neon.tech_virtualmachines.yaml @@ -44,6 +44,10 @@ spec: name: Image priority: 1 type: string + - jsonPath: .spec.cpuScalingMode + name: CPUScalingMode + priority: 1 + type: string name: v1 schema: openAPIV3Schema: @@ -831,6 +835,12 @@ spec: type: array type: object type: object + cpuScalingMode: + description: Use QMP scaling mode for CPU or online/offline CPU states. + enum: + - qmpScaling + - cpuSysfsStateScaling + type: string disks: description: List of disk that can be mounted by virtual machine. items: diff --git a/pkg/neonvm/controllers/vm_controller.go b/pkg/neonvm/controllers/vm_controller.go index ce36df021..70cbe8131 100644 --- a/pkg/neonvm/controllers/vm_controller.go +++ b/pkg/neonvm/controllers/vm_controller.go @@ -165,6 +165,16 @@ func (r *VMReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Re return ctrl.Result{}, nil } + // examine cpuScalingMode and set it to the default value if it is not set + if vm.Spec.CpuScalingMode == nil || *vm.Spec.CpuScalingMode == "" { + vm.Spec.CpuScalingMode = lo.ToPtr(vmv1.CpuScalingModeQMP) + if err := r.tryUpdateVM(ctx, &vm); err != nil { + log.Error(err, "Failed to set default CPU scaling mode") + return ctrl.Result{}, err + } + return ctrl.Result{Requeue: true}, nil + } + statusBefore := vm.Status.DeepCopy() if err := r.doReconcile(ctx, &vm); err != nil { r.Recorder.Eventf(&vm, corev1.EventTypeWarning, "Failed", diff --git a/pkg/neonvm/controllers/vm_controller_unit_test.go b/pkg/neonvm/controllers/vm_controller_unit_test.go index 139252924..434a6e6b1 100644 --- a/pkg/neonvm/controllers/vm_controller_unit_test.go +++ b/pkg/neonvm/controllers/vm_controller_unit_test.go @@ -166,6 +166,11 @@ func TestReconcile(t *testing.T) { }, res) assert.Contains(t, params.getVM().Finalizers, virtualmachineFinalizer) + // Added default value for the cpuScalingMode + res, err = params.r.Reconcile(params.ctx, req) + assert.NoError(t, err) + assert.Equal(t, true, res.Requeue) + // Round 2 res, err = params.r.Reconcile(params.ctx, req) assert.NoError(t, err) @@ -184,8 +189,11 @@ func TestReconcile(t *testing.T) { // We now have a pod vm := params.getVM() assert.NotEmpty(t, vm.Status.PodName) - // Spec is unchanged - assert.Equal(t, vm.Spec, origVM.Spec) + // Spec is unchanged except cpuScalingMode + var origVMWithCPUScalingMode vmv1.VirtualMachine + origVM.DeepCopy().DeepCopyInto(&origVMWithCPUScalingMode) + origVMWithCPUScalingMode.Spec.CpuScalingMode = lo.ToPtr(vmv1.CpuScalingModeQMP) + assert.Equal(t, vm.Spec, origVMWithCPUScalingMode.Spec) // Round 4 res, err = params.r.Reconcile(params.ctx, req) @@ -216,8 +224,13 @@ func TestRunningPod(t *testing.T) { // Round 1 params.mockRecorder.On("Event", mock.Anything, "Normal", "Created", mock.Anything) + // Requeue because we expect default values to be set res, err := params.r.Reconcile(params.ctx, req) require.NoError(t, err) + assert.Equal(t, true, res.Requeue) + + res, err = params.r.Reconcile(params.ctx, req) + require.NoError(t, err) assert.Equal(t, false, res.Requeue) // We now have a pod diff --git a/tests/e2e/tmp-default-cpu-scaling-mode/00-assert.yaml b/tests/e2e/tmp-default-cpu-scaling-mode/00-assert.yaml new file mode 100644 index 000000000..98bf6f0e1 --- /dev/null +++ b/tests/e2e/tmp-default-cpu-scaling-mode/00-assert.yaml @@ -0,0 +1,13 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 90 +--- +apiVersion: vm.neon.tech/v1 +kind: VirtualMachine +metadata: + name: example +# temporary added this check to validate that defaulting cpuScalingMode works as expected +# field spec.cpuScalingMode should go to default value `qmp_scaling` if it is not set +# TODO: delete once the https://github.com/neondatabase/autoscaling/issues/1082 went live +spec: + cpuScalingMode: qmpScaling diff --git a/tests/e2e/tmp-default-cpu-scaling-mode/00-create-vm.yaml b/tests/e2e/tmp-default-cpu-scaling-mode/00-create-vm.yaml new file mode 100644 index 000000000..335741642 --- /dev/null +++ b/tests/e2e/tmp-default-cpu-scaling-mode/00-create-vm.yaml @@ -0,0 +1,23 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +unitTest: false +--- +apiVersion: vm.neon.tech/v1 +kind: VirtualMachine +metadata: + name: example +spec: + schedulerName: autoscale-scheduler + guest: + cpus: + min: 0.25 + use: 0.25 + max: 0.25 + memorySlotSize: 1Gi + memorySlots: + min: 1 + use: 1 + max: 1 + rootDisk: + image: vm-postgres:15-bullseye + size: 1Gi