diff --git a/pkg/cloudprovider/provider/kubevirt/provider.go b/pkg/cloudprovider/provider/kubevirt/provider.go index dbcb0dc8c..821d32a2a 100644 --- a/pkg/cloudprovider/provider/kubevirt/provider.go +++ b/pkg/cloudprovider/provider/kubevirt/provider.go @@ -99,6 +99,8 @@ type Config struct { DNSConfig *corev1.PodDNSConfig DNSPolicy corev1.DNSPolicy CPUs string + VCPUs *kubevirtv1.CPU + Resources *corev1.ResourceList Memory string Namespace string OSImageSource *cdiv1beta1.DataVolumeSource @@ -274,14 +276,23 @@ func (p *provider) getConfig(provSpec clusterv1alpha1.ProviderSpec) (*Config, *p return nil, nil, fmt.Errorf("failed to decode kubeconfig: %w", err) } - config.CPUs, err = p.configVarResolver.GetConfigVarStringValue(rawConfig.VirtualMachine.Template.CPUs) + cpus, err := p.configVarResolver.GetConfigVarStringValue(rawConfig.VirtualMachine.Template.CPUs) if err != nil { return nil, nil, fmt.Errorf(`failed to get value of "cpus" field: %w`, err) } - config.Memory, err = p.configVarResolver.GetConfigVarStringValue(rawConfig.VirtualMachine.Template.Memory) + + memory, err := p.configVarResolver.GetConfigVarStringValue(rawConfig.VirtualMachine.Template.Memory) if err != nil { return nil, nil, fmt.Errorf(`failed to get value of "memory" field: %w`, err) } + + if rawConfig.VirtualMachine.Instancetype == nil { + config.Resources, config.VCPUs, err = parseResources(cpus, memory, rawConfig.VirtualMachine.Template.VCPUs) + if err != nil { + return nil, nil, fmt.Errorf(`failed to configure resource requests and limits and vcpus: %w`, err) + } + } + config.Namespace = getNamespace() if len(rawConfig.VirtualMachine.Template.PrimaryDisk.ExtraHeaders) > 0 { config.ExtraHeaders = rawConfig.VirtualMachine.Template.PrimaryDisk.ExtraHeaders @@ -617,8 +628,16 @@ func (p *provider) Validate(ctx context.Context, _ *zap.SugaredLogger, spec clus // If instancetype is specified, skip CPU and Memory validation. // Values will come from instancetype. if c.Instancetype == nil { - if _, err := parseResources(c.CPUs, c.Memory); err != nil { - return err + if c.Resources == nil { + return fmt.Errorf("no resource requests set for the virtual machine") + } + + if c.VCPUs == nil && c.Resources.Cpu().IsZero() { + return fmt.Errorf("no CPUs configured. Either vCPUs or CPUs have to be set") + } + + if c.VCPUs != nil && !c.Resources.Cpu().IsZero() { + return fmt.Errorf("vCPUs and CPUs cannot be configured at the same time") } } @@ -754,12 +773,8 @@ func (p *provider) newVirtualMachine(c *Config, pc *providerconfigtypes.Config, // if no instancetype, resources are from config. if c.Instancetype == nil { - requestsAndLimits, err := parseResources(c.CPUs, c.Memory) - if err != nil { - return nil, err - } - resourceRequirements.Requests = *requestsAndLimits - resourceRequirements.Limits = *requestsAndLimits + resourceRequirements.Requests = *c.Resources + resourceRequirements.Limits = *c.Resources } // Add cluster labels @@ -841,6 +856,13 @@ func (p *provider) newVirtualMachine(c *Config, pc *providerconfigtypes.Config, DataVolumeTemplates: getDataVolumeTemplates(c, dataVolumeName, dvAnnotations), }, } + + if c.VCPUs != nil { + virtualMachine.Spec.Template.Spec.Domain.CPU = &kubevirtv1.CPU{ + Cores: c.VCPUs.Cores, + } + } + return virtualMachine, nil } @@ -868,19 +890,25 @@ func (p *provider) Cleanup(ctx context.Context, _ *zap.SugaredLogger, machine *c return false, sigClient.Delete(ctx, vm) } -func parseResources(cpus, memory string) (*corev1.ResourceList, error) { +func parseResources(cpus, memory string, vpcus kubevirttypes.VCPUs) (*corev1.ResourceList, *kubevirtv1.CPU, error) { memoryResource, err := resource.ParseQuantity(memory) if err != nil { - return nil, fmt.Errorf("failed to parse memory requests: %w", err) + return nil, nil, fmt.Errorf("failed to parse memory requests: %w", err) + } + + if vpcus.Cores != 0 { + return &corev1.ResourceList{corev1.ResourceMemory: memoryResource}, &kubevirtv1.CPU{Cores: uint32(vpcus.Cores)}, nil } + cpuResource, err := resource.ParseQuantity(cpus) if err != nil { - return nil, fmt.Errorf("failed to parse cpu request: %w", err) + return nil, nil, fmt.Errorf("failed to parse cpu requests: %w", err) } + return &corev1.ResourceList{ corev1.ResourceMemory: memoryResource, corev1.ResourceCPU: cpuResource, - }, nil + }, nil, nil } func (p *provider) SetMetricsForMachines(_ clusterv1alpha1.MachineList) error { diff --git a/pkg/cloudprovider/provider/kubevirt/provider_test.go b/pkg/cloudprovider/provider/kubevirt/provider_test.go index 4651889d5..122c6f7a1 100644 --- a/pkg/cloudprovider/provider/kubevirt/provider_test.go +++ b/pkg/cloudprovider/provider/kubevirt/provider_test.go @@ -72,6 +72,7 @@ type kubevirtProviderSpecConf struct { ProviderNetwork *types.ProviderNetwork ExtraHeadersSet bool EvictStrategy string + VCPUs uint32 } func (k kubevirtProviderSpecConf) rawProviderSpec(t *testing.T) []byte { @@ -132,7 +133,13 @@ func (k kubevirtProviderSpecConf) rawProviderSpec(t *testing.T) []byte { }, {{- end }} "template": { + {{- if .VCPUs }} + "vcpus": { + "cores": {{ .VCPUs }} + }, + {{- else }} "cpus": "2", + {{- end }} "memory": "2Gi", {{- if .SecondaryDisks }} "secondaryDisks": [{ @@ -283,6 +290,10 @@ func TestNewVirtualMachine(t *testing.T) { name: "eviction-strategy-live-migrate", specConf: kubevirtProviderSpecConf{EvictStrategy: "LiveMigrate"}, }, + { + name: "dedicated-vcpus", + specConf: kubevirtProviderSpecConf{VCPUs: 2}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/cloudprovider/provider/kubevirt/testdata/dedicated-vcpus.yaml b/pkg/cloudprovider/provider/kubevirt/testdata/dedicated-vcpus.yaml new file mode 100644 index 000000000..daae69a66 --- /dev/null +++ b/pkg/cloudprovider/provider/kubevirt/testdata/dedicated-vcpus.yaml @@ -0,0 +1,80 @@ +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + annotations: + labels: + cluster.x-k8s.io/cluster-name: cluster-name + cluster.x-k8s.io/role: worker + kubevirt.io/vm: dedicated-vcpus + md: md-name + name: dedicated-vcpus + namespace: test-namespace +spec: + dataVolumeTemplates: + - metadata: + name: dedicated-vcpus + spec: + storage: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 10Gi + storageClassName: longhorn + source: + http: + url: http://x.y.z.t/ubuntu.img + runStrategy: Once + template: + metadata: + creationTimestamp: null + annotations: + "kubevirt.io/allow-pod-bridge-network-live-migration": "true" + "ovn.kubernetes.io/allow_live_migration": "true" + labels: + cluster.x-k8s.io/cluster-name: cluster-name + cluster.x-k8s.io/role: worker + kubevirt.io/vm: dedicated-vcpus + md: md-name + spec: + affinity: {} + domain: + cpu: + cores: 2 + devices: + disks: + - disk: + bus: virtio + name: datavolumedisk + - disk: + bus: virtio + name: cloudinitdisk + interfaces: + - name: default + bridge: {} + networkInterfaceMultiqueue: true + resources: + limits: + memory: 2Gi + requests: + memory: 2Gi + networks: + - name: default + pod: {} + terminationGracePeriodSeconds: 30 + topologyspreadconstraints: + - maxskew: 1 + topologykey: kubernetes.io/hostname + whenunsatisfiable: ScheduleAnyway + labelselector: + matchlabels: + md: md-name + volumes: + - dataVolume: + name: dedicated-vcpus + name: datavolumedisk + - cloudInitNoCloud: + secretRef: + name: udsn + name: cloudinitdisk + evictionStrategy: External diff --git a/pkg/cloudprovider/provider/kubevirt/types/types.go b/pkg/cloudprovider/provider/kubevirt/types/types.go index 79e7100fc..f4733ec4b 100644 --- a/pkg/cloudprovider/provider/kubevirt/types/types.go +++ b/pkg/cloudprovider/provider/kubevirt/types/types.go @@ -70,12 +70,23 @@ type Flavor struct { // Template. type Template struct { + // VCPUs is to configure vcpus used by a the virtual machine + // when using kubevirts cpuAllocationRatio feature this leads to auto assignment of the + // calculated ratio as resource cpu requests for the pod which launches the virtual machine + VCPUs VCPUs `json:"vcpus,omitempty"` + // CPUs is to configure cpu requests and limits directly for the pod which launches the virtual machine + // and is related to the underlying hardware CPUs providerconfigtypes.ConfigVarString `json:"cpus,omitempty"` Memory providerconfigtypes.ConfigVarString `json:"memory,omitempty"` PrimaryDisk PrimaryDisk `json:"primaryDisk,omitempty"` SecondaryDisks []SecondaryDisks `json:"secondaryDisks,omitempty"` } +// VCPUs. +type VCPUs struct { + Cores int `json:"cores,omitempty"` +} + // PrimaryDisk. type PrimaryDisk struct { Disk