@@ -17,13 +17,19 @@ limitations under the License.
17
17
package dra
18
18
19
19
import (
20
+ "context"
21
+ "errors"
20
22
"fmt"
21
23
"regexp"
22
24
"sort"
23
25
"strings"
24
26
"testing"
27
+ "time"
25
28
29
+ "github.com/onsi/gomega"
30
+ "github.com/onsi/gomega/gstruct"
26
31
"github.com/stretchr/testify/assert"
32
+ "github.com/stretchr/testify/require"
27
33
28
34
v1 "k8s.io/api/core/v1"
29
35
resourcealphaapi "k8s.io/api/resource/v1alpha3"
@@ -34,10 +40,15 @@ import (
34
40
utilfeature "k8s.io/apiserver/pkg/util/feature"
35
41
"k8s.io/component-base/featuregate"
36
42
featuregatetesting "k8s.io/component-base/featuregate/testing"
43
+ "k8s.io/klog/v2"
44
+ kubeschedulerconfigv1 "k8s.io/kube-scheduler/config/v1"
37
45
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
38
46
"k8s.io/kubernetes/pkg/features"
47
+ "k8s.io/kubernetes/pkg/scheduler/apis/config"
48
+ kubeschedulerscheme "k8s.io/kubernetes/pkg/scheduler/apis/config/scheme"
39
49
st "k8s.io/kubernetes/pkg/scheduler/testing"
40
50
"k8s.io/kubernetes/test/integration/framework"
51
+ "k8s.io/kubernetes/test/integration/util"
41
52
"k8s.io/kubernetes/test/utils/ktesting"
42
53
"k8s.io/utils/ptr"
43
54
)
@@ -54,11 +65,21 @@ var (
54
65
Container ("my-container" ).
55
66
PodResourceClaims (v1.PodResourceClaim {Name : resourceName , ResourceClaimName : & claimName }).
56
67
Obj ()
68
+ class = & resourceapi.DeviceClass {
69
+ ObjectMeta : metav1.ObjectMeta {
70
+ Name : className ,
71
+ },
72
+ }
57
73
claim = st .MakeResourceClaim ().
58
74
Name (claimName ).
59
75
Namespace (namespace ).
60
76
Request (className ).
61
77
Obj ()
78
+ claimPrioritizedList = st .MakeResourceClaim ().
79
+ Name (claimName ).
80
+ Namespace (namespace ).
81
+ RequestWithPrioritizedList (className ).
82
+ Obj ()
62
83
)
63
84
64
85
// createTestNamespace creates a namespace with a name that is derived from the
@@ -106,6 +127,7 @@ func TestDRA(t *testing.T) {
106
127
features : map [featuregate.Feature ]bool {features .DynamicResourceAllocation : true },
107
128
f : func (tCtx ktesting.TContext ) {
108
129
tCtx .Run ("AdminAccess" , func (tCtx ktesting.TContext ) { testAdminAccess (tCtx , false ) })
130
+ tCtx .Run ("PrioritizedList" , func (tCtx ktesting.TContext ) { testPrioritizedList (tCtx , false ) })
109
131
tCtx .Run ("Pod" , func (tCtx ktesting.TContext ) { testPod (tCtx , true ) })
110
132
},
111
133
},
@@ -119,11 +141,13 @@ func TestDRA(t *testing.T) {
119
141
// Additional DRA feature gates go here,
120
142
// in alphabetical order,
121
143
// as needed by tests for them.
122
- features .DRAAdminAccess : true ,
144
+ features .DRAAdminAccess : true ,
145
+ features .DRAPrioritizedList : true ,
123
146
},
124
147
f : func (tCtx ktesting.TContext ) {
125
148
tCtx .Run ("AdminAccess" , func (tCtx ktesting.TContext ) { testAdminAccess (tCtx , true ) })
126
149
tCtx .Run ("Convert" , testConvert )
150
+ tCtx .Run ("PrioritizedList" , func (tCtx ktesting.TContext ) { testPrioritizedList (tCtx , true ) })
127
151
},
128
152
},
129
153
} {
@@ -146,21 +170,43 @@ func TestDRA(t *testing.T) {
146
170
etcdOptions := framework .SharedEtcd ()
147
171
apiServerOptions := kubeapiservertesting .NewDefaultTestServerOptions ()
148
172
apiServerFlags := framework .DefaultTestServerFlags ()
149
- // Default kube-apiserver behavior, must be requested explicitly for test server.
150
- runtimeConfigs := []string {"api/alpha=false" , "api/beta=false" }
173
+ var runtimeConfigs []string
151
174
for key , value := range tc .apis {
152
175
runtimeConfigs = append (runtimeConfigs , fmt .Sprintf ("%s=%t" , key , value ))
153
176
}
154
177
apiServerFlags = append (apiServerFlags , "--runtime-config=" + strings .Join (runtimeConfigs , "," ))
155
178
server := kubeapiservertesting .StartTestServerOrDie (t , apiServerOptions , apiServerFlags , etcdOptions )
156
179
tCtx .Cleanup (server .TearDownFn )
157
-
158
180
tCtx = ktesting .WithRESTConfig (tCtx , server .ClientConfig )
181
+
159
182
tc .f (tCtx )
160
183
})
161
184
}
162
185
}
163
186
187
+ func startScheduler (tCtx ktesting.TContext ) {
188
+ // Run scheduler with default configuration.
189
+ tCtx .Log ("Scheduler starting..." )
190
+ schedulerCtx := klog .NewContext (tCtx , klog .LoggerWithName (tCtx .Logger (), "scheduler" ))
191
+ schedulerCtx , cancel := context .WithCancelCause (schedulerCtx )
192
+ _ , informerFactory := util .StartScheduler (schedulerCtx , tCtx .Client (), tCtx .RESTConfig (), newDefaultSchedulerComponentConfig (tCtx ), nil )
193
+ // Stop clients of the apiserver before stopping the apiserver itself,
194
+ // otherwise it delays its shutdown.
195
+ tCtx .Cleanup (informerFactory .Shutdown )
196
+ tCtx .Cleanup (func () {
197
+ tCtx .Log ("Stoping scheduler..." )
198
+ cancel (errors .New ("test is done" ))
199
+ })
200
+ }
201
+
202
+ func newDefaultSchedulerComponentConfig (tCtx ktesting.TContext ) * config.KubeSchedulerConfiguration {
203
+ gvk := kubeschedulerconfigv1 .SchemeGroupVersion .WithKind ("KubeSchedulerConfiguration" )
204
+ cfg := config.KubeSchedulerConfiguration {}
205
+ _ , _ , err := kubeschedulerscheme .Codecs .UniversalDecoder ().Decode (nil , & gvk , & cfg )
206
+ tCtx .ExpectNoError (err , "decode default scheduler configuration" )
207
+ return & cfg
208
+ }
209
+
164
210
// testPod creates a pod with a resource claim reference and then checks
165
211
// whether that field is or isn't getting dropped.
166
212
func testPod (tCtx ktesting.TContext , draEnabled bool ) {
@@ -220,3 +266,45 @@ func testAdminAccess(tCtx ktesting.TContext, adminAccessEnabled bool) {
220
266
}
221
267
}
222
268
}
269
+
270
+ func testPrioritizedList (tCtx ktesting.TContext , enabled bool ) {
271
+ tCtx .Parallel ()
272
+ _ , err := tCtx .Client ().ResourceV1beta1 ().DeviceClasses ().Create (tCtx , class , metav1.CreateOptions {})
273
+ tCtx .ExpectNoError (err , "create class" )
274
+ namespace := createTestNamespace (tCtx )
275
+ claim := claimPrioritizedList .DeepCopy ()
276
+ claim .Namespace = namespace
277
+ claim , err = tCtx .Client ().ResourceV1beta1 ().ResourceClaims (namespace ).Create (tCtx , claim , metav1.CreateOptions {})
278
+
279
+ if ! enabled {
280
+ require .Error (tCtx , err , "claim should have become invalid after dropping FirstAvailable" )
281
+ return
282
+ }
283
+
284
+ require .NotEmpty (tCtx , claim .Spec .Devices .Requests [0 ].FirstAvailable , "should store FirstAvailable" )
285
+ tCtx .Run ("scheduler" , func (tCtx ktesting.TContext ) {
286
+ startScheduler (tCtx )
287
+
288
+ // The fake cluster configuration is not complete enough to actually schedule pods.
289
+ // That is covered over in test/integration/scheduler_perf.
290
+ // Here we only test that we get to the point where it notices that, without failing
291
+ // during PreFilter because of FirstAvailable.
292
+ pod := podWithClaimName .DeepCopy ()
293
+ pod .Namespace = namespace
294
+ _ , err := tCtx .Client ().CoreV1 ().Pods (namespace ).Create (tCtx , pod , metav1.CreateOptions {})
295
+ tCtx .ExpectNoError (err , "create pod" )
296
+ schedulingAttempted := gomega .HaveField ("Status.Conditions" , gomega .ContainElement (
297
+ gstruct .MatchFields (gstruct .IgnoreExtras , gstruct.Fields {
298
+ "Type" : gomega .Equal (v1 .PodScheduled ),
299
+ "Status" : gomega .Equal (v1 .ConditionFalse ),
300
+ "Reason" : gomega .Equal ("Unschedulable" ),
301
+ "Message" : gomega .Equal ("no nodes available to schedule pods" ),
302
+ }),
303
+ ))
304
+ ktesting .Eventually (tCtx , func (tCtx ktesting.TContext ) * v1.Pod {
305
+ pod , err := tCtx .Client ().CoreV1 ().Pods (namespace ).Get (tCtx , pod .Name , metav1.GetOptions {})
306
+ tCtx .ExpectNoError (err , "get pod" )
307
+ return pod
308
+ }).WithTimeout (time .Minute ).WithPolling (time .Second ).Should (schedulingAttempted )
309
+ })
310
+ }
0 commit comments