Skip to content

Commit 939c9c0

Browse files
nojnhuhpohly
authored andcommitted
DRA: add ResourceSlice tracker
The purpose of the tracker is to emulate a ResourceSlice informer, including cache and event handlers. In contrast to that informer, the tracker adds taints from a DeviceTaint such that they appear in the ResourceSlice device definition. Code using the tracker doesn't need to care where the taints are coming from. The main advantage is that it enables fine-grained reactions to taints that only affect a few devices, the common case. Without this tracker, the pod eviction controller would have to sync all pods when any slice or any taint change. In the scheduler it avoids re-evaluating the selection criteria repeatedly. The tracker serves as a cross-pod-scheduling cache.
1 parent 99dbd85 commit 939c9c0

File tree

4 files changed

+2791
-0
lines changed

4 files changed

+2791
-0
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
Copyright 2024 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+
// TODO: This is duplicated from ./pkg/scheduler/util/queue because that
18+
// package is not allowed to be imported here.
19+
package queue
20+
21+
const (
22+
// normalSize limits the size of the buffer that is kept
23+
// for reuse.
24+
normalSize = 4
25+
)
26+
27+
// FIFO implements a first-in-first-out queue with unbounded size.
28+
// The null FIFO is a valid empty queue.
29+
//
30+
// Access must be protected by the caller when used concurrently by
31+
// different goroutines, the queue itself implements no locking.
32+
type FIFO[T any] struct {
33+
// elements contains a buffer for elements which have been
34+
// pushed and not popped yet. Two scenarios are possible:
35+
// - one chunk in the middle (start <= end)
36+
// - one chunk at the end, followed by one chunk at the
37+
// beginning (end <= start)
38+
//
39+
// start == end can be either an empty queue or a completely
40+
// full one (with two chunks).
41+
elements []T
42+
43+
// len counts the number of elements which have been pushed and
44+
// not popped yet.
45+
len int
46+
47+
// start is the index of the first valid element.
48+
start int
49+
50+
// end is the index after the last valid element.
51+
end int
52+
}
53+
54+
func (q *FIFO[T]) Len() int {
55+
return q.len
56+
}
57+
58+
func (q *FIFO[T]) Push(element T) {
59+
size := len(q.elements)
60+
if q.len == size {
61+
// Need larger buffer.
62+
newSize := size * 2
63+
if newSize == 0 {
64+
newSize = normalSize
65+
}
66+
elements := make([]T, newSize)
67+
if q.start == 0 {
68+
copy(elements, q.elements)
69+
} else {
70+
copy(elements, q.elements[q.start:])
71+
copy(elements[len(q.elements)-q.start:], q.elements[0:q.end])
72+
}
73+
q.start = 0
74+
q.end = q.len
75+
q.elements = elements
76+
size = newSize
77+
}
78+
if q.end == size {
79+
// Wrap around.
80+
q.elements[0] = element
81+
q.end = 1
82+
q.len++
83+
return
84+
}
85+
q.elements[q.end] = element
86+
q.end++
87+
q.len++
88+
}
89+
90+
func (q *FIFO[T]) Pop() (element T, ok bool) {
91+
if q.len == 0 {
92+
return
93+
}
94+
element = q.elements[q.start]
95+
q.start++
96+
if q.start == len(q.elements) {
97+
// Wrap around.
98+
q.start = 0
99+
}
100+
q.len--
101+
102+
// Once it is empty, shrink down to avoid hanging onto
103+
// a large buffer forever.
104+
if q.len == 0 && len(q.elements) > normalSize {
105+
q.elements = make([]T, normalSize)
106+
q.start = 0
107+
q.end = 0
108+
}
109+
110+
ok = true
111+
return
112+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
Copyright 2024 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 queue
18+
19+
import (
20+
"testing"
21+
22+
"github.com/stretchr/testify/require"
23+
)
24+
25+
func verifyPop(t *testing.T, expectedValue int, expectedOk bool, queue *FIFO[int]) {
26+
t.Helper()
27+
actual, ok := queue.Pop()
28+
require.Equal(t, expectedOk, ok)
29+
require.Equal(t, expectedValue, actual)
30+
}
31+
32+
func verifyEmpty(t *testing.T, queue *FIFO[int]) {
33+
t.Helper()
34+
require.Equal(t, 0, queue.Len())
35+
verifyPop(t, 0, false, queue)
36+
}
37+
38+
func TestNull(t *testing.T) {
39+
var queue FIFO[int]
40+
verifyEmpty(t, &queue)
41+
}
42+
43+
func TestOnePushPop(t *testing.T) {
44+
var queue FIFO[int]
45+
46+
expected := 10
47+
queue.Push(10)
48+
require.Equal(t, 1, queue.Len())
49+
verifyPop(t, expected, true, &queue)
50+
verifyEmpty(t, &queue)
51+
}
52+
53+
// Pushes some elements, pops all of them, then the same again.
54+
func TestWrapAroundEmpty(t *testing.T) {
55+
var queue FIFO[int]
56+
57+
for i := 0; i < 5; i++ {
58+
queue.Push(i)
59+
}
60+
require.Equal(t, 5, queue.Len())
61+
for i := 0; i < 5; i++ {
62+
verifyPop(t, i, true, &queue)
63+
}
64+
verifyEmpty(t, &queue)
65+
66+
for i := 5; i < 10; i++ {
67+
queue.Push(i)
68+
}
69+
for i := 5; i < 10; i++ {
70+
verifyPop(t, i, true, &queue)
71+
}
72+
verifyEmpty(t, &queue)
73+
}
74+
75+
// Pushes some elements, pops one, adds more, then pops all.
76+
func TestWrapAroundPartial(t *testing.T) {
77+
var queue FIFO[int]
78+
79+
for i := 0; i < 5; i++ {
80+
queue.Push(i)
81+
}
82+
require.Equal(t, 5, queue.Len())
83+
verifyPop(t, 0, true, &queue)
84+
85+
for i := 5; i < 10; i++ {
86+
queue.Push(i)
87+
}
88+
for i := 1; i < 10; i++ {
89+
verifyPop(t, i, true, &queue)
90+
}
91+
verifyEmpty(t, &queue)
92+
}
93+
94+
// Push an unusual amount of elements, pop all, and verify that
95+
// the FIFO shrinks back again.
96+
func TestShrink(t *testing.T) {
97+
var queue FIFO[int]
98+
99+
for i := 0; i < normalSize*2; i++ {
100+
queue.Push(i)
101+
}
102+
require.Equal(t, normalSize*2, queue.Len())
103+
require.LessOrEqual(t, 2*normalSize, len(queue.elements))
104+
105+
// Pop all, should be shrunken when done.
106+
for i := 0; i < normalSize*2; i++ {
107+
verifyPop(t, i, true, &queue)
108+
}
109+
require.Equal(t, 0, queue.Len())
110+
require.Len(t, queue.elements, normalSize)
111+
112+
// Still usable after shrinking?
113+
queue.Push(42)
114+
verifyPop(t, 42, true, &queue)
115+
require.Equal(t, 0, queue.Len())
116+
require.Len(t, queue.elements, normalSize)
117+
}

0 commit comments

Comments
 (0)