1
2
3
4
19
20 package scheduling
21
22 import (
23 "context"
24 "os"
25 "regexp"
26 "time"
27
28 appsv1 "k8s.io/api/apps/v1"
29 v1 "k8s.io/api/core/v1"
30 "k8s.io/apimachinery/pkg/api/resource"
31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32 "k8s.io/apimachinery/pkg/util/uuid"
33 extensionsinternal "k8s.io/kubernetes/pkg/apis/extensions"
34 "k8s.io/kubernetes/test/e2e/feature"
35 "k8s.io/kubernetes/test/e2e/framework"
36 e2edebug "k8s.io/kubernetes/test/e2e/framework/debug"
37 e2egpu "k8s.io/kubernetes/test/e2e/framework/gpu"
38 e2ejob "k8s.io/kubernetes/test/e2e/framework/job"
39 e2emanifest "k8s.io/kubernetes/test/e2e/framework/manifest"
40 e2enode "k8s.io/kubernetes/test/e2e/framework/node"
41 e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
42 "k8s.io/kubernetes/test/e2e/framework/providers/gce"
43 e2eresource "k8s.io/kubernetes/test/e2e/framework/resource"
44 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
45 e2etestfiles "k8s.io/kubernetes/test/e2e/framework/testfiles"
46 imageutils "k8s.io/kubernetes/test/utils/image"
47 admissionapi "k8s.io/pod-security-admission/api"
48
49 "github.com/onsi/ginkgo/v2"
50 "github.com/onsi/gomega"
51 )
52
53 const (
54 testPodNamePrefix = "nvidia-gpu-"
55
56 driverInstallTimeout = 10 * time.Minute
57 )
58
59 var (
60 gpuResourceName v1.ResourceName
61 )
62
63 func makeCudaAdditionDevicePluginTestPod() *v1.Pod {
64 podName := testPodNamePrefix + string(uuid.NewUUID())
65 testContainers := []v1.Container{
66 {
67 Name: "vector-addition-cuda8",
68 Image: imageutils.GetE2EImage(imageutils.CudaVectorAdd),
69 Resources: v1.ResourceRequirements{
70 Limits: v1.ResourceList{
71 gpuResourceName: *resource.NewQuantity(1, resource.DecimalSI),
72 },
73 },
74 },
75 {
76 Name: "vector-addition-cuda10",
77 Image: imageutils.GetE2EImage(imageutils.CudaVectorAdd2),
78 Resources: v1.ResourceRequirements{
79 Limits: v1.ResourceList{
80 gpuResourceName: *resource.NewQuantity(1, resource.DecimalSI),
81 },
82 },
83 },
84 }
85 testPod := &v1.Pod{
86 ObjectMeta: metav1.ObjectMeta{
87 Name: podName,
88 },
89 Spec: v1.PodSpec{
90 RestartPolicy: v1.RestartPolicyNever,
91 },
92 }
93
94 testPod.Spec.Containers = testContainers
95 if os.Getenv("TEST_MAX_GPU_COUNT") == "1" {
96 testPod.Spec.Containers = []v1.Container{testContainers[0]}
97 }
98 framework.Logf("testPod.Spec.Containers {%#v}", testPod.Spec.Containers)
99 return testPod
100 }
101
102 func logOSImages(ctx context.Context, f *framework.Framework) {
103 nodeList, err := f.ClientSet.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
104 framework.ExpectNoError(err, "getting node list")
105 for _, node := range nodeList.Items {
106 framework.Logf("Nodename: %v, OS Image: %v", node.Name, node.Status.NodeInfo.OSImage)
107 }
108 }
109
110 func isControlPlaneNode(node v1.Node) bool {
111 _, isControlPlane := node.Labels["node-role.kubernetes.io/control-plane"]
112 if isControlPlane {
113 framework.Logf("Node: %q is a control-plane node (label)", node.Name)
114 return true
115 }
116
117 for _, taint := range node.Spec.Taints {
118 if taint.Key == "node-role.kubernetes.io/control-plane" {
119 framework.Logf("Node: %q is a control-plane node (taint)", node.Name)
120 return true
121 }
122 }
123 framework.Logf("Node: %q is NOT a control-plane node", node.Name)
124 return false
125 }
126
127 func areGPUsAvailableOnAllSchedulableNodes(ctx context.Context, f *framework.Framework) bool {
128 framework.Logf("Getting list of Nodes from API server")
129 nodeList, err := f.ClientSet.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
130 framework.ExpectNoError(err, "getting node list")
131 for _, node := range nodeList.Items {
132 if node.Spec.Unschedulable || isControlPlaneNode(node) {
133 continue
134 }
135
136 if val, ok := node.Status.Capacity[gpuResourceName]; !ok || val.Value() == 0 {
137 framework.Logf("Nvidia GPUs not available on Node: %q", node.Name)
138 return false
139 }
140 }
141 framework.Logf("Nvidia GPUs exist on all schedulable worker nodes")
142 return true
143 }
144
145 func getGPUsAvailable(ctx context.Context, f *framework.Framework) int64 {
146 nodeList, err := f.ClientSet.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
147 framework.ExpectNoError(err, "getting node list")
148 var gpusAvailable int64
149 for _, node := range nodeList.Items {
150 if val, ok := node.Status.Allocatable[gpuResourceName]; ok {
151 gpusAvailable += (&val).Value()
152 }
153 }
154 return gpusAvailable
155 }
156
157
158 func SetupNVIDIAGPUNode(ctx context.Context, f *framework.Framework, setupResourceGatherer bool) *e2edebug.ContainerResourceGatherer {
159 logOSImages(ctx, f)
160
161 var err error
162 var ds *appsv1.DaemonSet
163 dsYamlURLFromEnv := os.Getenv("NVIDIA_DRIVER_INSTALLER_DAEMONSET")
164 if dsYamlURLFromEnv != "" {
165
166 framework.Logf("Using remote nvidia-driver-installer daemonset manifest from %v", dsYamlURLFromEnv)
167 ds, err = e2emanifest.DaemonSetFromURL(ctx, dsYamlURLFromEnv)
168 framework.ExpectNoError(err, "failed get remote")
169 } else {
170
171 framework.Logf("Using default local nvidia-driver-installer daemonset manifest.")
172 data, err := e2etestfiles.Read("test/e2e/testing-manifests/scheduling/nvidia-driver-installer.yaml")
173 framework.ExpectNoError(err, "failed to read local manifest for nvidia-driver-installer daemonset")
174 ds, err = e2emanifest.DaemonSetFromData(data)
175 framework.ExpectNoError(err, "failed to parse local manifest for nvidia-driver-installer daemonset")
176 }
177 gpuResourceName = e2egpu.NVIDIAGPUResourceName
178 ds.Namespace = f.Namespace.Name
179
180 _, err = f.ClientSet.AppsV1().DaemonSets(ds.Namespace).Create(ctx, ds, metav1.CreateOptions{})
181 framework.ExpectNoError(err, "failed to create nvidia-driver-installer daemonset")
182 framework.Logf("Successfully created daemonset to install Nvidia drivers.")
183
184 pods, err := e2eresource.WaitForControlledPods(ctx, f.ClientSet, ds.Namespace, ds.Name, extensionsinternal.Kind("DaemonSet"))
185 framework.ExpectNoError(err, "failed to get pods controlled by the nvidia-driver-installer daemonset")
186
187 devicepluginPods, err := e2eresource.WaitForControlledPods(ctx, f.ClientSet, "kube-system", "nvidia-gpu-device-plugin", extensionsinternal.Kind("DaemonSet"))
188 if err == nil {
189 framework.Logf("Adding deviceplugin addon pod.")
190 pods.Items = append(pods.Items, devicepluginPods.Items...)
191 }
192
193 var rsgather *e2edebug.ContainerResourceGatherer
194 if setupResourceGatherer {
195 framework.Logf("Starting ResourceUsageGather for the created DaemonSet pods.")
196 rsgather, err = e2edebug.NewResourceUsageGatherer(ctx, f.ClientSet, e2edebug.ResourceGathererOptions{InKubemark: false, Nodes: e2edebug.AllNodes, ResourceDataGatheringPeriod: 2 * time.Second, ProbeDuration: 2 * time.Second, PrintVerboseLogs: true}, pods)
197 framework.ExpectNoError(err, "creating ResourceUsageGather for the daemonset pods")
198 go rsgather.StartGatheringData(ctx)
199 }
200
201
202 framework.Logf("Waiting for drivers to be installed and GPUs to be available in Node Capacity...")
203 gomega.Eventually(ctx, func(ctx context.Context) bool {
204 return areGPUsAvailableOnAllSchedulableNodes(ctx, f)
205 }, driverInstallTimeout, time.Second).Should(gomega.BeTrue())
206
207 return rsgather
208 }
209
210 func getGPUsPerPod() int64 {
211 var gpusPerPod int64
212 gpuPod := makeCudaAdditionDevicePluginTestPod()
213 for _, container := range gpuPod.Spec.Containers {
214 if val, ok := container.Resources.Limits[gpuResourceName]; ok {
215 gpusPerPod += (&val).Value()
216 }
217 }
218 return gpusPerPod
219 }
220
221 func testNvidiaGPUs(ctx context.Context, f *framework.Framework) {
222 rsgather := SetupNVIDIAGPUNode(ctx, f, true)
223 gpuPodNum := getGPUsAvailable(ctx, f) / getGPUsPerPod()
224 framework.Logf("Creating %d pods and have the pods run a CUDA app", gpuPodNum)
225 podList := []*v1.Pod{}
226 for i := int64(0); i < gpuPodNum; i++ {
227 podList = append(podList, e2epod.NewPodClient(f).Create(ctx, makeCudaAdditionDevicePluginTestPod()))
228 }
229 framework.Logf("Wait for all test pods to succeed")
230
231 for _, pod := range podList {
232 e2epod.NewPodClient(f).WaitForSuccess(ctx, pod.Name, 5*time.Minute)
233 logContainers(ctx, f, pod)
234 }
235
236 framework.Logf("Stopping ResourceUsageGather")
237 constraints := make(map[string]e2edebug.ResourceConstraint)
238
239 summary, err := rsgather.StopAndSummarize([]int{50, 90, 100}, constraints)
240 f.TestSummaries = append(f.TestSummaries, summary)
241 framework.ExpectNoError(err, "getting resource usage summary")
242 }
243
244 func logContainers(ctx context.Context, f *framework.Framework, pod *v1.Pod) {
245 for _, container := range pod.Spec.Containers {
246 logs, err := e2epod.GetPodLogs(ctx, f.ClientSet, f.Namespace.Name, pod.Name, container.Name)
247 framework.ExpectNoError(err, "Should be able to get container logs for container: %s", container.Name)
248 framework.Logf("Got container logs for %s:\n%v", container.Name, logs)
249 }
250 }
251
252 var _ = SIGDescribe(feature.GPUDevicePlugin, func() {
253 f := framework.NewDefaultFramework("device-plugin-gpus")
254 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
255 ginkgo.It("run Nvidia GPU Device Plugin tests", func(ctx context.Context) {
256 testNvidiaGPUs(ctx, f)
257 })
258 })
259
260 func testNvidiaGPUsJob(ctx context.Context, f *framework.Framework) {
261 _ = SetupNVIDIAGPUNode(ctx, f, false)
262
263 completions := int32(5)
264 ginkgo.By("Starting GPU job")
265 StartJob(ctx, f, completions)
266
267 job, err := e2ejob.GetJob(ctx, f.ClientSet, f.Namespace.Name, "cuda-add")
268 framework.ExpectNoError(err)
269
270
271 err = e2ejob.WaitForJobPodsRunning(ctx, f.ClientSet, f.Namespace.Name, job.Name, 1)
272 framework.ExpectNoError(err)
273
274 numNodes, err := e2enode.TotalRegistered(ctx, f.ClientSet)
275 framework.ExpectNoError(err)
276 nodes, err := e2enode.CheckReady(ctx, f.ClientSet, numNodes, framework.NodeReadyInitialTimeout)
277 framework.ExpectNoError(err)
278
279 ginkgo.By("Recreating nodes")
280 err = gce.RecreateNodes(f.ClientSet, nodes)
281 framework.ExpectNoError(err)
282 ginkgo.By("Done recreating nodes")
283
284 ginkgo.By("Waiting for gpu job to finish")
285 err = e2ejob.WaitForJobFinish(ctx, f.ClientSet, f.Namespace.Name, job.Name)
286 framework.ExpectNoError(err)
287 ginkgo.By("Done with gpu job")
288
289 gomega.Expect(job.Status.Failed).To(gomega.BeZero(), "Job pods failed during node recreation: %v", job.Status.Failed)
290
291 VerifyJobNCompletions(ctx, f, completions)
292 }
293
294
295 func StartJob(ctx context.Context, f *framework.Framework, completions int32) {
296 var activeSeconds int64 = 3600
297 testJob := e2ejob.NewTestJob("succeed", "cuda-add", v1.RestartPolicyAlways, 1, completions, &activeSeconds, 6)
298 testJob.Spec.Template.Spec = v1.PodSpec{
299 RestartPolicy: v1.RestartPolicyOnFailure,
300 Containers: []v1.Container{
301 {
302 Name: "vector-addition",
303 Image: imageutils.GetE2EImage(imageutils.CudaVectorAdd),
304 Command: []string{"/bin/sh", "-c", "./vectorAdd && sleep 60"},
305 Resources: v1.ResourceRequirements{
306 Limits: v1.ResourceList{
307 gpuResourceName: *resource.NewQuantity(1, resource.DecimalSI),
308 },
309 },
310 },
311 },
312 }
313 ns := f.Namespace.Name
314 _, err := e2ejob.CreateJob(ctx, f.ClientSet, ns, testJob)
315 framework.ExpectNoError(err)
316 framework.Logf("Created job %v", testJob)
317 }
318
319
320 func VerifyJobNCompletions(ctx context.Context, f *framework.Framework, completions int32) {
321 ns := f.Namespace.Name
322 pods, err := e2ejob.GetJobPods(ctx, f.ClientSet, f.Namespace.Name, "cuda-add")
323 framework.ExpectNoError(err)
324 createdPods := pods.Items
325 createdPodNames := podNames(createdPods)
326 framework.Logf("Got the following pods for job cuda-add: %v", createdPodNames)
327
328 successes := int32(0)
329 regex := regexp.MustCompile("PASSED")
330 for _, podName := range createdPodNames {
331 e2epod.NewPodClient(f).WaitForFinish(ctx, podName, 5*time.Minute)
332 logs, err := e2epod.GetPodLogs(ctx, f.ClientSet, ns, podName, "vector-addition")
333 framework.ExpectNoError(err, "Should be able to get logs for pod %v", podName)
334 if regex.MatchString(logs) {
335 successes++
336 }
337 }
338 if successes != completions {
339 framework.Failf("Only got %v completions. Expected %v completions.", successes, completions)
340 }
341 }
342
343 func podNames(pods []v1.Pod) []string {
344 originalPodNames := make([]string, len(pods))
345 for i, p := range pods {
346 originalPodNames[i] = p.ObjectMeta.Name
347 }
348 return originalPodNames
349 }
350
351 var _ = SIGDescribe("GPUDevicePluginAcrossRecreate", feature.Recreate, func() {
352 ginkgo.BeforeEach(func() {
353 e2eskipper.SkipUnlessProviderIs("gce", "gke")
354 })
355 f := framework.NewDefaultFramework("device-plugin-gpus-recreate")
356 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
357 ginkgo.It("run Nvidia GPU Device Plugin tests with a recreation", func(ctx context.Context) {
358 testNvidiaGPUsJob(ctx, f)
359 })
360 })
361
View as plain text