diff --git a/pkg/kubelet/cm/cpumanager/topology_hints_test.go b/pkg/kubelet/cm/cpumanager/topology_hints_test.go index 6991db3a626..e7ac5eb9669 100644 --- a/pkg/kubelet/cm/cpumanager/topology_hints_test.go +++ b/pkg/kubelet/cm/cpumanager/topology_hints_test.go @@ -62,6 +62,95 @@ func returnMachineInfo() cadvisorapi.MachineInfo { } } +func TestPodGuaranteedCPUs(t *testing.T) { + CPUs := [][]struct { + request string + limit string + }{ + { + {request: "0", limit: "0"}, + }, + { + {request: "2", limit: "2"}, + }, + { + {request: "5", limit: "5"}, + }, + { + {request: "2", limit: "2"}, + {request: "4", limit: "4"}, + }, + } + // tc for not guaranteed Pod + testPod1 := makeMultiContainerPod(CPUs[0], CPUs[0]) + testPod2 := makeMultiContainerPod(CPUs[0], CPUs[1]) + testPod3 := makeMultiContainerPod(CPUs[1], CPUs[0]) + // tc for guaranteed Pod + testPod4 := makeMultiContainerPod(CPUs[1], CPUs[1]) + testPod5 := makeMultiContainerPod(CPUs[2], CPUs[2]) + // tc for comparing init containers and user containers + testPod6 := makeMultiContainerPod(CPUs[1], CPUs[2]) + testPod7 := makeMultiContainerPod(CPUs[2], CPUs[1]) + // tc for multi containers + testPod8 := makeMultiContainerPod(CPUs[3], CPUs[3]) + + p := staticPolicy{} + + tcases := []struct { + name string + pod *v1.Pod + expectedCPU int + }{ + { + name: "TestCase01: if requestedCPU == 0, Pod is not Guaranteed Qos", + pod: testPod1, + expectedCPU: 0, + }, + { + name: "TestCase02: if requestedCPU == 0, Pod is not Guaranteed Qos", + pod: testPod2, + expectedCPU: 0, + }, + { + name: "TestCase03: if requestedCPU == 0, Pod is not Guaranteed Qos", + pod: testPod3, + expectedCPU: 0, + }, + { + name: "TestCase04: Guaranteed Pod requests 2 CPUs", + pod: testPod4, + expectedCPU: 2, + }, + { + name: "TestCase05: Guaranteed Pod requests 5 CPUs", + pod: testPod5, + expectedCPU: 5, + }, + { + name: "TestCase06: The number of CPUs requested By app is bigger than the number of CPUs requested by init", + pod: testPod6, + expectedCPU: 5, + }, + { + name: "TestCase07: The number of CPUs requested By init is bigger than the number of CPUs requested by app", + pod: testPod7, + expectedCPU: 5, + }, + { + name: "TestCase08: Sum of CPUs requested by multiple containers", + pod: testPod8, + expectedCPU: 6, + }, + } + for _, tc := range tcases { + requestedCPU := p.podGuaranteedCPUs(tc.pod) + + if requestedCPU != tc.expectedCPU { + t.Errorf("Expected in result to be %v , got %v", tc.expectedCPU, requestedCPU) + } + } +} + func TestGetTopologyHints(t *testing.T) { machineInfo := returnMachineInfo() tcases := returnTestCases() @@ -111,6 +200,54 @@ func TestGetTopologyHints(t *testing.T) { } } +func TestGetPodTopologyHints(t *testing.T) { + machineInfo := returnMachineInfo() + + for _, tc := range returnTestCases() { + topology, _ := topology.Discover(&machineInfo) + + var activePods []*v1.Pod + for p := range tc.assignments { + pod := v1.Pod{} + pod.UID = types.UID(p) + for c := range tc.assignments[p] { + container := v1.Container{} + container.Name = c + pod.Spec.Containers = append(pod.Spec.Containers, container) + } + activePods = append(activePods, &pod) + } + + m := manager{ + policy: &staticPolicy{ + topology: topology, + }, + state: &mockState{ + assignments: tc.assignments, + defaultCPUSet: tc.defaultCPUSet, + }, + topology: topology, + activePods: func() []*v1.Pod { return activePods }, + podStatusProvider: mockPodStatusProvider{}, + sourcesReady: &sourcesReadyStub{}, + } + + podHints := m.GetPodTopologyHints(&tc.pod)[string(v1.ResourceCPU)] + if len(tc.expectedHints) == 0 && len(podHints) == 0 { + continue + } + sort.SliceStable(podHints, func(i, j int) bool { + return podHints[i].LessThan(podHints[j]) + }) + sort.SliceStable(tc.expectedHints, func(i, j int) bool { + return tc.expectedHints[i].LessThan(tc.expectedHints[j]) + }) + if !reflect.DeepEqual(tc.expectedHints, podHints) { + t.Errorf("Expected in result to be %v , got %v", tc.expectedHints, podHints) + } + } +} + func returnTestCases() []testCase { testPod1 := makePod("fakePod", "fakeContainer", "2", "2") testContainer1 := &testPod1.Spec.Containers[0] diff --git a/pkg/kubelet/cm/devicemanager/topology_hints_test.go b/pkg/kubelet/cm/devicemanager/topology_hints_test.go index 84f000a8cc6..1658049a05c 100644 --- a/pkg/kubelet/cm/devicemanager/topology_hints_test.go +++ b/pkg/kubelet/cm/devicemanager/topology_hints_test.go @@ -52,324 +52,7 @@ func makeSocketMask(sockets ...int) bitmask.BitMask { } func TestGetTopologyHints(t *testing.T) { - tcases := []struct { - description string - podUID string - containerName string - request map[string]string - devices map[string][]pluginapi.Device - allocatedDevices map[string]map[string]map[string][]string - expectedHints map[string][]topologymanager.TopologyHint - }{ - { - description: "Single Request, no alignment", - podUID: "fakePod", - containerName: "fakeContainer", - request: map[string]string{ - "testdevice": "1", - }, - devices: map[string][]pluginapi.Device{ - "testdevice": { - {ID: "Dev1"}, - {ID: "Dev2"}, - }, - }, - expectedHints: map[string][]topologymanager.TopologyHint{ - "testdevice": nil, - }, - }, - { - description: "Single Request, only one with alignment", - podUID: "fakePod", - containerName: "fakeContainer", - request: map[string]string{ - "testdevice": "1", - }, - devices: map[string][]pluginapi.Device{ - "testdevice": { - {ID: "Dev1"}, - makeNUMADevice("Dev2", 1), - }, - }, - expectedHints: map[string][]topologymanager.TopologyHint{ - "testdevice": { - { - NUMANodeAffinity: makeSocketMask(1), - Preferred: true, - }, - { - NUMANodeAffinity: makeSocketMask(0, 1), - Preferred: false, - }, - }, - }, - }, - { - description: "Single Request, one device per socket", - podUID: "fakePod", - containerName: "fakeContainer", - request: map[string]string{ - "testdevice": "1", - }, - devices: map[string][]pluginapi.Device{ - "testdevice": { - makeNUMADevice("Dev1", 0), - makeNUMADevice("Dev2", 1), - }, - }, - expectedHints: map[string][]topologymanager.TopologyHint{ - "testdevice": { - { - NUMANodeAffinity: makeSocketMask(0), - Preferred: true, - }, - { - NUMANodeAffinity: makeSocketMask(1), - Preferred: true, - }, - { - NUMANodeAffinity: makeSocketMask(0, 1), - Preferred: false, - }, - }, - }, - }, - { - description: "Request for 2, one device per socket", - podUID: "fakePod", - containerName: "fakeContainer", - request: map[string]string{ - "testdevice": "2", - }, - devices: map[string][]pluginapi.Device{ - "testdevice": { - makeNUMADevice("Dev1", 0), - makeNUMADevice("Dev2", 1), - }, - }, - expectedHints: map[string][]topologymanager.TopologyHint{ - "testdevice": { - { - NUMANodeAffinity: makeSocketMask(0, 1), - Preferred: true, - }, - }, - }, - }, - { - description: "Request for 2, 2 devices per socket", - podUID: "fakePod", - containerName: "fakeContainer", - request: map[string]string{ - "testdevice": "2", - }, - devices: map[string][]pluginapi.Device{ - "testdevice": { - makeNUMADevice("Dev1", 0), - makeNUMADevice("Dev2", 1), - makeNUMADevice("Dev3", 0), - makeNUMADevice("Dev4", 1), - }, - }, - expectedHints: map[string][]topologymanager.TopologyHint{ - "testdevice": { - { - NUMANodeAffinity: makeSocketMask(0), - Preferred: true, - }, - { - NUMANodeAffinity: makeSocketMask(1), - Preferred: true, - }, - { - NUMANodeAffinity: makeSocketMask(0, 1), - Preferred: false, - }, - }, - }, - }, - { - description: "Request for 2, optimal on 1 NUMA node, forced cross-NUMA", - podUID: "fakePod", - containerName: "fakeContainer", - request: map[string]string{ - "testdevice": "2", - }, - devices: map[string][]pluginapi.Device{ - "testdevice": { - makeNUMADevice("Dev1", 0), - makeNUMADevice("Dev2", 1), - makeNUMADevice("Dev3", 0), - makeNUMADevice("Dev4", 1), - }, - }, - allocatedDevices: map[string]map[string]map[string][]string{ - "fakePod": { - "fakeOtherContainer": { - "testdevice": {"Dev1", "Dev2"}, - }, - }, - }, - expectedHints: map[string][]topologymanager.TopologyHint{ - "testdevice": { - { - NUMANodeAffinity: makeSocketMask(0, 1), - Preferred: false, - }, - }, - }, - }, - { - description: "2 device types, mixed configuration", - podUID: "fakePod", - containerName: "fakeContainer", - request: map[string]string{ - "testdevice1": "2", - "testdevice2": "1", - }, - devices: map[string][]pluginapi.Device{ - "testdevice1": { - makeNUMADevice("Dev1", 0), - makeNUMADevice("Dev2", 1), - makeNUMADevice("Dev3", 0), - makeNUMADevice("Dev4", 1), - }, - "testdevice2": { - makeNUMADevice("Dev1", 0), - }, - }, - expectedHints: map[string][]topologymanager.TopologyHint{ - "testdevice1": { - { - NUMANodeAffinity: makeSocketMask(0), - Preferred: true, - }, - { - NUMANodeAffinity: makeSocketMask(1), - Preferred: true, - }, - { - NUMANodeAffinity: makeSocketMask(0, 1), - Preferred: false, - }, - }, - "testdevice2": { - { - NUMANodeAffinity: makeSocketMask(0), - Preferred: true, - }, - { - NUMANodeAffinity: makeSocketMask(0, 1), - Preferred: false, - }, - }, - }, - }, - { - description: "Single device type, more requested than available", - podUID: "fakePod", - containerName: "fakeContainer", - request: map[string]string{ - "testdevice": "6", - }, - devices: map[string][]pluginapi.Device{ - "testdevice": { - makeNUMADevice("Dev1", 0), - makeNUMADevice("Dev2", 0), - makeNUMADevice("Dev3", 1), - makeNUMADevice("Dev4", 1), - }, - }, - expectedHints: map[string][]topologymanager.TopologyHint{ - "testdevice": {}, - }, - }, - { - description: "Single device type, all already allocated to container", - podUID: "fakePod", - containerName: "fakeContainer", - request: map[string]string{ - "testdevice": "2", - }, - devices: map[string][]pluginapi.Device{ - "testdevice": { - makeNUMADevice("Dev1", 0), - makeNUMADevice("Dev2", 0), - }, - }, - allocatedDevices: map[string]map[string]map[string][]string{ - "fakePod": { - "fakeContainer": { - "testdevice": {"Dev1", "Dev2"}, - }, - }, - }, - expectedHints: map[string][]topologymanager.TopologyHint{ - "testdevice": { - { - NUMANodeAffinity: makeSocketMask(0), - Preferred: true, - }, - { - NUMANodeAffinity: makeSocketMask(0, 1), - Preferred: false, - }, - }, - }, - }, - { - description: "Single device type, less already allocated to container than requested", - podUID: "fakePod", - containerName: "fakeContainer", - request: map[string]string{ - "testdevice": "4", - }, - devices: map[string][]pluginapi.Device{ - "testdevice": { - makeNUMADevice("Dev1", 0), - makeNUMADevice("Dev2", 0), - makeNUMADevice("Dev3", 1), - makeNUMADevice("Dev4", 1), - }, - }, - allocatedDevices: map[string]map[string]map[string][]string{ - "fakePod": { - "fakeContainer": { - "testdevice": {"Dev1", "Dev2"}, - }, - }, - }, - expectedHints: map[string][]topologymanager.TopologyHint{ - "testdevice": {}, - }, - }, - { - description: "Single device type, more already allocated to container than requested", - podUID: "fakePod", - containerName: "fakeContainer", - request: map[string]string{ - "testdevice": "2", - }, - devices: map[string][]pluginapi.Device{ - "testdevice": { - makeNUMADevice("Dev1", 0), - makeNUMADevice("Dev2", 0), - makeNUMADevice("Dev3", 1), - makeNUMADevice("Dev4", 1), - }, - }, - allocatedDevices: map[string]map[string]map[string][]string{ - "fakePod": { - "fakeContainer": { - "testdevice": {"Dev1", "Dev2", "Dev3", "Dev4"}, - }, - }, - }, - expectedHints: map[string][]topologymanager.TopologyHint{ - "testdevice": {}, - }, - }, - } + tcases := getCommonTestCases() for _, tc := range tcases { resourceList := v1.ResourceList{} @@ -980,3 +663,326 @@ func TestGetPreferredAllocationParameters(t *testing.T) { } } } + +type topologyHintTestCase struct { + description string + podUID string + containerName string + request map[string]string + devices map[string][]pluginapi.Device + allocatedDevices map[string]map[string]map[string][]string + expectedHints map[string][]topologymanager.TopologyHint +} + +func getCommonTestCases() []topologyHintTestCase { + return []topologyHintTestCase{ + { + description: "Single Request, no alignment", + podUID: "fakePod", + containerName: "fakeContainer", + request: map[string]string{ + "testdevice": "1", + }, + devices: map[string][]pluginapi.Device{ + "testdevice": { + {ID: "Dev1"}, + {ID: "Dev2"}, + }, + }, + expectedHints: map[string][]topologymanager.TopologyHint{ + "testdevice": nil, + }, + }, + { + description: "Single Request, only one with alignment", + podUID: "fakePod", + containerName: "fakeContainer", + request: map[string]string{ + "testdevice": "1", + }, + devices: map[string][]pluginapi.Device{ + "testdevice": { + {ID: "Dev1"}, + makeNUMADevice("Dev2", 1), + }, + }, + expectedHints: map[string][]topologymanager.TopologyHint{ + "testdevice": { + { + NUMANodeAffinity: makeSocketMask(1), + Preferred: true, + }, + { + NUMANodeAffinity: makeSocketMask(0, 1), + Preferred: false, + }, + }, + }, + }, + { + description: "Single Request, one device per socket", + podUID: "fakePod", + containerName: "fakeContainer", + request: map[string]string{ + "testdevice": "1", + }, + devices: map[string][]pluginapi.Device{ + "testdevice": { + makeNUMADevice("Dev1", 0), + makeNUMADevice("Dev2", 1), + }, + }, + expectedHints: map[string][]topologymanager.TopologyHint{ + "testdevice": { + { + NUMANodeAffinity: makeSocketMask(0), + Preferred: true, + }, + { + NUMANodeAffinity: makeSocketMask(1), + Preferred: true, + }, + { + NUMANodeAffinity: makeSocketMask(0, 1), + Preferred: false, + }, + }, + }, + }, + { + description: "Request for 2, one device per socket", + podUID: "fakePod", + containerName: "fakeContainer", + request: map[string]string{ + "testdevice": "2", + }, + devices: map[string][]pluginapi.Device{ + "testdevice": { + makeNUMADevice("Dev1", 0), + makeNUMADevice("Dev2", 1), + }, + }, + expectedHints: map[string][]topologymanager.TopologyHint{ + "testdevice": { + { + NUMANodeAffinity: makeSocketMask(0, 1), + Preferred: true, + }, + }, + }, + }, + { + description: "Request for 2, 2 devices per socket", + podUID: "fakePod", + containerName: "fakeContainer", + request: map[string]string{ + "testdevice": "2", + }, + devices: map[string][]pluginapi.Device{ + "testdevice": { + makeNUMADevice("Dev1", 0), + makeNUMADevice("Dev2", 1), + makeNUMADevice("Dev3", 0), + makeNUMADevice("Dev4", 1), + }, + }, + expectedHints: map[string][]topologymanager.TopologyHint{ + "testdevice": { + { + NUMANodeAffinity: makeSocketMask(0), + Preferred: true, + }, + { + NUMANodeAffinity: makeSocketMask(1), + Preferred: true, + }, + { + NUMANodeAffinity: makeSocketMask(0, 1), + Preferred: false, + }, + }, + }, + }, + { + description: "Request for 2, optimal on 1 NUMA node, forced cross-NUMA", + podUID: "fakePod", + containerName: "fakeContainer", + request: map[string]string{ + "testdevice": "2", + }, + devices: map[string][]pluginapi.Device{ + "testdevice": { + makeNUMADevice("Dev1", 0), + makeNUMADevice("Dev2", 1), + makeNUMADevice("Dev3", 0), + makeNUMADevice("Dev4", 1), + }, + }, + allocatedDevices: map[string]map[string]map[string][]string{ + "fakePod": { + "fakeOtherContainer": { + "testdevice": {"Dev1", "Dev2"}, + }, + }, + }, + expectedHints: map[string][]topologymanager.TopologyHint{ + "testdevice": { + { + NUMANodeAffinity: makeSocketMask(0, 1), + Preferred: false, + }, + }, + }, + }, + { + description: "2 device types, mixed configuration", + podUID: "fakePod", + containerName: "fakeContainer", + request: map[string]string{ + "testdevice1": "2", + "testdevice2": "1", + }, + devices: map[string][]pluginapi.Device{ + "testdevice1": { + makeNUMADevice("Dev1", 0), + makeNUMADevice("Dev2", 1), + makeNUMADevice("Dev3", 0), + makeNUMADevice("Dev4", 1), + }, + "testdevice2": { + makeNUMADevice("Dev1", 0), + }, + }, + expectedHints: map[string][]topologymanager.TopologyHint{ + "testdevice1": { + { + NUMANodeAffinity: makeSocketMask(0), + Preferred: true, + }, + { + NUMANodeAffinity: makeSocketMask(1), + Preferred: true, + }, + { + NUMANodeAffinity: makeSocketMask(0, 1), + Preferred: false, + }, + }, + "testdevice2": { + { + NUMANodeAffinity: makeSocketMask(0), + Preferred: true, + }, + { + NUMANodeAffinity: makeSocketMask(0, 1), + Preferred: false, + }, + }, + }, + }, + { + description: "Single device type, more requested than available", + podUID: "fakePod", + containerName: "fakeContainer", + request: map[string]string{ + "testdevice": "6", + }, + devices: map[string][]pluginapi.Device{ + "testdevice": { + makeNUMADevice("Dev1", 0), + makeNUMADevice("Dev2", 0), + makeNUMADevice("Dev3", 1), + makeNUMADevice("Dev4", 1), + }, + }, + expectedHints: map[string][]topologymanager.TopologyHint{ + "testdevice": {}, + }, + }, + { + description: "Single device type, all already allocated to container", + podUID: "fakePod", + containerName: "fakeContainer", + request: map[string]string{ + "testdevice": "2", + }, + devices: map[string][]pluginapi.Device{ + "testdevice": { + makeNUMADevice("Dev1", 0), + makeNUMADevice("Dev2", 0), + }, + }, + allocatedDevices: map[string]map[string]map[string][]string{ + "fakePod": { + "fakeContainer": { + "testdevice": {"Dev1", "Dev2"}, + }, + }, + }, + expectedHints: map[string][]topologymanager.TopologyHint{ + "testdevice": { + { + NUMANodeAffinity: makeSocketMask(0), + Preferred: true, + }, + { + NUMANodeAffinity: makeSocketMask(0, 1), + Preferred: false, + }, + }, + }, + }, + { + description: "Single device type, less already allocated to container than requested", + podUID: "fakePod", + containerName: "fakeContainer", + request: map[string]string{ + "testdevice": "4", + }, + devices: map[string][]pluginapi.Device{ + "testdevice": { + makeNUMADevice("Dev1", 0), + makeNUMADevice("Dev2", 0), + makeNUMADevice("Dev3", 1), + makeNUMADevice("Dev4", 1), + }, + }, + allocatedDevices: map[string]map[string]map[string][]string{ + "fakePod": { + "fakeContainer": { + "testdevice": {"Dev1", "Dev2"}, + }, + }, + }, + expectedHints: map[string][]topologymanager.TopologyHint{ + "testdevice": {}, + }, + }, + { + description: "Single device type, more already allocated to container than requested", + podUID: "fakePod", + containerName: "fakeContainer", + request: map[string]string{ + "testdevice": "2", + }, + devices: map[string][]pluginapi.Device{ + "testdevice": { + makeNUMADevice("Dev1", 0), + makeNUMADevice("Dev2", 0), + makeNUMADevice("Dev3", 1), + makeNUMADevice("Dev4", 1), + }, + }, + allocatedDevices: map[string]map[string]map[string][]string{ + "fakePod": { + "fakeContainer": { + "testdevice": {"Dev1", "Dev2", "Dev3", "Dev4"}, + }, + }, + }, + expectedHints: map[string][]topologymanager.TopologyHint{ + "testdevice": {}, + }, + }, + } +}