From 537ea4cce55c3633bc4f36a6147adbaacd2d80d6 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Fri, 5 Dec 2025 05:24:36 +0300 Subject: [PATCH 1/3] chore(api): extend core fraction validation error msg Signed-off-by: Roman Sysoev --- .../controller/service/size_policy_service.go | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/service/size_policy_service.go b/images/virtualization-artifact/pkg/controller/service/size_policy_service.go index e5857917d1..7cdc478482 100644 --- a/images/virtualization-artifact/pkg/controller/service/size_policy_service.go +++ b/images/virtualization-artifact/pkg/controller/service/size_policy_service.go @@ -45,14 +45,14 @@ func (s *SizePolicyService) CheckVMMatchedSizePolicy(vm *v1alpha2.VirtualMachine return NewNoSizingPolicyMatchError(vm.Name, vm.Spec.VirtualMachineClassName) } - var errorsArray []error + var errs []error - errorsArray = append(errorsArray, validateCoreFraction(vm, sizePolicy)...) - errorsArray = append(errorsArray, validateMemory(vm, sizePolicy)...) - errorsArray = append(errorsArray, validatePerCoreMemory(vm, sizePolicy)...) + errs = append(errs, validateCoreFraction(vm, sizePolicy)...) + errs = append(errs, validateMemory(vm, sizePolicy)...) + errs = append(errs, validatePerCoreMemory(vm, sizePolicy)...) - if len(errorsArray) > 0 { - return fmt.Errorf("sizing policy validate: %w", errors.Join(errorsArray...)) + if len(errs) > 0 { + return fmt.Errorf("sizing policy validate: %w", errors.Join(errs...)) } return nil @@ -72,7 +72,7 @@ func getVMSizePolicy(vm *v1alpha2.VirtualMachine, vmClass *v1alpha2.VirtualMachi return nil } -func validateCoreFraction(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolicy) (errorsArray []error) { +func validateCoreFraction(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolicy) (errs []error) { if len(sp.CoreFractions) == 0 { return } @@ -80,7 +80,7 @@ func validateCoreFraction(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolicy fractionStr := strings.ReplaceAll(vm.Spec.CPU.CoreFraction, "%", "") fraction, err := strconv.Atoi(fractionStr) if err != nil { - errorsArray = append(errorsArray, fmt.Errorf("unable to parse CPU core fraction: %w", err)) + errs = append(errs, fmt.Errorf("unable to parse CPU core fraction: %w", err)) return } @@ -92,19 +92,19 @@ func validateCoreFraction(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolicy } if !hasFractionValueInPolicy { - errorsArray = append(errorsArray, fmt.Errorf("VM core fraction value %d is not within the allowed values", fraction)) + errs = append(errs, fmt.Errorf("VM core fraction value %d is not within the allowed values: %v", fraction, sp.CoreFractions)) } return } -func validateMemory(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolicy) (errorsArray []error) { +func validateMemory(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolicy) (errs []error) { if sp.Memory == nil || sp.Memory.Max == nil || sp.Memory.Max.IsZero() { return } if sp.Memory.Min != nil && vm.Spec.Memory.Size.Cmp(*sp.Memory.Min) == common.CmpLesser { - errorsArray = append(errorsArray, fmt.Errorf( + errs = append(errs, fmt.Errorf( "requested VM memory (%s) is less than the minimum allowed, available range [%s, %s]", vm.Spec.Memory.Size.String(), sp.Memory.Min.String(), @@ -113,7 +113,7 @@ func validateMemory(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolicy) (err } if vm.Spec.Memory.Size.Cmp(*sp.Memory.Max) == common.CmpGreater { - errorsArray = append(errorsArray, fmt.Errorf( + errs = append(errs, fmt.Errorf( "requested VM memory (%s) exceeds the maximum allowed, available range [%s, %s]", vm.Spec.Memory.Size.String(), sp.Memory.Min.String(), @@ -128,14 +128,14 @@ func validateMemory(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolicy) (err } err := validateIsQuantized(vm.Spec.Memory.Size, minVal, *sp.Memory.Max, *sp.Memory.Step, "VM memory") if err != nil { - errorsArray = append(errorsArray, err) + errs = append(errs, err) } } return } -func validatePerCoreMemory(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolicy) (errorsArray []error) { +func validatePerCoreMemory(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolicy) (errs []error) { if sp.Memory == nil || sp.Memory.PerCore == nil || sp.Memory.PerCore.Max == nil || sp.Memory.PerCore.Max.IsZero() { return } @@ -147,7 +147,7 @@ func validatePerCoreMemory(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolic perCoreMemory := resource.NewQuantity(vmPerCore, resource.BinarySI) if sp.Memory.PerCore.Min != nil && perCoreMemory.Cmp(*sp.Memory.PerCore.Min) == common.CmpLesser { - errorsArray = append(errorsArray, fmt.Errorf( + errs = append(errs, fmt.Errorf( "requested VM per core memory (%s) is less than the minimum allowed, available range [%s, %s]", perCoreMemory.String(), sp.Memory.PerCore.Min.String(), @@ -156,7 +156,7 @@ func validatePerCoreMemory(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolic } if perCoreMemory.Cmp(*sp.Memory.PerCore.Max) == common.CmpGreater { - errorsArray = append(errorsArray, fmt.Errorf( + errs = append(errs, fmt.Errorf( "requested VM per core memory (%s) exceeds the maximum allowed, available range [%s, %s]", perCoreMemory.String(), sp.Memory.PerCore.Min.String(), @@ -171,7 +171,7 @@ func validatePerCoreMemory(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolic } err := validateIsQuantized(*perCoreMemory, minVal, *sp.Memory.PerCore.Max, *sp.Memory.Step, "VM per core memory") if err != nil { - errorsArray = append(errorsArray, err) + errs = append(errs, err) } } From eff3973696844983d2d4632cb105e75cb9ab10c1 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Tue, 9 Dec 2025 18:18:19 +0300 Subject: [PATCH 2/3] chore(api): format core fraction values in err msg Signed-off-by: Roman Sysoev --- .../controller/service/size_policy_service.go | 74 ++++++++++++------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/service/size_policy_service.go b/images/virtualization-artifact/pkg/controller/service/size_policy_service.go index 7cdc478482..23ca0ac77b 100644 --- a/images/virtualization-artifact/pkg/controller/service/size_policy_service.go +++ b/images/virtualization-artifact/pkg/controller/service/size_policy_service.go @@ -28,6 +28,8 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2" ) +var ErrSizingPolicyValidation = errors.New("please check the sizing policy of the virtual machine class or contact the administrator for more information") + type SizePolicyService struct{} func NewSizePolicyService() *SizePolicyService { @@ -35,7 +37,6 @@ func NewSizePolicyService() *SizePolicyService { } func (s *SizePolicyService) CheckVMMatchedSizePolicy(vm *v1alpha2.VirtualMachine, vmClass *v1alpha2.VirtualMachineClass) error { - // check if no sizing policy requirements are set if vmClass == nil || len(vmClass.Spec.SizingPolicies) == 0 { return nil } @@ -47,12 +48,23 @@ func (s *SizePolicyService) CheckVMMatchedSizePolicy(vm *v1alpha2.VirtualMachine var errs []error - errs = append(errs, validateCoreFraction(vm, sizePolicy)...) - errs = append(errs, validateMemory(vm, sizePolicy)...) - errs = append(errs, validatePerCoreMemory(vm, sizePolicy)...) + err := validateCoreFraction(vm, sizePolicy) + if err != nil { + errs = append(errs, err) + } + + err = validateMemory(vm, sizePolicy) + if err != nil { + errs = append(errs, err) + } + + err = validatePerCoreMemory(vm, sizePolicy) + if err != nil { + errs = append(errs, err) + } if len(errs) > 0 { - return fmt.Errorf("sizing policy validate: %w", errors.Join(errs...)) + return fmt.Errorf("failed to validate sizing policy: %w: %w", errors.Join(errs...), ErrSizingPolicyValidation) } return nil @@ -72,16 +84,15 @@ func getVMSizePolicy(vm *v1alpha2.VirtualMachine, vmClass *v1alpha2.VirtualMachi return nil } -func validateCoreFraction(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolicy) (errs []error) { +func validateCoreFraction(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolicy) error { if len(sp.CoreFractions) == 0 { - return + return nil } fractionStr := strings.ReplaceAll(vm.Spec.CPU.CoreFraction, "%", "") fraction, err := strconv.Atoi(fractionStr) if err != nil { - errs = append(errs, fmt.Errorf("unable to parse CPU core fraction: %w", err)) - return + return fmt.Errorf("unable to parse CPU core fraction: %w", err) } hasFractionValueInPolicy := false @@ -92,33 +103,34 @@ func validateCoreFraction(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolicy } if !hasFractionValueInPolicy { - errs = append(errs, fmt.Errorf("VM core fraction value %d is not within the allowed values: %v", fraction, sp.CoreFractions)) + formattedCoreFractions := formatCoreFractionValues(sp.CoreFractions) + return fmt.Errorf("VM core fraction value %s is not within the allowed values: %v", vm.Spec.CPU.CoreFraction, formattedCoreFractions) } - return + return nil } -func validateMemory(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolicy) (errs []error) { +func validateMemory(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolicy) error { if sp.Memory == nil || sp.Memory.Max == nil || sp.Memory.Max.IsZero() { - return + return nil } if sp.Memory.Min != nil && vm.Spec.Memory.Size.Cmp(*sp.Memory.Min) == common.CmpLesser { - errs = append(errs, fmt.Errorf( + return fmt.Errorf( "requested VM memory (%s) is less than the minimum allowed, available range [%s, %s]", vm.Spec.Memory.Size.String(), sp.Memory.Min.String(), sp.Memory.Max.String(), - )) + ) } if vm.Spec.Memory.Size.Cmp(*sp.Memory.Max) == common.CmpGreater { - errs = append(errs, fmt.Errorf( + return fmt.Errorf( "requested VM memory (%s) exceeds the maximum allowed, available range [%s, %s]", vm.Spec.Memory.Size.String(), sp.Memory.Min.String(), sp.Memory.Max.String(), - )) + ) } if sp.Memory.Step != nil && !sp.Memory.Step.IsZero() { @@ -128,16 +140,16 @@ func validateMemory(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolicy) (err } err := validateIsQuantized(vm.Spec.Memory.Size, minVal, *sp.Memory.Max, *sp.Memory.Step, "VM memory") if err != nil { - errs = append(errs, err) + return err } } - return + return nil } -func validatePerCoreMemory(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolicy) (errs []error) { +func validatePerCoreMemory(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolicy) error { if sp.Memory == nil || sp.Memory.PerCore == nil || sp.Memory.PerCore.Max == nil || sp.Memory.PerCore.Max.IsZero() { - return + return nil } // Calculate memory portion per CPU core @@ -147,21 +159,21 @@ func validatePerCoreMemory(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolic perCoreMemory := resource.NewQuantity(vmPerCore, resource.BinarySI) if sp.Memory.PerCore.Min != nil && perCoreMemory.Cmp(*sp.Memory.PerCore.Min) == common.CmpLesser { - errs = append(errs, fmt.Errorf( + return fmt.Errorf( "requested VM per core memory (%s) is less than the minimum allowed, available range [%s, %s]", perCoreMemory.String(), sp.Memory.PerCore.Min.String(), sp.Memory.PerCore.Max.String(), - )) + ) } if perCoreMemory.Cmp(*sp.Memory.PerCore.Max) == common.CmpGreater { - errs = append(errs, fmt.Errorf( + return fmt.Errorf( "requested VM per core memory (%s) exceeds the maximum allowed, available range [%s, %s]", perCoreMemory.String(), sp.Memory.PerCore.Min.String(), sp.Memory.PerCore.Max.String(), - )) + ) } if sp.Memory.Step != nil && !sp.Memory.Step.IsZero() { @@ -171,11 +183,11 @@ func validatePerCoreMemory(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolic } err := validateIsQuantized(*perCoreMemory, minVal, *sp.Memory.PerCore.Max, *sp.Memory.Step, "VM per core memory") if err != nil { - errs = append(errs, err) + return err } } - return + return nil } func validateIsQuantized(value, min, max, step resource.Quantity, source string) (err error) { @@ -212,3 +224,11 @@ func generateValidGrid(min, max, step resource.Quantity) []resource.Quantity { return grid } + +func formatCoreFractionValues(cf []v1alpha2.CoreFractionValue) []string { + result := make([]string, len(cf)) + for i, v := range cf { + result[i] = fmt.Sprintf("%d%%", v) + } + return result +} From e555918f2409a090c0c82480acf9aab84d2451e2 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Thu, 11 Dec 2025 14:46:26 +0300 Subject: [PATCH 3/3] chore(api): use CutSuffix and improve err msg Signed-off-by: Roman Sysoev --- .../pkg/controller/service/size_policy_service.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/service/size_policy_service.go b/images/virtualization-artifact/pkg/controller/service/size_policy_service.go index 23ca0ac77b..b07bc5eaac 100644 --- a/images/virtualization-artifact/pkg/controller/service/size_policy_service.go +++ b/images/virtualization-artifact/pkg/controller/service/size_policy_service.go @@ -64,7 +64,7 @@ func (s *SizePolicyService) CheckVMMatchedSizePolicy(vm *v1alpha2.VirtualMachine } if len(errs) > 0 { - return fmt.Errorf("failed to validate sizing policy: %w: %w", errors.Join(errs...), ErrSizingPolicyValidation) + return fmt.Errorf("sizing policy validation has failed: %w: %w", errors.Join(errs...), ErrSizingPolicyValidation) } return nil @@ -89,7 +89,7 @@ func validateCoreFraction(vm *v1alpha2.VirtualMachine, sp *v1alpha2.SizingPolicy return nil } - fractionStr := strings.ReplaceAll(vm.Spec.CPU.CoreFraction, "%", "") + fractionStr, _ := strings.CutSuffix(vm.Spec.CPU.CoreFraction, "%") fraction, err := strconv.Atoi(fractionStr) if err != nil { return fmt.Errorf("unable to parse CPU core fraction: %w", err)