Skip to content

Commit 70875e6

Browse files
author
Valeriy Khorunzhin
committed
t
Signed-off-by: Valeriy Khorunzhin <[email protected]>
1 parent f5ee46f commit 70875e6

File tree

11 files changed

+208
-5
lines changed

11 files changed

+208
-5
lines changed

api/core/v1alpha2/virtual_machine.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,9 @@ type CPUSpec struct {
165165
Cores int `json:"cores"`
166166

167167
// Guaranteed share of CPU that will be allocated to the VM. Specified as a percentage.
168-
// +kubebuilder:default:="100%"
169-
// +kubebuilder:validation:Enum:={"5%", "10%", "25%", "50%", "100%"}
168+
// The range of available values is defined in the VirtualMachineClass sizing policy.
169+
// If not specified, the default value from the VirtualMachineClass will be used.
170+
// +kubebuilder:validation:Pattern=`^(100|[1-9][0-9]?|[1-9])%$`
170171
CoreFraction string `json:"coreFraction,omitempty"`
171172
}
172173

api/core/v1alpha2/virtual_machine_class.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ type SizingPolicy struct {
119119
Memory *SizingPolicyMemory `json:"memory,omitempty"`
120120
// Allowed values of the `coreFraction` parameter.
121121
CoreFractions []CoreFractionValue `json:"coreFractions,omitempty"`
122+
// Default core fraction value for the VirtualMachineClass.
123+
// This value will be used when creating a VM without an explicitly specified coreFraction.
124+
DefaultCoreFraction *CoreFractionValue `json:"defaultCoreFraction,omitempty"`
122125
// Allowed values of the `dedicatedCores` parameter.
123126
DedicatedCores []bool `json:"dedicatedCores,omitempty"`
124127
// The policy applies for a specified range of the number of CPU cores.

api/core/v1alpha3/virtual_machine_class.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ type SizingPolicy struct {
117117
Memory *SizingPolicyMemory `json:"memory,omitempty"`
118118
// Allowed values of the `coreFraction` parameter in percentages (e.g., "5%", "10%", "25%", "50%", "100%").
119119
CoreFractions []CoreFractionValue `json:"coreFractions,omitempty"`
120+
// Default core fraction value for the VirtualMachineClass.
121+
DefaultCoreFraction *CoreFractionValue `json:"defaultCoreFraction,omitempty"`
120122
// Allowed values of the `dedicatedCores` parameter.
121123
DedicatedCores []bool `json:"dedicatedCores,omitempty"`
122124
// The policy applies for a specified range of the number of CPU cores.

api/core/v1alpha3/virtual_machine_class_conversion.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,20 @@ func convertSpecV3ToV2(v3Spec VirtualMachineClassSpec) (v1alpha2.VirtualMachineC
124124
}
125125
}
126126

127+
if v3Policy.DefaultCoreFraction != nil {
128+
fractionStr := string(*v3Policy.DefaultCoreFraction)
129+
if !regexp.MustCompile(`^([1-9]|[1-9][0-9]|100)%$`).MatchString(fractionStr) {
130+
return v1alpha2.VirtualMachineClassSpec{}, fmt.Errorf("spec.sizingPolicies[%d].defaultCoreFraction: value must be a percentage between 1%% and 100%% (e.g., 5%%, 10%%, 50%%), got %q", i, fractionStr)
131+
}
132+
fractionStr = fractionStr[:len(fractionStr)-1]
133+
fractionInt, err := strconv.Atoi(fractionStr)
134+
if err != nil {
135+
return v1alpha2.VirtualMachineClassSpec{}, fmt.Errorf("failed to parse default core fraction: %w", err)
136+
}
137+
v2Fraction := v1alpha2.CoreFractionValue(fractionInt)
138+
v2Policy.DefaultCoreFraction = &v2Fraction
139+
}
140+
127141
v2Spec.SizingPolicies[i] = v2Policy
128142
}
129143
}
@@ -191,6 +205,11 @@ func convertSpecV2ToV3(v2Spec v1alpha2.VirtualMachineClassSpec) VirtualMachineCl
191205
}
192206
}
193207

208+
if v2Policy.DefaultCoreFraction != nil {
209+
v3Fraction := CoreFractionValue(fmt.Sprintf("%d%%", *v2Policy.DefaultCoreFraction))
210+
v3Policy.DefaultCoreFraction = &v3Fraction
211+
}
212+
194213
v3Spec.SizingPolicies[i] = v3Policy
195214
}
196215
}

crds/doc-ru-virtualmachineclasses.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ spec:
134134
coreFractions:
135135
description: |
136136
Допустимые значения параметра `coreFraction` в процентах (например, "5%", "10%", "25%", "50%", "100%").
137+
defaultCoreFraction:
138+
description: |
139+
Значение coreFraction по умолчанию для VirtualMachineClass.
140+
Это значение будет использовано при создании ВМ без явно указанного coreFraction.
137141
cores:
138142
description: |
139143
Политика применяется для заданного диапазона числа ядер CPU.

crds/virtualmachineclasses.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,13 @@ spec:
281281
items:
282282
type: boolean
283283
type: array
284+
defaultCoreFraction:
285+
description: |-
286+
Default core fraction value for the VirtualMachineClass.
287+
This value will be used when creating a VM without an explicitly specified coreFraction.
288+
maximum: 100
289+
minimum: 1
290+
type: integer
284291
memory:
285292
description: Memory sizing policy.
286293
properties:
@@ -334,6 +341,9 @@ spec:
334341
x-kubernetes-int-or-string: true
335342
type: object
336343
type: object
344+
x-kubernetes-validations:
345+
- message: The defaultCoreFraction must be one of the values in coreFractions
346+
rule: "!has(self.defaultCoreFraction) || self.defaultCoreFraction in self.coreFractions"
337347
type: array
338348
tolerations:
339349
description: |-
@@ -775,6 +785,9 @@ spec:
775785
items:
776786
type: boolean
777787
type: array
788+
defaultCoreFraction:
789+
description: Default core fraction value for the VirtualMachineClass.
790+
type: string
778791
memory:
779792
description: Memory sizing policy.
780793
properties:

crds/virtualmachines.yaml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ spec:
3838
properties:
3939
spec:
4040
x-kubernetes-validations:
41-
- rule: "self.cpu.coreFraction.endsWith('%') && int(self.cpu.coreFraction.replace('%', '')) >= 1 && int(self.cpu.coreFraction.replace('%', '')) <= 100"
41+
- rule: "!has(self.cpu.coreFraction) || (self.cpu.coreFraction.endsWith('%') && int(self.cpu.coreFraction.replace('%', '')) >= 1 && int(self.cpu.coreFraction.replace('%', '')) <= 100)"
4242
message: "Core fraction must be between 1% and 100%."
4343
type: object
4444
required:
@@ -888,9 +888,8 @@ spec:
888888
description: Number of cores.
889889
coreFraction:
890890
type: string
891-
default: "100%"
892891
description: |
893-
Guaranteed share of CPU time that will be allocated to the VM. Specified as a percentage. The range of available values is set in the `sizePolicy` parameter of the VirtualMachineClass; if it is not set, use values within the 1–100% range.
892+
Guaranteed share of CPU time that will be allocated to the VM. Specified as a percentage. The range of available values is set in the `sizePolicy` parameter of the VirtualMachineClass. If not specified, the default value from the VirtualMachineClass sizing policy will be used. If no default is set in the class, "100%" will be applied.
894893
895894
memory:
896895
type: object
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
Copyright 2025 Flant JSC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package defaulter
18+
19+
import (
20+
"context"
21+
"fmt"
22+
23+
"k8s.io/apimachinery/pkg/types"
24+
"sigs.k8s.io/controller-runtime/pkg/client"
25+
26+
"github.com/deckhouse/virtualization/api/core/v1alpha2"
27+
"github.com/deckhouse/virtualization/api/core/v1alpha3"
28+
)
29+
30+
type CoreFractionDefaulter struct {
31+
client client.Client
32+
}
33+
34+
func NewCoreFractionDefaulter(client client.Client) *CoreFractionDefaulter {
35+
return &CoreFractionDefaulter{
36+
client: client,
37+
}
38+
}
39+
40+
func (d *CoreFractionDefaulter) Default(ctx context.Context, vm *v1alpha2.VirtualMachine) error {
41+
// Skip if coreFraction is already set.
42+
if vm.Spec.CPU.CoreFraction != "" {
43+
return nil
44+
}
45+
46+
// Skip if vmClassName is not set (will be handled by validation later).
47+
if vm.Spec.VirtualMachineClassName == "" {
48+
return nil
49+
}
50+
51+
// Get the VMClass.
52+
vmClass := &v1alpha3.VirtualMachineClass{}
53+
err := d.client.Get(ctx, types.NamespacedName{Name: vm.Spec.VirtualMachineClassName}, vmClass)
54+
if err != nil {
55+
return fmt.Errorf("failed to get VirtualMachineClass %q: %w", vm.Spec.VirtualMachineClassName, err)
56+
}
57+
58+
// Find the matching sizing policy based on CPU cores.
59+
defaultCoreFraction := d.getDefaultCoreFraction(vm, vmClass)
60+
if defaultCoreFraction != "" {
61+
vm.Spec.CPU.CoreFraction = defaultCoreFraction
62+
}
63+
64+
return nil
65+
}
66+
67+
// getDefaultCoreFraction finds the default core fraction from the VMClass sizing policy
68+
// that matches the VM's CPU cores count.
69+
func (d *CoreFractionDefaulter) getDefaultCoreFraction(vm *v1alpha2.VirtualMachine, vmClass *v1alpha3.VirtualMachineClass) string {
70+
const defaultValue = "100%"
71+
72+
if vmClass == nil || len(vmClass.Spec.SizingPolicies) == 0 {
73+
return defaultValue
74+
}
75+
76+
for _, sp := range vmClass.Spec.SizingPolicies {
77+
if sp.Cores == nil {
78+
continue
79+
}
80+
81+
// Check if VM's cores fall within this policy's range.
82+
if vm.Spec.CPU.Cores >= sp.Cores.Min && vm.Spec.CPU.Cores <= sp.Cores.Max {
83+
if sp.DefaultCoreFraction != nil {
84+
return string(*sp.DefaultCoreFraction)
85+
}
86+
return defaultValue
87+
}
88+
}
89+
90+
return defaultValue
91+
}

images/virtualization-artifact/pkg/controller/vm/vm_webhook.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ func NewDefaulter(client client.Client, vmClassService *service.VirtualMachineCl
131131
return &Defaulter{
132132
defaulters: []VirtualMachineDefaulter{
133133
defaulter.NewVirtualMachineClassNameDefaulter(client, vmClassService),
134+
defaulter.NewCoreFractionDefaulter(client),
134135
},
135136
log: log.With("webhook", "mutating"),
136137
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
Copyright 2024 Flant JSC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package validators
18+
19+
import (
20+
"context"
21+
"fmt"
22+
23+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
24+
25+
"github.com/deckhouse/virtualization/api/core/v1alpha2"
26+
)
27+
28+
type DefaultCoreFractionValidator struct{}
29+
30+
func NewDefaultCoreFractionValidator() *DefaultCoreFractionValidator {
31+
return &DefaultCoreFractionValidator{}
32+
}
33+
34+
func (v *DefaultCoreFractionValidator) ValidateCreate(_ context.Context, vmclass *v1alpha2.VirtualMachineClass) (admission.Warnings, error) {
35+
return nil, v.validate(vmclass)
36+
}
37+
38+
func (v *DefaultCoreFractionValidator) ValidateUpdate(_ context.Context, _, newVMClass *v1alpha2.VirtualMachineClass) (admission.Warnings, error) {
39+
return nil, v.validate(newVMClass)
40+
}
41+
42+
func (v *DefaultCoreFractionValidator) validate(vmclass *v1alpha2.VirtualMachineClass) error {
43+
for i, policy := range vmclass.Spec.SizingPolicies {
44+
if policy.DefaultCoreFraction == nil {
45+
continue
46+
}
47+
48+
if len(policy.CoreFractions) == 0 {
49+
continue
50+
}
51+
52+
if !containsCoreFraction(policy.CoreFractions, *policy.DefaultCoreFraction) {
53+
return fmt.Errorf("vmclass %s sizingPolicy[%d]: defaultCoreFraction %d is not in the allowed coreFractions list %v",
54+
vmclass.Name, i, *policy.DefaultCoreFraction, policy.CoreFractions)
55+
}
56+
}
57+
58+
return nil
59+
}
60+
61+
func containsCoreFraction(fractions []v1alpha2.CoreFractionValue, value v1alpha2.CoreFractionValue) bool {
62+
for _, f := range fractions {
63+
if f == value {
64+
return true
65+
}
66+
}
67+
return false
68+
}
69+

0 commit comments

Comments
 (0)