Skip to content

Commit 2a7767e

Browse files
authored
Merge pull request #735 from ffromani/nrt-hostlevel-resources
[noderesourcetopology] improve classification of resources
2 parents e556b49 + c48f462 commit 2a7767e

File tree

3 files changed

+169
-17
lines changed

3 files changed

+169
-17
lines changed

pkg/noderesourcetopology/filter.go

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
v1 "k8s.io/api/core/v1"
2323
"k8s.io/apimachinery/pkg/api/resource"
2424
"k8s.io/klog/v2"
25-
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
2625
v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos"
2726
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
2827
bm "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask"
@@ -132,8 +131,8 @@ func resourcesAvailableInAnyNUMANodes(lh logr.Logger, numaNodes NUMANodeList, re
132131

133132
// non-native resources or ephemeral-storage may not expose NUMA affinity,
134133
// but since they are available at node level, this is fine
135-
if !hasNUMAAffinity && (!v1helper.IsNativeResource(resource) || resource == v1.ResourceEphemeralStorage) {
136-
lh.V(6).Info("resource available at node level (no NUMA affinity)", "resource", resource)
134+
if !hasNUMAAffinity && isHostLevelResource(resource) {
135+
lh.V(6).Info("resource available at host level (no NUMA affinity)", "resource", resource)
137136
continue
138137
}
139138

@@ -156,21 +155,9 @@ func resourcesAvailableInAnyNUMANodes(lh logr.Logger, numaNodes NUMANodeList, re
156155
}
157156

158157
func isResourceSetSuitable(qos v1.PodQOSClass, resource v1.ResourceName, quantity, numaQuantity resource.Quantity) bool {
159-
// Check for the following:
160-
if qos != v1.PodQOSGuaranteed {
161-
// 1. set numa node as possible node if resource is memory or Hugepages
162-
if resource == v1.ResourceMemory {
163-
return true
164-
}
165-
if v1helper.IsHugePageResourceName(resource) {
166-
return true
167-
}
168-
// 2. set numa node as possible node if resource is CPU
169-
if resource == v1.ResourceCPU {
170-
return true
171-
}
158+
if qos != v1.PodQOSGuaranteed && isNUMAAffineResource(resource) {
159+
return true
172160
}
173-
// 3. otherwise check amount of resources
174161
return numaQuantity.Cmp(quantity) >= 0
175162
}
176163

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
Copyright 2021 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package noderesourcetopology
18+
19+
import (
20+
corev1 "k8s.io/api/core/v1"
21+
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
22+
)
23+
24+
func isHostLevelResource(resource corev1.ResourceName) bool {
25+
// host-level resources are resources which *may* not be bound to NUMA nodes.
26+
// A Key example is generic [ephemeral] storage which doesn't expose NUMA affinity.
27+
if resource == corev1.ResourceEphemeralStorage {
28+
return true
29+
}
30+
if resource == corev1.ResourceStorage {
31+
return true
32+
}
33+
if !v1helper.IsNativeResource(resource) {
34+
return true
35+
}
36+
return false
37+
}
38+
39+
func isNUMAAffineResource(resource corev1.ResourceName) bool {
40+
// NUMA-affine resources are resources which are required to be bound to NUMA nodes.
41+
// A Key example is CPU and memory, which must expose NUMA affinity.
42+
if resource == corev1.ResourceCPU {
43+
return true
44+
}
45+
if resource == corev1.ResourceMemory {
46+
return true
47+
}
48+
if v1helper.IsHugePageResourceName(resource) {
49+
return true
50+
}
51+
// Devices are *expected* to expose NUMA Affinity, but they are not *required* to do so.
52+
// We can't tell for sure, so we default to "no".
53+
return false
54+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
Copyright 2021 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package noderesourcetopology
18+
19+
import (
20+
"testing"
21+
22+
corev1 "k8s.io/api/core/v1"
23+
)
24+
25+
func TestIsHostLevelResource(t *testing.T) {
26+
testCases := []struct {
27+
resource corev1.ResourceName
28+
expected bool
29+
}{
30+
{
31+
resource: corev1.ResourceCPU,
32+
expected: false,
33+
},
34+
{
35+
resource: corev1.ResourceMemory,
36+
expected: false,
37+
},
38+
{
39+
resource: corev1.ResourceName("hugepages-1Gi"),
40+
expected: false,
41+
},
42+
{
43+
resource: corev1.ResourceStorage,
44+
expected: true,
45+
},
46+
{
47+
resource: corev1.ResourceEphemeralStorage,
48+
expected: true,
49+
},
50+
{
51+
resource: corev1.ResourceName("vendor.io/fastest-nic"),
52+
expected: true,
53+
},
54+
{
55+
resource: corev1.ResourceName("awesome.com/gpu-for-ai"),
56+
expected: true,
57+
},
58+
}
59+
for _, testCase := range testCases {
60+
t.Run(string(testCase.resource), func(t *testing.T) {
61+
got := isHostLevelResource(testCase.resource)
62+
if got != testCase.expected {
63+
t.Fatalf("expected %t to equal %t", got, testCase.expected)
64+
}
65+
})
66+
}
67+
}
68+
69+
func TestIsNUMAAffineResource(t *testing.T) {
70+
testCases := []struct {
71+
resource corev1.ResourceName
72+
expected bool
73+
}{
74+
{
75+
resource: corev1.ResourceCPU,
76+
expected: true,
77+
},
78+
{
79+
resource: corev1.ResourceMemory,
80+
expected: true,
81+
},
82+
{
83+
resource: corev1.ResourceName("hugepages-1Gi"),
84+
expected: true,
85+
},
86+
{
87+
resource: corev1.ResourceStorage,
88+
expected: false,
89+
},
90+
{
91+
resource: corev1.ResourceEphemeralStorage,
92+
expected: false,
93+
},
94+
{
95+
resource: corev1.ResourceName("vendor.io/fastest-nic"),
96+
expected: false,
97+
},
98+
{
99+
resource: corev1.ResourceName("awesome.com/gpu-for-ai"),
100+
expected: false,
101+
},
102+
}
103+
for _, testCase := range testCases {
104+
t.Run(string(testCase.resource), func(t *testing.T) {
105+
got := isNUMAAffineResource(testCase.resource)
106+
if got != testCase.expected {
107+
t.Fatalf("expected %t to equal %t", got, testCase.expected)
108+
}
109+
})
110+
}
111+
}

0 commit comments

Comments
 (0)