• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubevirt / kubevirt / 08d406d8-2a6f-46c7-9640-1b0c0a01b642

06 Jul 2026 11:33AM UTC coverage: 72.32% (-0.001%) from 72.321%
08d406d8-2a6f-46c7-9640-1b0c0a01b642

push

prow

web-flow
Merge pull request #18308 from nestoracunablanco/refactor/validateGPUsHostDevices

refactor: extract GPU/HostDevice validation into separate functions

20 of 20 new or added lines in 1 file covered. (100.0%)

34 existing lines in 2 files now uncovered.

83784 of 115852 relevant lines covered (72.32%)

600.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.12
/pkg/virt-controller/services/renderresources.go
1
package services
2

3
import (
4
        "fmt"
5
        "strconv"
6
        "strings"
7

8
        "k8s.io/client-go/tools/cache"
9

10
        "kubevirt.io/client-go/log"
11

12
        k8sv1 "k8s.io/api/core/v1"
13
        "k8s.io/apimachinery/pkg/api/resource"
14
        v1 "kubevirt.io/api/core/v1"
15

16
        netvmispec "kubevirt.io/kubevirt/pkg/network/vmispec"
17
        "kubevirt.io/kubevirt/pkg/util"
18
        "kubevirt.io/kubevirt/pkg/util/hardware"
19
        virtconfig "kubevirt.io/kubevirt/pkg/virt-config"
20
)
21

22
type ResourceRendererOption func(renderer *ResourceRenderer)
23

24
type ResourceRenderer struct {
25
        vmLimits           k8sv1.ResourceList
26
        vmRequests         k8sv1.ResourceList
27
        calculatedLimits   k8sv1.ResourceList
28
        calculatedRequests k8sv1.ResourceList
29
        resourceClaims     []k8sv1.ResourceClaim
30
}
31

32
type resourcePredicate func(*v1.VirtualMachineInstance) bool
33

34
type VMIResourcePredicates struct {
35
        resourceRules []VMIResourceRule
36
        vmi           *v1.VirtualMachineInstance
37
}
38

39
type VMIResourceRule struct {
40
        predicate resourcePredicate
41
        option    ResourceRendererOption
42
}
43

44
func not(p resourcePredicate) resourcePredicate {
652✔
45
        return func(vmi *v1.VirtualMachineInstance) bool {
1,304✔
46
                return !p(vmi)
652✔
47
        }
652✔
48
}
49
func NewVMIResourceRule(p resourcePredicate, option ResourceRendererOption) VMIResourceRule {
5,216✔
50
        return VMIResourceRule{predicate: p, option: option}
5,216✔
51
}
5,216✔
52

53
func doesVMIRequireDedicatedCPU(vmi *v1.VirtualMachineInstance) bool {
652✔
54
        return vmi.IsCPUDedicated()
652✔
55
}
652✔
56

57
func NewResourceRenderer(vmLimits k8sv1.ResourceList, vmRequests k8sv1.ResourceList, options ...ResourceRendererOption) *ResourceRenderer {
361✔
58
        limits := map[k8sv1.ResourceName]resource.Quantity{}
361✔
59
        requests := map[k8sv1.ResourceName]resource.Quantity{}
361✔
60
        copyResources(vmLimits, limits)
361✔
61
        copyResources(vmRequests, requests)
361✔
62

361✔
63
        resourceRenderer := &ResourceRenderer{
361✔
64
                vmLimits:           limits,
361✔
65
                vmRequests:         requests,
361✔
66
                calculatedLimits:   map[k8sv1.ResourceName]resource.Quantity{},
361✔
67
                calculatedRequests: map[k8sv1.ResourceName]resource.Quantity{},
361✔
68
                resourceClaims:     []k8sv1.ResourceClaim{},
361✔
69
        }
361✔
70

361✔
71
        for _, opt := range options {
1,886✔
72
                opt(resourceRenderer)
1,525✔
73
        }
1,525✔
74
        return resourceRenderer
361✔
75
}
76

77
func (rr *ResourceRenderer) Limits() k8sv1.ResourceList {
384✔
78
        podLimits := map[k8sv1.ResourceName]resource.Quantity{}
384✔
79
        copyResources(rr.calculatedLimits, podLimits)
384✔
80
        copyResources(rr.vmLimits, podLimits)
384✔
81
        return podLimits
384✔
82
}
384✔
83

84
func (rr *ResourceRenderer) Requests() k8sv1.ResourceList {
380✔
85
        podRequests := map[k8sv1.ResourceName]resource.Quantity{}
380✔
86
        copyResources(rr.calculatedRequests, podRequests)
380✔
87
        copyResources(rr.vmRequests, podRequests)
380✔
88
        return podRequests
380✔
89
}
380✔
90

91
func (rr *ResourceRenderer) Claims() []k8sv1.ResourceClaim {
358✔
92
        return rr.resourceClaims
358✔
93
}
358✔
94

95
func (rr *ResourceRenderer) ResourceRequirements() k8sv1.ResourceRequirements {
350✔
96
        return k8sv1.ResourceRequirements{
350✔
97
                Limits:   rr.Limits(),
350✔
98
                Requests: rr.Requests(),
350✔
99
                Claims:   rr.Claims(),
350✔
100
        }
350✔
101
}
350✔
102

103
func WithEphemeralStorageRequest() ResourceRendererOption {
327✔
104
        return func(renderer *ResourceRenderer) {
654✔
105
                // Add ephemeral storage request to container to be used by Kubevirt. This amount of ephemeral storage
327✔
106
                // should be added to the user's request.
327✔
107
                ephemeralStorageOverhead := resource.MustParse(ephemeralStorageOverheadSize)
327✔
108
                ephemeralStorageRequested := renderer.vmRequests[k8sv1.ResourceEphemeralStorage]
327✔
109
                ephemeralStorageRequested.Add(ephemeralStorageOverhead)
327✔
110
                renderer.vmRequests[k8sv1.ResourceEphemeralStorage] = ephemeralStorageRequested
327✔
111

327✔
112
                if ephemeralStorageLimit, ephemeralStorageLimitDefined := renderer.vmLimits[k8sv1.ResourceEphemeralStorage]; ephemeralStorageLimitDefined {
329✔
113
                        ephemeralStorageLimit.Add(ephemeralStorageOverhead)
2✔
114
                        renderer.vmLimits[k8sv1.ResourceEphemeralStorage] = ephemeralStorageLimit
2✔
115
                }
2✔
116
        }
117
}
118

119
// Helper function to extract IO thread CPU count from VMI
120
func getIOThreadsCount(vmi *v1.VirtualMachineInstance) int64 {
336✔
121
        if vmi == nil || vmi.Spec.Domain.IOThreads == nil ||
336✔
122
                vmi.Spec.Domain.IOThreads.SupplementalPoolThreadCount == nil {
666✔
123
                return 0
330✔
124
        }
330✔
125
        return int64(*vmi.Spec.Domain.IOThreads.SupplementalPoolThreadCount)
6✔
126
}
127

128
func WithoutDedicatedCPU(vmi *v1.VirtualMachineInstance, cpuAllocationRatio int, withCPULimits bool) ResourceRendererOption {
330✔
129
        return func(renderer *ResourceRenderer) {
653✔
130
                cpu := vmi.Spec.Domain.CPU
323✔
131
                vcpus := calcVCPUs(cpu)
323✔
132
                ioThreadCPUs := getIOThreadsCount(vmi) // Get IO thread count
323✔
133
                totalCPUs := vcpus + ioThreadCPUs      // Include IO threads
323✔
134
                if totalCPUs != 0 && cpuAllocationRatio > 0 {
634✔
135
                        val := float64(totalCPUs) / float64(cpuAllocationRatio)
311✔
136
                        vcpusStr := fmt.Sprintf("%g", val)
311✔
137
                        if val < 1 {
619✔
138
                                val *= 1000
308✔
139
                                vcpusStr = fmt.Sprintf("%gm", val)
308✔
140
                        }
308✔
141
                        renderer.calculatedRequests[k8sv1.ResourceCPU] = resource.MustParse(vcpusStr)
311✔
142
                        if withCPULimits {
315✔
143
                                renderer.calculatedLimits[k8sv1.ResourceCPU] = resource.MustParse(strconv.FormatInt(totalCPUs, 10))
4✔
144
                        }
4✔
145
                }
146
        }
147
}
148

149
func WithGPUsDevicePlugins(gpus []v1.GPU) ResourceRendererOption {
329✔
150
        return func(r *ResourceRenderer) {
333✔
151
                res := r.ResourceRequirements()
4✔
152
                for _, g := range gpus {
8✔
153
                        if g.DeviceName != "" && g.ClaimRequest == nil {
7✔
154
                                requestResource(&res, g.DeviceName)
3✔
155
                        }
3✔
156
                }
157
                copyResources(res.Limits, r.calculatedLimits)
4✔
158
                copyResources(res.Requests, r.calculatedRequests)
4✔
159
        }
160
}
161

162
func WithGPUsDRA(gpus []v1.GPU) ResourceRendererOption {
331✔
163
        return func(r *ResourceRenderer) {
336✔
164
                for _, g := range gpus {
12✔
165
                        if g.DeviceName == "" && g.ClaimRequest != nil {
13✔
166
                                claim := k8sv1.ResourceClaim{
6✔
167
                                        Name:    g.ClaimRequest.ClaimName,
6✔
168
                                        Request: g.ClaimRequest.RequestName,
6✔
169
                                }
6✔
170
                                r.resourceClaims = append(r.resourceClaims, claim)
6✔
171
                        }
6✔
172
                }
173
        }
174
}
175

176
// WithHostDevicesDevicePlugins adds resource requests/limits only for HostDevices managed by device plugins.
177
func WithHostDevicesDevicePlugins(hostDevices []v1.HostDevice) ResourceRendererOption {
329✔
178
        return func(r *ResourceRenderer) {
333✔
179
                resources := r.ResourceRequirements()
4✔
180
                for _, hd := range hostDevices {
8✔
181
                        if hd.DeviceName != "" && hd.ClaimRequest == nil {
7✔
182
                                requestResource(&resources, hd.DeviceName)
3✔
183
                        }
3✔
184
                }
185
                copyResources(resources.Limits, r.calculatedLimits)
4✔
186
                copyResources(resources.Requests, r.calculatedRequests)
4✔
187
        }
188
}
189

190
// WithHostDevicesDRA adds ResourceClaims for HostDevices provisioned via DRA.
191
func WithHostDevicesDRA(hostDevices []v1.HostDevice) ResourceRendererOption {
330✔
192
        return func(r *ResourceRenderer) {
334✔
193
                for _, hd := range hostDevices {
10✔
194
                        if hd.DeviceName == "" && hd.ClaimRequest != nil {
11✔
195
                                claim := k8sv1.ResourceClaim{
5✔
196
                                        Name:    hd.ClaimRequest.ClaimName,
5✔
197
                                        Request: hd.ClaimRequest.RequestName,
5✔
198
                                }
5✔
199
                                r.resourceClaims = append(r.resourceClaims, claim)
5✔
200
                        }
5✔
201
                }
202
        }
203
}
204

205
// WithNetworksDRA adds ResourceClaims for Networks provisioned via DRA.
206
func WithNetworksDRA(networks []v1.Network) ResourceRendererOption {
328✔
207
        return func(r *ResourceRenderer) {
331✔
208
                for _, net := range networks {
7✔
209
                        if netvmispec.IsDRANetwork(net) {
7✔
210
                                claim := k8sv1.ResourceClaim{
3✔
211
                                        Name:    net.NetworkSource.ResourceClaim.ClaimName,
3✔
212
                                        Request: net.NetworkSource.ResourceClaim.RequestName,
3✔
213
                                }
3✔
214
                                r.resourceClaims = append(r.resourceClaims, claim)
3✔
215
                        }
3✔
216
                }
217
        }
218
}
219

220
func WithHugePages(vmMemory *v1.Memory, memoryOverhead resource.Quantity) ResourceRendererOption {
326✔
221
        return func(renderer *ResourceRenderer) {
348✔
222
                hugepageType := k8sv1.ResourceName(k8sv1.ResourceHugePagesPrefix + vmMemory.Hugepages.PageSize)
22✔
223
                hugepagesMemReq := renderer.vmRequests.Memory()
22✔
224

22✔
225
                // If requested, use the guest memory to allocate hugepages
22✔
226
                if vmMemory != nil && vmMemory.Guest != nil {
24✔
227
                        requests := hugepagesMemReq.Value()
2✔
228
                        guest := vmMemory.Guest.Value()
2✔
229
                        if requests > guest {
4✔
230
                                hugepagesMemReq = vmMemory.Guest
2✔
231
                        }
2✔
232
                }
233
                renderer.calculatedRequests[hugepageType] = *hugepagesMemReq
22✔
234
                renderer.calculatedLimits[hugepageType] = *hugepagesMemReq
22✔
235

22✔
236
                reqMemDiff := resource.NewScaledQuantity(0, resource.Kilo)
22✔
237
                limMemDiff := resource.NewScaledQuantity(0, resource.Kilo)
22✔
238
                // In case the guest memory and the requested memory are different, add the difference
22✔
239
                // to the overhead
22✔
240
                if vmMemory != nil && vmMemory.Guest != nil {
24✔
241
                        requests := renderer.vmRequests.Memory().Value()
2✔
242
                        limits := renderer.vmLimits.Memory().Value()
2✔
243
                        guest := vmMemory.Guest.Value()
2✔
244
                        if requests > guest {
4✔
245
                                reqMemDiff.Add(*renderer.vmRequests.Memory())
2✔
246
                                reqMemDiff.Sub(*vmMemory.Guest)
2✔
247
                        }
2✔
248
                        if limits > guest {
4✔
249
                                limMemDiff.Add(*renderer.vmLimits.Memory())
2✔
250
                                limMemDiff.Sub(*vmMemory.Guest)
2✔
251
                        }
2✔
252
                }
253
                // Set requested memory equals to overhead memory
254
                reqMemDiff.Add(memoryOverhead)
22✔
255
                renderer.vmRequests[k8sv1.ResourceMemory] = *reqMemDiff
22✔
256
                if _, ok := renderer.vmLimits[k8sv1.ResourceMemory]; ok {
36✔
257
                        limMemDiff.Add(memoryOverhead)
14✔
258
                        renderer.vmLimits[k8sv1.ResourceMemory] = *limMemDiff
14✔
259
                }
14✔
260
        }
261
}
262

263
func WithMemoryRequests(vmiSpecMemory *v1.Memory, overcommit int) ResourceRendererOption {
326✔
264
        return func(renderer *ResourceRenderer) {
491✔
265
                limit, hasLimit := renderer.vmLimits[k8sv1.ResourceMemory]
165✔
266
                request, hasRequest := renderer.vmRequests[k8sv1.ResourceMemory]
165✔
267
                if hasLimit && !limit.IsZero() && (!hasRequest || request.IsZero()) {
173✔
268
                        renderer.vmRequests[k8sv1.ResourceMemory] = limit
8✔
269
                }
8✔
270

271
                if _, exists := renderer.vmRequests[k8sv1.ResourceMemory]; exists {
173✔
272
                        return
8✔
273
                }
8✔
274

275
                var memory *resource.Quantity
157✔
276
                if vmiSpecMemory != nil && vmiSpecMemory.Guest != nil {
160✔
277
                        memory = vmiSpecMemory.Guest
3✔
278
                } else if vmiSpecMemory != nil && vmiSpecMemory.Hugepages != nil {
161✔
279
                        if hugepagesSize, err := resource.ParseQuantity(vmiSpecMemory.Hugepages.PageSize); err == nil {
8✔
280
                                memory = &hugepagesSize
4✔
281
                        }
4✔
282
                }
283

284
                if memory != nil && memory.Value() > 0 {
164✔
285
                        if overcommit == 100 {
11✔
286
                                renderer.vmRequests[k8sv1.ResourceMemory] = *memory
4✔
287
                        } else {
7✔
288
                                value := (memory.Value() * int64(100)) / int64(overcommit)
3✔
289
                                renderer.vmRequests[k8sv1.ResourceMemory] = *resource.NewQuantity(value, memory.Format)
3✔
290
                        }
3✔
291
                }
292
        }
293
}
294

295
func WithMemoryOverhead(guestResourceSpec v1.ResourceRequirements, memoryOverhead resource.Quantity) ResourceRendererOption {
328✔
296
        return func(renderer *ResourceRenderer) {
634✔
297
                memoryRequest := renderer.vmRequests[k8sv1.ResourceMemory]
306✔
298
                if !guestResourceSpec.OvercommitGuestOverhead {
609✔
299
                        memoryRequest.Add(memoryOverhead)
303✔
300
                }
303✔
301
                renderer.vmRequests[k8sv1.ResourceMemory] = memoryRequest
306✔
302

306✔
303
                if memoryLimit, ok := renderer.vmLimits[k8sv1.ResourceMemory]; ok {
323✔
304
                        memoryLimit.Add(memoryOverhead)
17✔
305
                        renderer.vmLimits[k8sv1.ResourceMemory] = memoryLimit
17✔
306
                }
17✔
307
        }
308
}
309

310
func WithAutoMemoryLimits(namespace string, namespaceStore cache.Store) ResourceRendererOption {
329✔
311
        return func(renderer *ResourceRenderer) {
336✔
312
                requestRatio := getMemoryLimitsRatio(namespace, namespaceStore)
7✔
313
                memoryRequest := renderer.vmRequests[k8sv1.ResourceMemory]
7✔
314
                value := int64(float64(memoryRequest.Value()) * requestRatio)
7✔
315
                renderer.calculatedLimits[k8sv1.ResourceMemory] = *resource.NewQuantity(value, memoryRequest.Format)
7✔
316
        }
7✔
317
}
318

319
func WithCPUPinning(vmi *v1.VirtualMachineInstance, annotations map[string]string, additionalCPUs uint32) ResourceRendererOption {
332✔
320
        return func(renderer *ResourceRenderer) {
345✔
321
                cpu := vmi.Spec.Domain.CPU
13✔
322
                vcpus := hardware.GetNumberOfVCPUs(cpu)
13✔
323
                ioThreadCPUs := getIOThreadsCount(vmi)
13✔
324
                if vcpus != 0 {
23✔
325
                        totalCPUs := vcpus + ioThreadCPUs
10✔
326
                        renderer.vmLimits[k8sv1.ResourceCPU] = *resource.NewQuantity(totalCPUs, resource.BinarySI)
10✔
327
                        renderer.vmRequests[k8sv1.ResourceCPU] = *resource.NewQuantity(totalCPUs, resource.BinarySI) // Ensure requests match limits for dedicated CPUs
10✔
328
                } else {
13✔
329
                        ioThreadsCount := resource.NewQuantity(ioThreadCPUs, resource.BinarySI)
3✔
330
                        if cpuLimit, ok := renderer.vmLimits[k8sv1.ResourceCPU]; ok {
5✔
331
                                cpuLimit.Add(*ioThreadsCount)
2✔
332
                                renderer.vmLimits[k8sv1.ResourceCPU] = cpuLimit
2✔
333
                        }
2✔
334
                        if cpuRequest, ok := renderer.vmRequests[k8sv1.ResourceCPU]; ok {
5✔
335
                                cpuRequest.Add(*ioThreadsCount)
2✔
336
                                renderer.vmRequests[k8sv1.ResourceCPU] = cpuRequest
2✔
337
                        }
2✔
338
                }
339

340
                if cpu.IsolateEmulatorThread {
24✔
341
                        emulatorThreadCPUs := resource.NewQuantity(1, resource.BinarySI)
11✔
342
                        limits := renderer.vmLimits[k8sv1.ResourceCPU]
11✔
343
                        _, emulatorThreadCompleteToEvenParityAnnotationExists := annotations[v1.EmulatorThreadCompleteToEvenParity]
11✔
344
                        if emulatorThreadCompleteToEvenParityAnnotationExists &&
11✔
345
                                (limits.Value()+int64(additionalCPUs))%2 == 0 {
13✔
346
                                emulatorThreadCPUs = resource.NewQuantity(2, resource.BinarySI)
2✔
347
                        }
2✔
348
                        limits.Add(*emulatorThreadCPUs)
11✔
349
                        renderer.vmLimits[k8sv1.ResourceCPU] = limits
11✔
350
                        if cpuRequest, ok := renderer.vmRequests[k8sv1.ResourceCPU]; ok {
22✔
351
                                cpuRequest.Add(*emulatorThreadCPUs)
11✔
352
                                renderer.vmRequests[k8sv1.ResourceCPU] = cpuRequest
11✔
353
                        }
11✔
354
                }
355

356
                // Align memory limits with requests for consistency
357
                if memRequest, ok := renderer.vmRequests[k8sv1.ResourceMemory]; ok {
15✔
358
                        renderer.vmLimits[k8sv1.ResourceMemory] = memRequest
2✔
359
                }
2✔
360
        }
361
}
362

363
func WithNetworkResources(networkToResourceMap map[string]string) ResourceRendererOption {
328✔
364
        return func(renderer *ResourceRenderer) {
330✔
365
                resources := renderer.ResourceRequirements()
2✔
366
                for _, resourceName := range networkToResourceMap {
4✔
367
                        if resourceName != "" {
4✔
368
                                requestResource(&resources, resourceName)
2✔
369
                        }
2✔
370
                }
371
                copyResources(resources.Limits, renderer.calculatedLimits)
2✔
372
                copyResources(resources.Requests, renderer.calculatedRequests)
2✔
373
        }
374
}
375

376
func WithSEV() ResourceRendererOption {
327✔
377
        return func(renderer *ResourceRenderer) {
334✔
378
                resources := renderer.ResourceRequirements()
7✔
379
                requestResource(&resources, SevDevice)
7✔
380
                copyResources(resources.Limits, renderer.calculatedLimits)
7✔
381
                copyResources(resources.Requests, renderer.calculatedRequests)
7✔
382
        }
7✔
383
}
384

385
func WithTDX() ResourceRendererOption {
327✔
386
        return func(renderer *ResourceRenderer) {
330✔
387
                resources := renderer.ResourceRequirements()
3✔
388
                requestResource(&resources, TdxDevice)
3✔
389
                copyResources(resources.Limits, renderer.calculatedLimits)
3✔
390
                copyResources(resources.Requests, renderer.calculatedRequests)
3✔
391
        }
3✔
392
}
393

394
func WithPersistentReservation() ResourceRendererOption {
326✔
395
        return func(renderer *ResourceRenderer) {
328✔
396
                resources := renderer.ResourceRequirements()
2✔
397
                requestResource(&resources, PrDevice)
2✔
398
                copyResources(resources.Limits, renderer.calculatedLimits)
2✔
399
                copyResources(resources.Requests, renderer.calculatedRequests)
2✔
400
        }
2✔
401
}
402

403
func WithIOMMUFD() ResourceRendererOption {
327✔
404
        return func(renderer *ResourceRenderer) {
329✔
405
                resources := renderer.ResourceRequirements()
2✔
406
                requestResource(&resources, IOMMUFDDevice)
2✔
407
                copyResources(resources.Limits, renderer.calculatedLimits)
2✔
408
                copyResources(resources.Requests, renderer.calculatedRequests)
2✔
409
        }
2✔
410
}
411

412
func copyResources(srcResources, dstResources k8sv1.ResourceList) {
2,624✔
413
        for key, value := range srcResources {
5,584✔
414
                dstResources[key] = value
2,960✔
415
        }
2,960✔
416
}
417

418
// Request a resource by name. This function bumps the number of resources,
419
// both its limits and requests attributes.
420
//
421
// If we were operating with a regular resource (CPU, memory, network
422
// bandwidth), we would need to take care of QoS. For example,
423
// https://kubernetes.io/docs/tasks/configure-pod-container/quality-service-pod/#create-a-pod-that-gets-assigned-a-qos-class-of-guaranteed
424
// explains that when Limits are set but Requests are not then scheduler
425
// assumes that Requests are the same as Limits for a particular resource.
426
//
427
// But this function is not called for this standard resources but for
428
// resources managed by device plugins. The device plugin design document says
429
// the following on the matter:
430
// https://github.com/kubernetes/community/blob/master/contributors/design-proposals/resource-management/device-plugin.md#end-user-story
431
//
432
// ```
433
// Devices can be selected using the same process as for OIRs in the pod spec.
434
// Devices have no impact on QOS. However, for the alpha, we expect the request
435
// to have limits == requests.
436
// ```
437
//
438
// Which suggests that, for resources managed by device plugins, 1) limits
439
// should be equal to requests; and 2) QoS rules do not apVFIO//
440
// Hence we don't copy Limits value to Requests if the latter is missing.
441
func requestResource(resources *k8sv1.ResourceRequirements, resourceName string) {
27✔
442
        name := k8sv1.ResourceName(resourceName)
27✔
443
        bumpResources(resources.Limits, name)
27✔
444
        bumpResources(resources.Requests, name)
27✔
445
}
27✔
446

447
func bumpResources(resources k8sv1.ResourceList, name k8sv1.ResourceName) {
54✔
448
        unitQuantity := *resource.NewQuantity(1, resource.DecimalSI)
54✔
449

54✔
450
        val, ok := resources[name]
54✔
451
        if ok {
62✔
452
                val.Add(unitQuantity)
8✔
453
                resources[name] = val
8✔
454
        } else {
54✔
455
                resources[name] = unitQuantity
46✔
456
        }
46✔
457
}
458

459
func calcVCPUs(cpu *v1.CPU) int64 {
323✔
460
        if cpu != nil {
359✔
461
                return hardware.GetNumberOfVCPUs(cpu)
36✔
462
        }
36✔
463
        return int64(1)
287✔
464
}
465

466
func getRequiredResources(vmi *v1.VirtualMachineInstance, hypervisorResource k8sv1.ResourceName, allowEmulation bool) k8sv1.ResourceList {
326✔
467
        res := k8sv1.ResourceList{}
326✔
468
        if netvmispec.RequiresTunDevice(vmi) {
651✔
469
                res[TunDevice] = resource.MustParse("1")
325✔
470
        }
325✔
471
        if netvmispec.RequiresVirtioNetDevice(vmi, allowEmulation) {
341✔
472
                // Note that about network interface, allowEmulation does not make
15✔
473
                // any difference on eventual Domain xml, but uniformly making
15✔
474
                // /dev/vhost-net unavailable and libvirt implicitly fallback
15✔
475
                // to use QEMU userland NIC emulation.
15✔
476
                res[VhostNetDevice] = resource.MustParse("1")
15✔
477
        }
15✔
478
        if !allowEmulation {
651✔
479
                res[hypervisorResource] = resource.MustParse("1")
325✔
480
        }
325✔
481
        if util.IsAutoAttachVSOCK(vmi) {
327✔
482
                res[VhostVsockDevice] = resource.MustParse("1")
1✔
483
        }
1✔
484
        return res
326✔
485
}
486

487
func WithVirtualizationResources(virtResources k8sv1.ResourceList) ResourceRendererOption {
326✔
488
        return func(renderer *ResourceRenderer) {
652✔
489
                copyResources(virtResources, renderer.vmLimits)
326✔
490
        }
326✔
491
}
492

493
func validatePermittedHostDevices(spec *v1.VirtualMachineInstanceSpec, config *virtconfig.ClusterConfig) error {
335✔
494
        errors := make([]string, 0)
335✔
495

335✔
496
        if hostDevs := config.GetPermittedHostDevices(); hostDevs != nil {
344✔
497
                // build a map of all permitted host devices
9✔
498
                supportedHostDevicesMap := make(map[string]bool)
9✔
499
                for _, dev := range hostDevs.PciHostDevices {
18✔
500
                        supportedHostDevicesMap[dev.ResourceName] = true
9✔
501
                }
9✔
502
                for _, dev := range hostDevs.MediatedDevices {
9✔
503
                        supportedHostDevicesMap[dev.ResourceName] = true
×
504
                }
×
505
                for _, dev := range hostDevs.USB {
9✔
506
                        supportedHostDevicesMap[dev.ResourceName] = true
×
507
                }
×
508
                errors = append(errors, validateGPUs(spec.Domain.Devices.GPUs, config.GPUsWithDRAGateEnabled(), supportedHostDevicesMap)...)
9✔
509
                errors = append(errors, validateHostDevices(spec.Domain.Devices.HostDevices, config.HostDevicesWithDRAEnabled(), supportedHostDevicesMap)...)
9✔
510
        }
511

512
        if len(errors) != 0 {
339✔
513
                return fmt.Errorf("%s", strings.Join(errors, " "))
4✔
514
        }
4✔
515

516
        return nil
331✔
517
}
518

519
func validateGPUs(gpus []v1.GPU, draEnabled bool, supportedHostDevicesMap map[string]bool) (errors []string) {
9✔
520
        for _, hostDev := range gpus {
14✔
521
                // skip GPU devices backed by DRA claims, since they are validated via DRA instead of the permittedHostDevices config
5✔
522
                if draEnabled && hostDev.ClaimRequest != nil {
6✔
523
                        continue
1✔
524
                }
525
                if _, exist := supportedHostDevicesMap[hostDev.DeviceName]; !exist {
6✔
526
                        errors = append(errors, fmt.Sprintf("GPU %s is not permitted in permittedHostDevices configuration", hostDev.DeviceName))
2✔
527
                }
2✔
528
        }
529
        return errors
9✔
530
}
531

532
func validateHostDevices(hostDevs []v1.HostDevice, draEnabled bool, supportedHostDevicesMap map[string]bool) (errors []string) {
9✔
533
        for _, hostDev := range hostDevs {
14✔
534
                // skip host devices backed by DRA claims, since they are validated via DRA instead of the permittedHostDevices config
5✔
535
                if draEnabled && hostDev.ClaimRequest != nil {
6✔
536
                        continue
1✔
537
                }
538
                if _, exist := supportedHostDevicesMap[hostDev.DeviceName]; !exist {
6✔
539
                        errors = append(errors, fmt.Sprintf("HostDevice %s is not permitted in permittedHostDevices configuration", hostDev.DeviceName))
2✔
540
                }
2✔
541
        }
542
        return errors
9✔
543
}
544

545
func sidecarResources(vmi *v1.VirtualMachineInstance, config *virtconfig.ClusterConfig) k8sv1.ResourceRequirements {
21✔
546
        resources := k8sv1.ResourceRequirements{
21✔
547
                Requests: k8sv1.ResourceList{},
21✔
548
                Limits:   k8sv1.ResourceList{},
21✔
549
        }
21✔
550
        if reqCpu := config.GetSupportContainerRequest(v1.SideCar, k8sv1.ResourceCPU); reqCpu != nil {
23✔
551
                resources.Requests[k8sv1.ResourceCPU] = *reqCpu
2✔
552
        }
2✔
553
        if reqMem := config.GetSupportContainerRequest(v1.SideCar, k8sv1.ResourceMemory); reqMem != nil {
23✔
554
                resources.Requests[k8sv1.ResourceMemory] = *reqMem
2✔
555
        }
2✔
556

557
        // add default cpu and memory limits to enable cpu pinning if requested
558
        // TODO(vladikr): make the hookSidecar express resources
559
        if vmi.IsCPUDedicated() || vmi.WantsToHaveQOSGuaranteed() {
25✔
560
                resources.Limits[k8sv1.ResourceCPU] = resource.MustParse("200m")
4✔
561
                if limCpu := config.GetSupportContainerLimit(v1.SideCar, k8sv1.ResourceCPU); limCpu != nil {
5✔
562
                        resources.Limits[k8sv1.ResourceCPU] = *limCpu
1✔
563
                }
1✔
564
                resources.Limits[k8sv1.ResourceMemory] = resource.MustParse("64M")
4✔
565
                if limMem := config.GetSupportContainerLimit(v1.SideCar, k8sv1.ResourceMemory); limMem != nil {
5✔
566
                        resources.Limits[k8sv1.ResourceMemory] = *limMem
1✔
567
                }
1✔
568
                resources.Requests[k8sv1.ResourceCPU] = resources.Limits[k8sv1.ResourceCPU]
4✔
569
                resources.Requests[k8sv1.ResourceMemory] = resources.Limits[k8sv1.ResourceMemory]
4✔
570
        } else {
17✔
571
                if limCpu := config.GetSupportContainerLimit(v1.SideCar, k8sv1.ResourceCPU); limCpu != nil {
18✔
572
                        resources.Limits[k8sv1.ResourceCPU] = *limCpu
1✔
573
                }
1✔
574
                if limMem := config.GetSupportContainerLimit(v1.SideCar, k8sv1.ResourceMemory); limMem != nil {
18✔
575
                        resources.Limits[k8sv1.ResourceMemory] = *limMem
1✔
576
                }
1✔
577
        }
578
        return resources
21✔
579
}
580

581
func initContainerResourceRequirementsForVMI(vmi *v1.VirtualMachineInstance, containerType v1.SupportContainerType, config *virtconfig.ClusterConfig) k8sv1.ResourceRequirements {
5✔
582
        if vmi.IsCPUDedicated() || vmi.WantsToHaveQOSGuaranteed() {
5✔
UNCOV
583
                return k8sv1.ResourceRequirements{
×
UNCOV
584
                        Limits:   initContainerDedicatedCPURequiredResources(containerType, config),
×
UNCOV
585
                        Requests: initContainerDedicatedCPURequiredResources(containerType, config),
×
UNCOV
586
                }
×
587
        } else {
5✔
588
                return k8sv1.ResourceRequirements{
5✔
589
                        Limits:   initContainerMinimalLimits(containerType, config),
5✔
590
                        Requests: initContainerMinimalRequests(containerType, config),
5✔
591
                }
5✔
592
        }
5✔
593
}
594

595
func initContainerDedicatedCPURequiredResources(containerType v1.SupportContainerType, config *virtconfig.ClusterConfig) k8sv1.ResourceList {
×
596
        res := k8sv1.ResourceList{
×
UNCOV
597
                k8sv1.ResourceCPU:    resource.MustParse("10m"),
×
UNCOV
598
                k8sv1.ResourceMemory: resource.MustParse("40M"),
×
UNCOV
599
        }
×
UNCOV
600
        if cpuLim := config.GetSupportContainerLimit(containerType, k8sv1.ResourceCPU); cpuLim != nil {
×
UNCOV
601
                res[k8sv1.ResourceCPU] = *cpuLim
×
UNCOV
602
        }
×
UNCOV
603
        if memLim := config.GetSupportContainerLimit(containerType, k8sv1.ResourceMemory); memLim != nil {
×
UNCOV
604
                res[k8sv1.ResourceMemory] = *memLim
×
605
        }
×
606
        return res
×
607
}
608

609
func initContainerMinimalLimits(containerType v1.SupportContainerType, config *virtconfig.ClusterConfig) k8sv1.ResourceList {
5✔
610
        res := k8sv1.ResourceList{
5✔
611
                k8sv1.ResourceCPU:    resource.MustParse("100m"),
5✔
612
                k8sv1.ResourceMemory: resource.MustParse("40M"),
5✔
613
        }
5✔
614
        if cpuLim := config.GetSupportContainerLimit(containerType, k8sv1.ResourceCPU); cpuLim != nil {
5✔
615
                res[k8sv1.ResourceCPU] = *cpuLim
×
616
        }
×
617
        if memLim := config.GetSupportContainerLimit(containerType, k8sv1.ResourceMemory); memLim != nil {
5✔
UNCOV
618
                res[k8sv1.ResourceMemory] = *memLim
×
UNCOV
619
        }
×
620
        return res
5✔
621
}
622

623
func initContainerMinimalRequests(containerType v1.SupportContainerType, config *virtconfig.ClusterConfig) k8sv1.ResourceList {
5✔
624
        res := k8sv1.ResourceList{
5✔
625
                k8sv1.ResourceCPU:    resource.MustParse("10m"),
5✔
626
                k8sv1.ResourceMemory: resource.MustParse("1M"),
5✔
627
        }
5✔
628
        if cpuReq := config.GetSupportContainerRequest(containerType, k8sv1.ResourceCPU); cpuReq != nil {
5✔
629
                res[k8sv1.ResourceCPU] = *cpuReq
×
UNCOV
630
        }
×
631
        if memReq := config.GetSupportContainerRequest(containerType, k8sv1.ResourceMemory); memReq != nil {
5✔
UNCOV
632
                res[k8sv1.ResourceMemory] = *memReq
×
UNCOV
633
        }
×
634
        return res
5✔
635
}
636

637
func hotplugContainerResourceRequirementsForVMI(config *virtconfig.ClusterConfig) k8sv1.ResourceRequirements {
21✔
638
        return k8sv1.ResourceRequirements{
21✔
639
                Limits:   hotplugContainerLimits(config),
21✔
640
                Requests: hotplugContainerRequests(config),
21✔
641
        }
21✔
642
}
21✔
643

644
func hotplugContainerLimits(config *virtconfig.ClusterConfig) k8sv1.ResourceList {
21✔
645
        cpuQuantity := resource.MustParse("100m")
21✔
646
        if cpu := config.GetSupportContainerLimit(v1.HotplugAttachment, k8sv1.ResourceCPU); cpu != nil {
26✔
647
                cpuQuantity = *cpu
5✔
648
        }
5✔
649
        memQuantity := resource.MustParse("80M")
21✔
650
        if mem := config.GetSupportContainerLimit(v1.HotplugAttachment, k8sv1.ResourceMemory); mem != nil {
26✔
651
                memQuantity = *mem
5✔
652
        }
5✔
653
        return k8sv1.ResourceList{
21✔
654
                k8sv1.ResourceCPU:    cpuQuantity,
21✔
655
                k8sv1.ResourceMemory: memQuantity,
21✔
656
        }
21✔
657
}
658

659
func hotplugContainerRequests(config *virtconfig.ClusterConfig) k8sv1.ResourceList {
21✔
660
        cpuQuantity := resource.MustParse("10m")
21✔
661
        if cpu := config.GetSupportContainerRequest(v1.HotplugAttachment, k8sv1.ResourceCPU); cpu != nil {
26✔
662
                cpuQuantity = *cpu
5✔
663
        }
5✔
664
        memQuantity := resource.MustParse("2M")
21✔
665
        if mem := config.GetSupportContainerRequest(v1.HotplugAttachment, k8sv1.ResourceMemory); mem != nil {
26✔
666
                memQuantity = *mem
5✔
667
        }
5✔
668
        return k8sv1.ResourceList{
21✔
669
                k8sv1.ResourceCPU:    cpuQuantity,
21✔
670
                k8sv1.ResourceMemory: memQuantity,
21✔
671
        }
21✔
672
}
673

674
func hotplugPodTolerations() []k8sv1.Toleration {
9✔
675
        return []k8sv1.Toleration{
9✔
676
                {
9✔
677
                        Key:      k8sv1.TaintNodeUnschedulable,
9✔
678
                        Operator: k8sv1.TolerationOpExists,
9✔
679
                        Effect:   k8sv1.TaintEffectNoSchedule,
9✔
680
                },
9✔
681
                {
9✔
682
                        Key:      k8sv1.TaintNodeNetworkUnavailable,
9✔
683
                        Operator: k8sv1.TolerationOpExists,
9✔
684
                        Effect:   k8sv1.TaintEffectNoSchedule,
9✔
685
                },
9✔
686
                {
9✔
687
                        Key:      k8sv1.TaintNodeDiskPressure,
9✔
688
                        Operator: k8sv1.TolerationOpExists,
9✔
689
                        Effect:   k8sv1.TaintEffectNoSchedule,
9✔
690
                },
9✔
691
                {
9✔
692
                        Key:      k8sv1.TaintNodeMemoryPressure,
9✔
693
                        Operator: k8sv1.TolerationOpExists,
9✔
694
                        Effect:   k8sv1.TaintEffectNoSchedule,
9✔
695
                },
9✔
696
                {
9✔
697
                        Key:      k8sv1.TaintNodePIDPressure,
9✔
698
                        Operator: k8sv1.TolerationOpExists,
9✔
699
                        Effect:   k8sv1.TaintEffectNoSchedule,
9✔
700
                },
9✔
701
        }
9✔
702
}
9✔
703

704
func vmExportContainerResourceRequirements(config *virtconfig.ClusterConfig) k8sv1.ResourceRequirements {
33✔
705
        return k8sv1.ResourceRequirements{
33✔
706
                Limits:   vmExportContainerLimits(config),
33✔
707
                Requests: vmExportContainerRequests(config),
33✔
708
        }
33✔
709
}
33✔
710

711
func vmExportContainerLimits(config *virtconfig.ClusterConfig) k8sv1.ResourceList {
33✔
712
        cpuQuantity := resource.MustParse("1")
33✔
713
        if cpu := config.GetSupportContainerLimit(v1.VMExport, k8sv1.ResourceCPU); cpu != nil {
33✔
UNCOV
714
                cpuQuantity = *cpu
×
UNCOV
715
        }
×
716
        memQuantity := resource.MustParse("1024Mi")
33✔
717
        if mem := config.GetSupportContainerLimit(v1.VMExport, k8sv1.ResourceMemory); mem != nil {
33✔
UNCOV
718
                memQuantity = *mem
×
UNCOV
719
        }
×
720
        return k8sv1.ResourceList{
33✔
721
                k8sv1.ResourceCPU:    cpuQuantity,
33✔
722
                k8sv1.ResourceMemory: memQuantity,
33✔
723
        }
33✔
724
}
725

726
func vmExportContainerRequests(config *virtconfig.ClusterConfig) k8sv1.ResourceList {
33✔
727
        cpuQuantity := resource.MustParse("100m")
33✔
728
        if cpu := config.GetSupportContainerRequest(v1.VMExport, k8sv1.ResourceCPU); cpu != nil {
33✔
729
                cpuQuantity = *cpu
×
UNCOV
730
        }
×
731
        memQuantity := resource.MustParse("200Mi")
33✔
732
        if mem := config.GetSupportContainerRequest(v1.VMExport, k8sv1.ResourceMemory); mem != nil {
33✔
UNCOV
733
                memQuantity = *mem
×
UNCOV
734
        }
×
735
        return k8sv1.ResourceList{
33✔
736
                k8sv1.ResourceCPU:    cpuQuantity,
33✔
737
                k8sv1.ResourceMemory: memQuantity,
33✔
738
        }
33✔
739
}
740

741
func getMemoryLimitsRatio(namespace string, namespaceStore cache.Store) float64 {
7✔
742
        if namespaceStore == nil {
7✔
743
                return DefaultMemoryLimitOverheadRatio
×
744
        }
×
745

746
        obj, exists, err := namespaceStore.GetByKey(namespace)
7✔
747
        if err != nil {
7✔
UNCOV
748
                log.Log.Warningf("Error retrieving namespace from informer. Using the default memory limits ratio. %s", err.Error())
×
UNCOV
749
                return DefaultMemoryLimitOverheadRatio
×
750
        } else if !exists {
10✔
751
                log.Log.Warningf("namespace %s does not exist. Using the default memory limits ratio.", namespace)
3✔
752
                return DefaultMemoryLimitOverheadRatio
3✔
753
        }
3✔
754

755
        ns, ok := obj.(*k8sv1.Namespace)
4✔
756
        if !ok {
4✔
UNCOV
757
                log.Log.Errorf("couldn't cast object to Namespace: %+v", obj)
×
758
                return DefaultMemoryLimitOverheadRatio
×
759
        }
×
760

761
        value, ok := ns.GetLabels()[v1.AutoMemoryLimitsRatioLabel]
4✔
762
        if !ok {
4✔
UNCOV
763
                return DefaultMemoryLimitOverheadRatio
×
UNCOV
764
        }
×
765

766
        limitRatioValue, err := strconv.ParseFloat(value, 64)
4✔
767
        if err != nil || limitRatioValue < 1.0 {
5✔
768
                log.Log.Warningf("%s is an invalid value for %s label in namespace %s. Using the default one: %f", value, v1.AutoMemoryLimitsRatioLabel, namespace, DefaultMemoryLimitOverheadRatio)
1✔
769
                return DefaultMemoryLimitOverheadRatio
1✔
770
        }
1✔
771

772
        return limitRatioValue
3✔
773
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc