Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions pkg/scheduler/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -646,11 +646,26 @@ func (s *Scheduler) getNodesUsage(nodes *[]string, task *corev1.Pod) (*map[strin
d.Device.Health = false
continue
}
tmpIdx, Instance, _ := device.ExtractMigTemplatesFromUUID(udevice.UUID)
tmpIdx, instanceIdx, err := device.ExtractMigTemplatesFromUUID(udevice.UUID)
if err != nil {
klog.Errorf("failed to extract mig templates from uuid %s: %v", udevice.UUID, err)
continue
}
if tmpIdx < 0 || tmpIdx >= len(d.Device.MigTemplate) {
klog.Errorf("invalid mig template index %d in uuid %s (templates length: %d)", tmpIdx, udevice.UUID, len(d.Device.MigTemplate))
continue
}
if len(d.Device.MigUsage.UsageList) == 0 {
device.PlatternMIG(&d.Device.MigUsage, d.Device.MigTemplate, tmpIdx)
} else if tmpIdx != int(d.Device.MigUsage.Index) {
klog.Errorf("mig template index mismatch in uuid %s: expected %d, got %d", udevice.UUID, d.Device.MigUsage.Index, tmpIdx)
continue
}
Comment thread
mesutoezdil marked this conversation as resolved.
if instanceIdx < 0 || instanceIdx >= len(d.Device.MigUsage.UsageList) {
klog.Errorf("invalid mig instance in uuid %s", udevice.UUID)
continue
}
d.Device.MigUsage.UsageList[Instance].InUse = true
d.Device.MigUsage.UsageList[instanceIdx].InUse = true
klog.V(5).Infoln("add mig usage", d.Device.MigUsage, "template=", d.Device.MigTemplate, "uuid=", d.Device.ID)
}
}
Expand Down
225 changes: 225 additions & 0 deletions pkg/scheduler/scheduler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,231 @@ func Test_getNodesUsage(t *testing.T) {
assert.Equal(t, v.Devices.DeviceLists[0].Device.Usedcores, int32(20))
}

func Test_getNodesUsage_StaleMigIndexDoesNotPanic(t *testing.T) {
nodeMage := newNodeManager()
nodeMage.addNode("node1", &device.NodeInfo{
ID: "node1",
Node: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node1",
},
},
Devices: map[string][]device.DeviceInfo{
nvidia.NvidiaGPUDevice: {{
ID: "GPU0",
Index: 0,
Count: 10,
Devmem: 1024,
Devcore: 100,
Numa: 1,
Mode: "mig",
Health: true,
MIGTemplate: []device.Geometry{
{{Name: "1g.5gb", Memory: 5, Count: 1}},
},
}},
},
})
// tmpIdx=99 is far past len(MIGTemplate)==1: a stale/corrupt annotation
// must not panic PlatternMIG or the UsageList index write.
podDevces := device.PodDevices{
"NVIDIA": device.PodSingleDevice{
[]device.ContainerDevice{
{
Idx: 0,
UUID: "GPU0[99-0]",
Usedmem: 100,
Usedcores: 10,
},
},
},
}
podMap := device.NewPodManager()
podMap.AddPod(&corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "1111",
Name: "test1",
Namespace: "default",
},
}, "node1", podDevces)
s := Scheduler{
nodeManager: nodeMage,
podManager: podMap,
}
nodes := []string{"node1"}
cachenodeMap, _, _, err := s.getNodesUsage(&nodes, nil)
if err != nil {
t.Fatal(err)
}
v, ok := (*cachenodeMap)["node1"]
assert.Assert(t, ok)
Comment thread
mesutoezdil marked this conversation as resolved.
assert.Equal(t, int32(1), v.Devices.DeviceLists[0].Device.Used)
assert.Equal(t, 0, len(v.Devices.DeviceLists[0].Device.MigUsage.UsageList))
}

func Test_getNodesUsage_UnparsableMigUUIDDoesNotPanic(t *testing.T) {
nodeMage := newNodeManager()
nodeMage.addNode("node1", &device.NodeInfo{
ID: "node1",
Node: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node1",
},
},
Devices: map[string][]device.DeviceInfo{
nvidia.NvidiaGPUDevice: {{
ID: "GPU0",
Index: 0,
Count: 10,
Devmem: 1024,
Devcore: 100,
Numa: 1,
Mode: "mig",
Health: true,
MIGTemplate: []device.Geometry{
{{Name: "1g.5gb", Memory: 5, Count: 1}},
},
}},
},
})
// "abc" fails strconv.Atoi inside ExtractMigTemplatesFromUUID: exercises
// the parse-error branch, distinct from the out-of-range branch above.
podMap := device.NewPodManager()
podMap.AddPod(&corev1.Pod{
ObjectMeta: metav1.ObjectMeta{UID: "1111", Name: "test1", Namespace: "default"},
}, "node1", device.PodDevices{
nvidia.NvidiaGPUDevice: device.PodSingleDevice{
[]device.ContainerDevice{{Idx: 0, UUID: "GPU0[abc-0]", Usedmem: 100, Usedcores: 10}},
},
})
s := Scheduler{
nodeManager: nodeMage,
podManager: podMap,
}
nodes := []string{"node1"}
cachenodeMap, _, _, err := s.getNodesUsage(&nodes, nil)
if err != nil {
t.Fatal(err)
}
v, ok := (*cachenodeMap)["node1"]
assert.Assert(t, ok)
Comment thread
mesutoezdil marked this conversation as resolved.
assert.Equal(t, int32(1), v.Devices.DeviceLists[0].Device.Used)
assert.Equal(t, 0, len(v.Devices.DeviceLists[0].Device.MigUsage.UsageList))
}

func Test_getNodesUsage_OutOfRangeMigInstanceSkipped(t *testing.T) {
nodeMage := newNodeManager()
nodeMage.addNode("node1", &device.NodeInfo{
ID: "node1",
Node: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node1",
},
},
Devices: map[string][]device.DeviceInfo{
nvidia.NvidiaGPUDevice: {{
ID: "GPU0",
Index: 0,
Count: 10,
Devmem: 1024,
Devcore: 100,
Numa: 1,
Mode: "mig",
Health: true,
MIGTemplate: []device.Geometry{
{{Name: "1g.5gb", Memory: 5, Count: 1}},
},
}},
},
})
// Template index 0 is valid and populates a 1-entry UsageList, but the
// instance position "5" is past its end: exercises the instance bounds
// check separately from the template bounds check above.
podMap := device.NewPodManager()
podMap.AddPod(&corev1.Pod{
ObjectMeta: metav1.ObjectMeta{UID: "1111", Name: "test1", Namespace: "default"},
}, "node1", device.PodDevices{
nvidia.NvidiaGPUDevice: device.PodSingleDevice{
[]device.ContainerDevice{{Idx: 0, UUID: "GPU0[0-5]", Usedmem: 100, Usedcores: 10}},
},
})
s := Scheduler{
nodeManager: nodeMage,
podManager: podMap,
}
nodes := []string{"node1"}
cachenodeMap, _, _, err := s.getNodesUsage(&nodes, nil)
if err != nil {
t.Fatal(err)
}
v, ok := (*cachenodeMap)["node1"]
assert.Assert(t, ok)
assert.Equal(t, int32(1), v.Devices.DeviceLists[0].Device.Used)
assert.Equal(t, 1, len(v.Devices.DeviceLists[0].Device.MigUsage.UsageList))
assert.Assert(t, !v.Devices.DeviceLists[0].Device.MigUsage.UsageList[0].InUse)
Comment thread
mesutoezdil marked this conversation as resolved.
}

func Test_getNodesUsage_MismatchedMigIndexSkipped(t *testing.T) {
nodeMage := newNodeManager()
nodeMage.addNode("node1", &device.NodeInfo{
ID: "node1",
Node: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node1",
},
},
Devices: map[string][]device.DeviceInfo{
nvidia.NvidiaGPUDevice: {{
ID: "GPU0",
Index: 0,
Count: 10,
Devmem: 1024,
Devcore: 100,
Numa: 1,
Mode: "mig",
Health: true,
MIGTemplate: []device.Geometry{
{{Name: "1g.5gb", Memory: 5, Count: 1}},
{{Name: "2g.10gb", Memory: 10, Count: 1}},
},
}},
},
})
// Two pods on the same device disagree on which geometry (template index)
// is active. Whichever is processed first wins; the other must be
// skipped rather than writing its Instance into the wrong UsageList.
podMap := device.NewPodManager()
podMap.AddPod(&corev1.Pod{
ObjectMeta: metav1.ObjectMeta{UID: "1111", Name: "test1", Namespace: "default"},
}, "node1", device.PodDevices{
nvidia.NvidiaGPUDevice: device.PodSingleDevice{
[]device.ContainerDevice{{Idx: 0, UUID: "GPU0[0-0]", Usedmem: 100, Usedcores: 10}},
},
})
podMap.AddPod(&corev1.Pod{
ObjectMeta: metav1.ObjectMeta{UID: "2222", Name: "test2", Namespace: "default"},
}, "node1", device.PodDevices{
nvidia.NvidiaGPUDevice: device.PodSingleDevice{
[]device.ContainerDevice{{Idx: 0, UUID: "GPU0[1-0]", Usedmem: 100, Usedcores: 10}},
},
})
s := Scheduler{
nodeManager: nodeMage,
podManager: podMap,
}
nodes := []string{"node1"}
cachenodeMap, _, _, err := s.getNodesUsage(&nodes, nil)
if err != nil {
t.Fatal(err)
}
v, ok := (*cachenodeMap)["node1"]
assert.Assert(t, ok)
dev := v.Devices.DeviceLists[0].Device
assert.Equal(t, int32(2), dev.Used)
assert.Equal(t, 1, len(dev.MigUsage.UsageList))
assert.Assert(t, dev.MigUsage.UsageList[0].InUse)
Comment thread
mesutoezdil marked this conversation as resolved.
}

// test case matrix
/**
| pod name | node name| pod status | annotations | result |
Expand Down
Loading