Skip to content

Commit d65be5e

Browse files
authored
Merge pull request #44 from cobaltcore-dev/fix-spread-numa-cell-allocation
Fix cell allocation with spread instances
2 parents 34d9316 + a61529d commit d65be5e

2 files changed

Lines changed: 231 additions & 2 deletions

File tree

internal/libvirt/libvirt.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,12 @@ func (l *LibVirt) addAllocationCapacity(old v1.Hypervisor) (v1.Hypervisor, error
488488
)
489489
}
490490
memAllocCell := cell.Allocation["memory"]
491-
memAllocCell.Add(memAlloc)
491+
// If a domain is using multiple memory cells, assume the
492+
// distribution across cells is even.
493+
nCells := int64(len(domInfo.NumaTune.MemNodes)) // is non-zero
494+
memAllocPerCell := *resource.
495+
NewQuantity(memAlloc.Value()/nCells, resource.BinarySI)
496+
memAllocCell.Add(memAllocPerCell)
492497
cell.Allocation["memory"] = memAllocCell
493498
cellsById[memoryNode.CellID] = cell
494499
}
@@ -506,7 +511,12 @@ func (l *LibVirt) addAllocationCapacity(old v1.Hypervisor) (v1.Hypervisor, error
506511
)
507512
}
508513
cpuAllocCell := cell.Allocation["cpu"]
509-
cpuAllocCell.Add(cpuAlloc)
514+
// If a domain is using multiple cpu cells, assume the distribution
515+
// across cells is even.
516+
nCells := int64(len(domInfo.CPU.Numa.Cells)) // is non-zero
517+
cpuAllocPerCell := *resource.
518+
NewQuantity(cpuAlloc.Value()/nCells, resource.DecimalSI)
519+
cpuAllocCell.Add(cpuAllocPerCell)
510520
cell.Allocation["cpu"] = cpuAllocCell
511521
cellsById[cpuCell.ID] = cell
512522
}

internal/libvirt/libvirt_test.go

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,225 @@ func TestAddDomainCapabilities_UnsupportedFiltered(t *testing.T) {
395395
}
396396
}
397397

398+
func TestAddAllocationCapacity_InstanceSpreadAcrossMultipleNumaCells(t *testing.T) {
399+
// Test scenario: An instance with 128 GiB memory and 16 CPUs is spread
400+
// across NUMA cells 1 and 2. Each cell should report 64 GiB memory
401+
// allocation and 8 CPU allocation.
402+
caps := capabilities.Capabilities{
403+
Host: capabilities.CapabilitiesHost{
404+
Topology: capabilities.CapabilitiesHostTopology{
405+
CellSpec: capabilities.CapabilitiesHostTopologyCells{
406+
Num: 4,
407+
Cells: []capabilities.CapabilitiesHostTopologyCell{
408+
{
409+
ID: 0,
410+
Memory: capabilities.CapabilitiesHostTopologyCellMemory{
411+
Unit: "GiB",
412+
Value: 256,
413+
},
414+
CPUs: capabilities.CapabilitiesHostTopologyCellCPUs{
415+
Num: 64,
416+
},
417+
},
418+
{
419+
ID: 1,
420+
Memory: capabilities.CapabilitiesHostTopologyCellMemory{
421+
Unit: "GiB",
422+
Value: 256,
423+
},
424+
CPUs: capabilities.CapabilitiesHostTopologyCellCPUs{
425+
Num: 64,
426+
},
427+
},
428+
{
429+
ID: 2,
430+
Memory: capabilities.CapabilitiesHostTopologyCellMemory{
431+
Unit: "GiB",
432+
Value: 256,
433+
},
434+
CPUs: capabilities.CapabilitiesHostTopologyCellCPUs{
435+
Num: 64,
436+
},
437+
},
438+
{
439+
ID: 3,
440+
Memory: capabilities.CapabilitiesHostTopologyCellMemory{
441+
Unit: "GiB",
442+
Value: 256,
443+
},
444+
CPUs: capabilities.CapabilitiesHostTopologyCellCPUs{
445+
Num: 64,
446+
},
447+
},
448+
},
449+
},
450+
},
451+
},
452+
}
453+
454+
domInfos := []dominfo.DomainInfo{
455+
{
456+
Name: "multi-numa-instance",
457+
Memory: &dominfo.DomainMemory{
458+
Unit: "GiB",
459+
Value: 128,
460+
},
461+
CPUTune: &dominfo.DomainCPUTune{
462+
VCPUPins: []dominfo.DomainVCPUPin{
463+
{VCPU: 0, CPUSet: "64"},
464+
{VCPU: 1, CPUSet: "65"},
465+
{VCPU: 2, CPUSet: "66"},
466+
{VCPU: 3, CPUSet: "67"},
467+
{VCPU: 4, CPUSet: "68"},
468+
{VCPU: 5, CPUSet: "69"},
469+
{VCPU: 6, CPUSet: "70"},
470+
{VCPU: 7, CPUSet: "71"},
471+
{VCPU: 8, CPUSet: "128"},
472+
{VCPU: 9, CPUSet: "129"},
473+
{VCPU: 10, CPUSet: "130"},
474+
{VCPU: 11, CPUSet: "131"},
475+
{VCPU: 12, CPUSet: "132"},
476+
{VCPU: 13, CPUSet: "133"},
477+
{VCPU: 14, CPUSet: "134"},
478+
{VCPU: 15, CPUSet: "135"},
479+
},
480+
},
481+
// Instance uses NUMA cells 1 and 2 for memory
482+
NumaTune: &dominfo.DomainNumaTune{
483+
MemNodes: []dominfo.DomainNumaMemNode{
484+
{CellID: 1, Mode: "strict", Nodeset: "1"},
485+
{CellID: 2, Mode: "strict", Nodeset: "2"},
486+
},
487+
},
488+
// Instance uses NUMA cells 1 and 2 for CPUs
489+
CPU: &dominfo.DomainCPU{
490+
Numa: &dominfo.DomainCPUNuma{
491+
Cells: []dominfo.DomainCPUNumaCell{
492+
{ID: 1, CPUs: "0-7", Memory: 64, Unit: "GiB"},
493+
{ID: 2, CPUs: "8-15", Memory: 64, Unit: "GiB"},
494+
},
495+
},
496+
},
497+
},
498+
}
499+
500+
l := &LibVirt{
501+
capabilitiesClient: &mockCapabilitiesClient{caps: caps},
502+
domainInfoClient: &mockDomInfoClient{infos: domInfos},
503+
}
504+
505+
hv := v1.Hypervisor{}
506+
result, err := l.addAllocationCapacity(hv)
507+
508+
if err != nil {
509+
t.Fatalf("addAllocationCapacity() returned unexpected error: %v", err)
510+
}
511+
512+
// Check total capacity (4 cells * 256 GiB = 1024 GiB)
513+
expectedTotalMemCapacity := resource.NewQuantity(1024*1024*1024*1024, resource.BinarySI)
514+
memCap := result.Status.Capacity["memory"]
515+
if !memCap.Equal(*expectedTotalMemCapacity) {
516+
t.Errorf("Expected total memory capacity %s, got %s",
517+
expectedTotalMemCapacity.String(), memCap.String())
518+
}
519+
520+
// Check total CPU capacity (4 cells * 64 = 256)
521+
expectedTotalCpuCapacity := resource.NewQuantity(256, resource.DecimalSI)
522+
cpuCap := result.Status.Capacity["cpu"]
523+
if !cpuCap.Equal(*expectedTotalCpuCapacity) {
524+
t.Errorf("Expected total CPU capacity %s, got %s",
525+
expectedTotalCpuCapacity.String(), cpuCap.String())
526+
}
527+
528+
// Check total allocation (128 GiB memory, 16 CPUs)
529+
expectedTotalMemAlloc := resource.NewQuantity(128*1024*1024*1024, resource.BinarySI)
530+
memAlloc := result.Status.Allocation["memory"]
531+
if !memAlloc.Equal(*expectedTotalMemAlloc) {
532+
t.Errorf("Expected total memory allocation %s, got %s",
533+
expectedTotalMemAlloc.String(), memAlloc.String())
534+
}
535+
536+
expectedTotalCpuAlloc := resource.NewQuantity(16, resource.DecimalSI)
537+
cpuAlloc := result.Status.Allocation["cpu"]
538+
if !cpuAlloc.Equal(*expectedTotalCpuAlloc) {
539+
t.Errorf("Expected total CPU allocation %s, got %s",
540+
expectedTotalCpuAlloc.String(), cpuAlloc.String())
541+
}
542+
543+
// Check that we have 4 cells
544+
if len(result.Status.Cells) != 4 {
545+
t.Fatalf("Expected 4 cells, got %d", len(result.Status.Cells))
546+
}
547+
548+
// Create a map for easier cell lookup
549+
cellsByID := make(map[uint64]v1.Cell)
550+
for _, cell := range result.Status.Cells {
551+
cellsByID[cell.CellID] = cell
552+
}
553+
554+
// Cell 0 should have zero allocation (not used by the instance)
555+
cell0 := cellsByID[0]
556+
expectedCell0MemAlloc := resource.NewQuantity(0, resource.BinarySI)
557+
cell0MemAlloc := cell0.Allocation["memory"]
558+
if !cell0MemAlloc.Equal(*expectedCell0MemAlloc) {
559+
t.Errorf("Cell 0: Expected memory allocation %s, got %s",
560+
expectedCell0MemAlloc.String(), cell0MemAlloc.String())
561+
}
562+
expectedCell0CpuAlloc := resource.NewQuantity(0, resource.DecimalSI)
563+
cell0CpuAlloc := cell0.Allocation["cpu"]
564+
if !cell0CpuAlloc.Equal(*expectedCell0CpuAlloc) {
565+
t.Errorf("Cell 0: Expected CPU allocation %s, got %s",
566+
expectedCell0CpuAlloc.String(), cell0CpuAlloc.String())
567+
}
568+
569+
// Cell 1 should have 64 GiB memory and 8 CPUs allocated
570+
// (128 GiB / 2 cells = 64 GiB per cell, 16 CPUs / 2 cells = 8 CPUs per cell)
571+
cell1 := cellsByID[1]
572+
expectedCell1MemAlloc := resource.NewQuantity(64*1024*1024*1024, resource.BinarySI)
573+
cell1MemAlloc := cell1.Allocation["memory"]
574+
if !cell1MemAlloc.Equal(*expectedCell1MemAlloc) {
575+
t.Errorf("Cell 1: Expected memory allocation %s, got %s",
576+
expectedCell1MemAlloc.String(), cell1MemAlloc.String())
577+
}
578+
expectedCell1CpuAlloc := resource.NewQuantity(8, resource.DecimalSI)
579+
cell1CpuAlloc := cell1.Allocation["cpu"]
580+
if !cell1CpuAlloc.Equal(*expectedCell1CpuAlloc) {
581+
t.Errorf("Cell 1: Expected CPU allocation %s, got %s",
582+
expectedCell1CpuAlloc.String(), cell1CpuAlloc.String())
583+
}
584+
585+
// Cell 2 should have 64 GiB memory and 8 CPUs allocated
586+
// (128 GiB / 2 cells = 64 GiB per cell, 16 CPUs / 2 cells = 8 CPUs per cell)
587+
cell2 := cellsByID[2]
588+
expectedCell2MemAlloc := resource.NewQuantity(64*1024*1024*1024, resource.BinarySI)
589+
cell2MemAlloc := cell2.Allocation["memory"]
590+
if !cell2MemAlloc.Equal(*expectedCell2MemAlloc) {
591+
t.Errorf("Cell 2: Expected memory allocation %s, got %s",
592+
expectedCell2MemAlloc.String(), cell2MemAlloc.String())
593+
}
594+
expectedCell2CpuAlloc := resource.NewQuantity(8, resource.DecimalSI)
595+
cell2CpuAlloc := cell2.Allocation["cpu"]
596+
if !cell2CpuAlloc.Equal(*expectedCell2CpuAlloc) {
597+
t.Errorf("Cell 2: Expected CPU allocation %s, got %s",
598+
expectedCell2CpuAlloc.String(), cell2CpuAlloc.String())
599+
}
600+
601+
// Cell 3 should have zero allocation (not used by the instance)
602+
cell3 := cellsByID[3]
603+
expectedCell3MemAlloc := resource.NewQuantity(0, resource.BinarySI)
604+
cell3MemAlloc := cell3.Allocation["memory"]
605+
if !cell3MemAlloc.Equal(*expectedCell3MemAlloc) {
606+
t.Errorf("Cell 3: Expected memory allocation %s, got %s",
607+
expectedCell3MemAlloc.String(), cell3MemAlloc.String())
608+
}
609+
expectedCell3CpuAlloc := resource.NewQuantity(0, resource.DecimalSI)
610+
cell3CpuAlloc := cell3.Allocation["cpu"]
611+
if !cell3CpuAlloc.Equal(*expectedCell3CpuAlloc) {
612+
t.Errorf("Cell 3: Expected CPU allocation %s, got %s",
613+
expectedCell3CpuAlloc.String(), cell3CpuAlloc.String())
614+
}
615+
}
616+
398617
func TestAddAllocationCapacity_Success(t *testing.T) {
399618
caps := capabilities.Capabilities{
400619
Host: capabilities.CapabilitiesHost{

0 commit comments

Comments
 (0)