• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

NVIDIA / gpu-operator / 29513807383

16 Jul 2026 03:58PM UTC coverage: 32.688% (+0.9%) from 31.812%
29513807383

Pull #2571

github

karthikvetrivel
Add golden render tests for GPUCluster operand manifests

Signed-off-by: Karthik Vetrivel <kvetrivel@nvidia.com>
Pull Request #2571: Add GPUCluster CRD and controller for DRA-based stack

513 of 1343 new or added lines in 31 files covered. (38.2%)

5 existing lines in 4 files now uncovered.

4663 of 14265 relevant lines covered (32.69%)

0.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

10.15
/cmd/nvidia-validator/main.go
1
/*
2
 * Copyright (c) 2021, NVIDIA CORPORATION.  All rights reserved.
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 main
18

19
import (
20
        "context"
21
        "fmt"
22
        "os"
23
        "os/exec"
24
        "os/signal"
25
        "path/filepath"
26
        "regexp"
27
        "strings"
28
        "syscall"
29
        "time"
30

31
        "github.com/NVIDIA/go-nvlib/pkg/nvmdev"
32
        "github.com/NVIDIA/go-nvlib/pkg/nvpci"
33
        devchar "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk/system/create-dev-char-symlinks"
34
        log "github.com/sirupsen/logrus"
35
        "github.com/stretchr/testify/assert/yaml"
36
        cli "github.com/urfave/cli/v3"
37
        corev1 "k8s.io/api/core/v1"
38
        "k8s.io/apimachinery/pkg/api/resource"
39
        meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
40
        "k8s.io/apimachinery/pkg/fields"
41
        "k8s.io/apimachinery/pkg/labels"
42
        "k8s.io/apimachinery/pkg/runtime/serializer/json"
43
        "k8s.io/apimachinery/pkg/util/wait"
44
        "k8s.io/client-go/kubernetes"
45
        "k8s.io/client-go/kubernetes/scheme"
46
        "k8s.io/client-go/rest"
47

48
        nvidiav1 "github.com/NVIDIA/gpu-operator/api/nvidia/v1"
49
        nvidiav1alpha1 "github.com/NVIDIA/gpu-operator/api/nvidia/v1alpha1"
50
        "github.com/NVIDIA/gpu-operator/internal/driverroot"
51
        "github.com/NVIDIA/gpu-operator/internal/info"
52
        "github.com/NVIDIA/gpu-operator/internal/utils"
53
)
54

55
// Component of GPU operator
56
type Component interface {
57
        validate() error
58
        createStatusFile() error
59
        deleteStatusFile() error
60
}
61

62
// Driver component
63
type Driver struct {
64
        ctx context.Context
65
}
66

67
// NvidiaFs GDS Driver component
68
type NvidiaFs struct{}
69

70
// GDRCopy driver component
71
type GDRCopy struct{}
72

73
// NvidiaPeermem driver component
74
type NvidiaPeermem struct{}
75

76
// CUDA represents spec to run cuda workload
77
type CUDA struct {
78
        ctx        context.Context
79
        kubeClient kubernetes.Interface
80
}
81

82
// Plugin component
83
type Plugin struct {
84
        ctx        context.Context
85
        kubeClient kubernetes.Interface
86
}
87

88
// Toolkit component
89
type Toolkit struct{}
90

91
// MOFED represents spec to validate MOFED driver installation
92
type MOFED struct {
93
        ctx        context.Context
94
        kubeClient kubernetes.Interface
95
}
96

97
// Metrics represents spec to run metrics exporter
98
type Metrics struct {
99
        ctx context.Context
100
}
101

102
// VfioPCI represents spec to validate vfio-pci driver
103
type VfioPCI struct {
104
        ctx context.Context
105
}
106

107
// VGPUManager represents spec to validate vGPU Manager installation
108
type VGPUManager struct {
109
        ctx context.Context
110
}
111

112
// VGPUDevices represents spec to validate vGPU device creation
113
type VGPUDevices struct {
114
        ctx context.Context
115
}
116

117
// CCManager represents spec to validate CC Manager installation
118
type CCManager struct {
119
        ctx        context.Context
120
        kubeClient kubernetes.Interface
121
}
122

123
var (
124
        kubeconfigFlag                string
125
        nodeNameFlag                  string
126
        namespaceFlag                 string
127
        withWaitFlag                  bool
128
        withWorkloadFlag              bool
129
        componentFlag                 string
130
        cleanupAllFlag                bool
131
        outputDirFlag                 string
132
        sleepIntervalSecondsFlag      int
133
        migStrategyFlag               string
134
        metricsPort                   int
135
        defaultGPUWorkloadConfigFlag  string
136
        disableDevCharSymlinkCreation bool
137
        hostRootFlag                  string
138
        driverInstallDirFlag          string
139
        driverInstallDirCtrPathFlag   string
140
)
141

142
// defaultGPUWorkloadConfig is "vm-passthrough" unless
143
// overridden by defaultGPUWorkloadConfigFlag
144
var defaultGPUWorkloadConfig = gpuWorkloadConfigVMPassthrough
145

146
const (
147
        // defaultStatusPath indicates directory to create all validation status files
148
        defaultStatusPath = "/run/nvidia/validations"
149
        // defaultSleepIntervalSeconds indicates sleep interval in seconds between validation command retries
150
        defaultSleepIntervalSeconds = 5
151
        // defaultMetricsPort indicates the port on which the metrics will be exposed.
152
        defaultMetricsPort = 0
153
        // hostDevCharPath indicates the path in the container where the host '/dev/char' directory is mounted to
154
        hostDevCharPath = "/host-dev-char"
155
        // nvidiaModuleRefcntPath is the path to check if the nvidia kernel module is loaded
156
        nvidiaModuleRefcntPath = "/sys/module/nvidia/refcnt"
157
        // defaultDriverInstallDir indicates the default path on the host where the driver container installation is made available
158
        defaultDriverInstallDir = "/run/nvidia/driver"
159
        // defaultDriverInstallDirCtrPath indicates the default path where the NVIDIA driver install dir is mounted in the container
160
        defaultDriverInstallDirCtrPath = "/run/nvidia/driver"
161
        // driverContainerStatusFilePath indicates the path to the driver container status file
162
        // which also contains additional drivers status flags
163
        driverContainerStatusFilePath = defaultStatusPath + "/.driver-ctr-ready"
164
        // driverStatusFile indicates status file for containerized driver readiness
165
        driverStatusFile = "driver-ready"
166
        // nvidiaFsStatusFile indicates status file for nvidia-fs driver readiness
167
        nvidiaFsStatusFile = "nvidia-fs-ready"
168
        // gdrCopyStatusFile indicates status file for GDRCopy driver (gdrdrv) readiness
169
        gdrCopyStatusFile = "gdrcopy-ready"
170
        // nvidiaPeermemStatusFile indicates status file for nvidia-peermem driver readiness
171
        nvidiaPeermemStatusFile = "nvidia-peermem-ready"
172
        // toolkitStatusFile indicates status file for toolkit readiness
173
        toolkitStatusFile = "toolkit-ready"
174
        // pluginStatusFile indicates status file for plugin readiness
175
        pluginStatusFile = "plugin-ready"
176
        // cudaStatusFile indicates status file for cuda readiness
177
        cudaStatusFile = "cuda-ready"
178
        // mofedStatusFile indicates status file for mofed driver readiness
179
        mofedStatusFile = "mofed-ready"
180
        // vfioPCIStatusFile indicates status file for vfio-pci driver readiness
181
        vfioPCIStatusFile = "vfio-pci-ready"
182
        // vGPUManagerStatusFile indicates status file for vGPU Manager driver readiness
183
        vGPUManagerStatusFile = "vgpu-manager-ready"
184
        // hostVGPUManagerStatusFile indicates status file for host vGPU Manager driver readiness
185
        hostVGPUManagerStatusFile = "host-vgpu-manager-ready"
186
        // vGPUDevicesStatusFile is name of the file which indicates vGPU Manager is installed and vGPU devices have been created
187
        vGPUDevicesStatusFile = "vgpu-devices-ready"
188
        // ccManagerStatusFile indicates status file for cc-manager readiness
189
        ccManagerStatusFile = "cc-manager-ready"
190
        // workloadTypeStatusFile is the name of the file which specifies the workload type configured for the node
191
        workloadTypeStatusFile = "workload-type"
192
        // podCreationWaitRetries indicates total retries to wait for plugin validation pod creation
193
        podCreationWaitRetries = 60
194
        // podCreationSleepIntervalSeconds indicates sleep interval in seconds between checking for plugin validation pod readiness
195
        podCreationSleepIntervalSeconds = 5
196
        // gpuResourceDiscoveryWaitRetries indicates total retries to wait for node to discovery GPU resources
197
        gpuResourceDiscoveryWaitRetries = 30
198
        // gpuResourceDiscoveryIntervalSeconds indicates sleep interval in seconds between checking for available GPU resources
199
        gpuResourceDiscoveryIntervalSeconds = 5
200
        // genericGPUResourceType indicates the generic name of the GPU exposed by NVIDIA DevicePlugin
201
        genericGPUResourceType = "nvidia.com/gpu"
202
        // migGPUResourcePrefix indicates the prefix of the MIG resources exposed by NVIDIA DevicePlugin
203
        migGPUResourcePrefix = "nvidia.com/mig-"
204
        // migStrategySingle indicates mixed MIG strategy
205
        migStrategySingle = "single"
206
        // pluginWorkloadPodSpecPath indicates path to plugin validation pod definition
207
        pluginWorkloadPodSpecPath = "/opt/validator/manifests/plugin-workload-validation.yaml"
208
        // cudaWorkloadPodSpecPath indicates path to cuda validation pod definition
209
        cudaWorkloadPodSpecPath = "/opt/validator/manifests/cuda-workload-validation.yaml"
210
        // validatorImageEnvName indicates env name for validator image passed
211
        validatorImageEnvName = "VALIDATOR_IMAGE"
212
        // validatorImagePullPolicyEnvName indicates env name for validator image pull policy passed
213
        validatorImagePullPolicyEnvName = "VALIDATOR_IMAGE_PULL_POLICY"
214
        // validatorImagePullSecretsEnvName indicates env name for validator image pull secrets passed
215
        validatorImagePullSecretsEnvName = "VALIDATOR_IMAGE_PULL_SECRETS"
216
        // validatorRuntimeClassEnvName indicates env name for validator runtimeclass passed
217
        validatorRuntimeClassEnvName = "VALIDATOR_RUNTIME_CLASS"
218
        // cudaValidatorLabelValue represents label for cuda workload validation pod
219
        cudaValidatorLabelValue = "nvidia-cuda-validator"
220
        // pluginValidatorLabelValue represents label for device-plugin workload validation pod
221
        pluginValidatorLabelValue = "nvidia-device-plugin-validator"
222
        // MellanoxDeviceLabelKey represents NFD label name for Mellanox devices
223
        MellanoxDeviceLabelKey = "feature.node.kubernetes.io/pci-15b3.present"
224
        // GPUDirectRDMAEnabledEnvName represents env name to indicate if GPUDirect RDMA is enabled through GPU Operator
225
        GPUDirectRDMAEnabledEnvName = "GPU_DIRECT_RDMA_ENABLED"
226
        // UseHostMOFEDEnvname represents env name to indicate if MOFED is pre-installed on host
227
        UseHostMOFEDEnvname = "USE_HOST_MOFED"
228
        // TODO: create a common package to share these variables between operator and validator
229
        gpuWorkloadConfigLabelKey      = "nvidia.com/gpu.workload.config"
230
        gpuWorkloadConfigContainer     = "container"
231
        gpuWorkloadConfigVMPassthrough = "vm-passthrough"
232
        gpuWorkloadConfigVMVgpu        = "vm-vgpu"
233
        // CCCapableLabelKey represents NFD label name to indicate if the node is capable to run CC workloads
234
        CCCapableLabelKey = "nvidia.com/cc.capable"
235
        // appComponentLabelKey indicates the label key of the component
236
        appComponentLabelKey = "app.kubernetes.io/component"
237
        // wslNvidiaSMIPath indicates the path to the nvidia-smi binary on WSL
238
        wslNvidiaSMIPath = "/usr/lib/wsl/lib/nvidia-smi"
239
        // shell indicates what shell to use when invoking commands in a subprocess
240
        shell = "sh"
241
        // defaultVFWaitTimeout is the default timeout for waiting for VFs to be created
242
        defaultVFWaitTimeout = 5 * time.Minute
243
        // constants for driver components
244
        GDRCOPY       = "gdrcopy"
245
        NVIDIAFS      = "nvidia-fs"
246
        NVIDIAPEERMEM = "nvidia-peermem"
247
)
248

249
func main() {
×
250
        c := cli.Command{}
×
251

×
252
        c.Before = validateFlags
×
253
        c.Action = start
×
254
        c.Version = info.GetVersionString()
×
255

×
256
        c.Flags = []cli.Flag{
×
257
                &cli.StringFlag{
×
258
                        Name:        "kubeconfig",
×
259
                        Value:       "",
×
260
                        Usage:       "absolute path to the kubeconfig file",
×
261
                        Destination: &kubeconfigFlag,
×
262
                        Sources:     cli.EnvVars("KUBECONFIG"),
×
263
                },
×
264
                &cli.StringFlag{
×
265
                        Name:        "node-name",
×
266
                        Aliases:     []string{"n"},
×
267
                        Value:       "",
×
268
                        Usage:       "the name of the node to deploy plugin validation pod",
×
269
                        Destination: &nodeNameFlag,
×
270
                        Sources:     cli.EnvVars("NODE_NAME"),
×
271
                },
×
272
                &cli.StringFlag{
×
273
                        Name:        "namespace",
×
274
                        Aliases:     []string{"ns"},
×
275
                        Value:       "",
×
276
                        Usage:       "the namespace in which the operator resources are deployed",
×
277
                        Destination: &namespaceFlag,
×
278
                        Sources:     cli.EnvVars("OPERATOR_NAMESPACE"),
×
279
                },
×
280
                &cli.BoolFlag{
×
281
                        Name:        "with-wait",
×
282
                        Aliases:     []string{"w"},
×
283
                        Value:       false,
×
284
                        Usage:       "indicates to wait for validation to complete successfully",
×
285
                        Destination: &withWaitFlag,
×
286
                        Sources:     cli.EnvVars("WITH_WAIT"),
×
287
                },
×
288
                &cli.BoolFlag{
×
289
                        Name:        "with-workload",
×
290
                        Aliases:     []string{"l"},
×
291
                        Value:       true,
×
292
                        Usage:       "indicates to validate with GPU workload",
×
293
                        Destination: &withWorkloadFlag,
×
294
                        Sources:     cli.EnvVars("WITH_WORKLOAD"),
×
295
                },
×
296
                &cli.StringFlag{
×
297
                        Name:        "component",
×
298
                        Aliases:     []string{"c"},
×
299
                        Value:       "",
×
300
                        Usage:       "the name of the operator component to validate",
×
301
                        Destination: &componentFlag,
×
302
                        Sources:     cli.EnvVars("COMPONENT"),
×
303
                },
×
304
                &cli.BoolFlag{
×
305
                        Name:        "cleanup-all",
×
306
                        Aliases:     []string{"r"},
×
307
                        Value:       false,
×
308
                        Usage:       "indicates to cleanup all previous validation status files",
×
309
                        Destination: &cleanupAllFlag,
×
310
                        Sources:     cli.EnvVars("CLEANUP_ALL"),
×
311
                },
×
312
                &cli.StringFlag{
×
313
                        Name:        "output-dir",
×
314
                        Aliases:     []string{"o"},
×
315
                        Value:       defaultStatusPath,
×
316
                        Usage:       "output directory where all validation status files are created",
×
317
                        Destination: &outputDirFlag,
×
318
                        Sources:     cli.EnvVars("OUTPUT_DIR"),
×
319
                },
×
320
                &cli.IntFlag{
×
321
                        Name:        "sleep-interval-seconds",
×
322
                        Aliases:     []string{"s"},
×
323
                        Value:       defaultSleepIntervalSeconds,
×
324
                        Usage:       "sleep interval in seconds between command retries",
×
325
                        Destination: &sleepIntervalSecondsFlag,
×
326
                        Sources:     cli.EnvVars("SLEEP_INTERVAL_SECONDS"),
×
327
                },
×
328
                &cli.StringFlag{
×
329
                        Name:        "mig-strategy",
×
330
                        Aliases:     []string{"m"},
×
331
                        Value:       migStrategySingle,
×
332
                        Usage:       "MIG Strategy",
×
333
                        Destination: &migStrategyFlag,
×
334
                        Sources:     cli.EnvVars("MIG_STRATEGY"),
×
335
                },
×
336
                &cli.IntFlag{
×
337
                        Name:        "metrics-port",
×
338
                        Aliases:     []string{"p"},
×
339
                        Value:       defaultMetricsPort,
×
340
                        Usage:       "port on which the metrics will be exposed. 0 means disabled.",
×
341
                        Destination: &metricsPort,
×
342
                        Sources:     cli.EnvVars("METRICS_PORT"),
×
343
                },
×
344
                &cli.StringFlag{
×
345
                        Name:        "default-gpu-workload-config",
×
346
                        Aliases:     []string{"g"},
×
347
                        Value:       "",
×
348
                        Usage:       "default GPU workload config. determines what components to validate by default when sandbox workloads are enabled in the cluster.",
×
349
                        Destination: &defaultGPUWorkloadConfigFlag,
×
350
                        Sources:     cli.EnvVars("DEFAULT_GPU_WORKLOAD_CONFIG"),
×
351
                },
×
352
                &cli.BoolFlag{
×
353
                        Name:        "disable-dev-char-symlink-creation",
×
354
                        Value:       false,
×
355
                        Usage:       "disable creation of symlinks under /dev/char corresponding to NVIDIA character devices",
×
356
                        Destination: &disableDevCharSymlinkCreation,
×
357
                        Sources:     cli.EnvVars("DISABLE_DEV_CHAR_SYMLINK_CREATION"),
×
358
                },
×
359
                &cli.StringFlag{
×
360
                        Name:        "host-root",
×
361
                        Value:       "/",
×
362
                        Usage:       "root path of the underlying host",
×
363
                        Destination: &hostRootFlag,
×
364
                        Sources:     cli.EnvVars("HOST_ROOT"),
×
365
                },
×
366
                &cli.StringFlag{
×
367
                        Name:        "driver-install-dir",
×
368
                        Value:       defaultDriverInstallDir,
×
369
                        Usage:       "the path on the host where a containerized NVIDIA driver installation is made available",
×
370
                        Destination: &driverInstallDirFlag,
×
371
                        Sources:     cli.EnvVars("DRIVER_INSTALL_DIR"),
×
372
                },
×
373
                &cli.StringFlag{
×
374
                        Name:        "driver-install-dir-ctr-path",
×
375
                        Value:       defaultDriverInstallDirCtrPath,
×
376
                        Usage:       "the path where the NVIDIA driver install dir is mounted in the container",
×
377
                        Destination: &driverInstallDirCtrPathFlag,
×
378
                        Sources:     cli.EnvVars("DRIVER_INSTALL_DIR_CTR_PATH"),
×
379
                },
×
380
        }
×
381

×
382
        // Log version info
×
383
        log.Infof("version: %s", c.Version)
×
384

×
385
        // Handle signals
×
386
        go handleSignal()
×
387

×
388
        // invoke command
×
389
        err := c.Run(context.Background(), os.Args)
×
390
        if err != nil {
×
391
                log.SetOutput(os.Stderr)
×
392
                log.Printf("Error: %v", err)
×
393
                os.Exit(1)
×
394
        }
×
395
}
396

397
func handleSignal() {
×
398
        // Handle signals
×
399
        stop := make(chan os.Signal, 1)
×
400
        signal.Notify(stop, os.Interrupt,
×
401
                syscall.SIGTERM, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT)
×
402

×
403
        s := <-stop
×
404
        log.Fatalf("Exiting due to signal [%v] notification for pid [%d]", s.String(), os.Getpid())
×
405
}
×
406

407
func validateFlags(ctx context.Context, cli *cli.Command) (context.Context, error) {
×
408
        if componentFlag == "" {
×
409
                return ctx, fmt.Errorf("invalid -c <component-name> flag: must not be empty string")
×
410
        }
×
411
        if !isValidComponent() {
×
412
                return ctx, fmt.Errorf("invalid -c <component-name> flag value: %s", componentFlag)
×
413
        }
×
414
        if componentFlag == "plugin" {
×
415
                if nodeNameFlag == "" {
×
416
                        return ctx, fmt.Errorf("invalid -n <node-name> flag: must not be empty string for plugin validation")
×
417
                }
×
418
                if namespaceFlag == "" {
×
419
                        return ctx, fmt.Errorf("invalid -ns <namespace> flag: must not be empty string for plugin validation")
×
420
                }
×
421
        }
422
        if componentFlag == "cuda" && namespaceFlag == "" {
×
423
                return ctx, fmt.Errorf("invalid -ns <namespace> flag: must not be empty string for cuda validation")
×
424
        }
×
425
        if componentFlag == "metrics" {
×
426
                if metricsPort == defaultMetricsPort {
×
427
                        return ctx, fmt.Errorf("invalid -p <port> flag: must not be empty or 0 for the metrics component")
×
428
                }
×
429
                if nodeNameFlag == "" {
×
430
                        return ctx, fmt.Errorf("invalid -n <node-name> flag: must not be empty string for metrics exporter")
×
431
                }
×
432
        }
433
        if nodeNameFlag == "" && (componentFlag == "vfio-pci" || componentFlag == "vgpu-manager" || componentFlag == "vgpu-devices") {
×
434
                return ctx, fmt.Errorf("invalid -n <node-name> flag: must not be empty string for %s validation", componentFlag)
×
435
        }
×
436

437
        return ctx, nil
×
438
}
439

440
func isValidComponent() bool {
1✔
441
        switch componentFlag {
1✔
442
        case "driver":
1✔
443
                fallthrough
1✔
444
        case "toolkit":
1✔
445
                fallthrough
1✔
446
        case "cuda":
1✔
447
                fallthrough
1✔
448
        case "metrics":
1✔
449
                fallthrough
1✔
450
        case "plugin":
1✔
451
                fallthrough
1✔
452
        case "mofed":
1✔
453
                fallthrough
1✔
454
        case "vfio-pci":
1✔
455
                fallthrough
1✔
456
        case "vgpu-manager":
1✔
457
                fallthrough
1✔
458
        case "vgpu-devices":
1✔
459
                fallthrough
1✔
460
        case "cc-manager":
1✔
461
                fallthrough
1✔
462
        case NVIDIAFS:
1✔
463
                fallthrough
1✔
464
        case GDRCOPY:
1✔
465
                fallthrough
1✔
466
        case NVIDIAPEERMEM:
1✔
467
                return true
1✔
468
        default:
1✔
469
                return false
1✔
470
        }
471
}
472

473
func isValidWorkloadConfig(config string) bool {
×
474
        return config == gpuWorkloadConfigContainer ||
×
475
                config == gpuWorkloadConfigVMPassthrough ||
×
476
                config == gpuWorkloadConfigVMVgpu
×
477
}
×
478

479
func getWorkloadConfig(ctx context.Context) (string, error) {
×
480
        // check if default workload is overridden by flag
×
481
        if isValidWorkloadConfig(defaultGPUWorkloadConfigFlag) {
×
482
                defaultGPUWorkloadConfig = defaultGPUWorkloadConfigFlag
×
483
        }
×
484

485
        kubeConfig, err := rest.InClusterConfig()
×
486
        if err != nil {
×
487
                return "", fmt.Errorf("error getting cluster config - %s", err.Error())
×
488
        }
×
489

490
        kubeClient, err := kubernetes.NewForConfig(kubeConfig)
×
491
        if err != nil {
×
492
                return "", fmt.Errorf("error getting k8s client - %w", err)
×
493
        }
×
494

495
        node, err := getNode(ctx, kubeClient)
×
496
        if err != nil {
×
497
                return "", fmt.Errorf("error getting node labels - %w", err)
×
498
        }
×
499

500
        labels := node.GetLabels()
×
501
        value, ok := labels[gpuWorkloadConfigLabelKey]
×
502
        if !ok {
×
503
                log.Infof("No %s label found; using default workload config: %s", gpuWorkloadConfigLabelKey, defaultGPUWorkloadConfig)
×
504
                return defaultGPUWorkloadConfig, nil
×
505
        }
×
506
        if !isValidWorkloadConfig(value) {
×
507
                log.Warnf("%s is an invalid workload config; using default workload config: %s", value, defaultGPUWorkloadConfig)
×
508
                return defaultGPUWorkloadConfig, nil
×
509
        }
×
510
        return value, nil
×
511
}
512

513
func start(ctx context.Context, cli *cli.Command) error {
×
514
        // if cleanup is requested, delete all existing status files(default)
×
515
        if cleanupAllFlag {
×
516
                // cleanup output directory and create again each time
×
517
                err := os.RemoveAll(outputDirFlag)
×
518
                if err != nil {
×
519
                        if !os.IsNotExist(err) {
×
520
                                return err
×
521
                        }
×
522
                }
523
        }
524

525
        // create status directory
526
        err := os.Mkdir(outputDirFlag, 0755)
×
527
        if err != nil && !os.IsExist(err) {
×
528
                return err
×
529
        }
×
530

531
        return validateComponent(ctx, componentFlag)
×
532
}
533

534
func validateComponent(ctx context.Context, componentFlag string) error {
1✔
535
        switch componentFlag {
1✔
536
        case "driver":
×
537
                driver := &Driver{
×
538
                        ctx: ctx,
×
539
                }
×
540
                err := driver.validate()
×
541
                if err != nil {
×
542
                        return fmt.Errorf("error validating driver installation: %w", err)
×
543
                }
×
544
                return nil
×
545
        case NVIDIAFS:
1✔
546
                nvidiaFs := &NvidiaFs{}
1✔
547
                err := nvidiaFs.validate()
1✔
548
                if err != nil {
2✔
549
                        return fmt.Errorf("error validating nvidia-fs driver installation: %w", err)
1✔
550
                }
1✔
551
                return nil
×
552
        case GDRCOPY:
1✔
553
                gdrcopy := &GDRCopy{}
1✔
554
                err := gdrcopy.validate()
1✔
555
                if err != nil {
2✔
556
                        return fmt.Errorf("error validating gdrcopy driver installation: %w", err)
1✔
557
                }
1✔
558
                return nil
×
559
        case NVIDIAPEERMEM:
1✔
560
                nvidiaPeermem := &NvidiaPeermem{}
1✔
561
                err := nvidiaPeermem.validate()
1✔
562
                if err != nil {
2✔
563
                        return fmt.Errorf("error validating nvidia-peermem driver installation: %w", err)
1✔
564
                }
1✔
565
                return nil
×
566
        case "toolkit":
×
567
                toolkit := &Toolkit{}
×
568
                err := toolkit.validate()
×
569
                if err != nil {
×
570
                        return fmt.Errorf("error validating toolkit installation: %w", err)
×
571
                }
×
572
                return nil
×
573
        case "cuda":
×
574
                cuda := &CUDA{
×
575
                        ctx: ctx,
×
576
                }
×
577
                err := cuda.validate()
×
578
                if err != nil {
×
579
                        return fmt.Errorf("error validating cuda workload: %w", err)
×
580
                }
×
581
                return nil
×
582
        case "plugin":
×
583
                plugin := &Plugin{
×
584
                        ctx: ctx,
×
585
                }
×
586
                err := plugin.validate()
×
587
                if err != nil {
×
588
                        return fmt.Errorf("error validating plugin installation: %w", err)
×
589
                }
×
590
                return nil
×
591
        case "mofed":
×
592
                mofed := &MOFED{
×
593
                        ctx: ctx,
×
594
                }
×
595
                err := mofed.validate()
×
596
                if err != nil {
×
597
                        return fmt.Errorf("error validating MOFED driver installation: %s", err)
×
598
                }
×
599
                return nil
×
600
        case "metrics":
×
601
                metrics := &Metrics{
×
602
                        ctx: ctx,
×
603
                }
×
604
                err := metrics.run()
×
605
                if err != nil {
×
606
                        return fmt.Errorf("error running validation-metrics exporter: %s", err)
×
607
                }
×
608
                return nil
×
609
        case "vfio-pci":
×
610
                vfioPCI := &VfioPCI{
×
611
                        ctx: ctx,
×
612
                }
×
613
                err := vfioPCI.validate()
×
614
                if err != nil {
×
615
                        return fmt.Errorf("error validating vfio-pci driver installation: %w", err)
×
616
                }
×
617
                return nil
×
618
        case "vgpu-manager":
×
619
                vGPUManager := &VGPUManager{
×
620
                        ctx: ctx,
×
621
                }
×
622
                err := vGPUManager.validate()
×
623
                if err != nil {
×
624
                        return fmt.Errorf("error validating vGPU Manager installation: %w", err)
×
625
                }
×
626
                return nil
×
627
        case "vgpu-devices":
×
628
                vGPUDevices := &VGPUDevices{
×
629
                        ctx: ctx,
×
630
                }
×
631
                err := vGPUDevices.validate()
×
632
                if err != nil {
×
633
                        return fmt.Errorf("error validating vGPU devices: %s", err)
×
634
                }
×
635
                return nil
×
636
        case "cc-manager":
×
637
                CCManager := &CCManager{
×
638
                        ctx: ctx,
×
639
                }
×
640
                err := CCManager.validate()
×
641
                if err != nil {
×
642
                        return fmt.Errorf("error validating CC Manager installation: %w", err)
×
643
                }
×
644
                return nil
×
645
        default:
×
646
                return fmt.Errorf("invalid component specified for validation: %s", componentFlag)
×
647
        }
648
}
649

650
func runCommand(command string, args []string, silent bool) error {
1✔
651
        cmd := exec.Command(command, args...)
1✔
652
        if !silent {
2✔
653
                cmd.Stdout = os.Stdout
1✔
654
                cmd.Stderr = os.Stderr
1✔
655
        }
1✔
656
        return cmd.Run()
1✔
657
}
658

659
func runCommandWithWait(command string, args []string, sleepSeconds int, silent bool) error {
×
660
        for {
×
661
                cmd := exec.Command(command, args...)
×
662
                if !silent {
×
663
                        cmd.Stdout = os.Stdout
×
664
                        cmd.Stderr = os.Stderr
×
665
                }
×
666
                fmt.Printf("running command %s with args %v\n", command, args)
×
667
                err := cmd.Run()
×
668
                if err != nil {
×
669
                        log.Warningf("error running command: %v", err)
×
670
                        fmt.Printf("command failed, retrying after %d seconds\n", sleepSeconds)
×
671
                        time.Sleep(time.Duration(sleepSeconds) * time.Second)
×
672
                        continue
×
673
                }
674
                return nil
×
675
        }
676
}
677

678
// For driver container installs, check existence of .driver-ctr-ready to confirm running driver
679
// container has completed and is in Ready state.
680
func assertDriverContainerReady(silent bool) error {
×
681
        command := shell
×
682
        args := []string{"-c", fmt.Sprintf("stat %s", driverContainerStatusFilePath)}
×
683

×
684
        if withWaitFlag {
×
685
                return runCommandWithWait(command, args, sleepIntervalSecondsFlag, silent)
×
686
        }
×
687

688
        return runCommand(command, args, silent)
×
689
}
690

691
// isDriverManagedByOperator determines if the NVIDIA driver is managed by the GPU Operator.
692
// We check if at least one driver DaemonSet exists in the operator namespace that is
693
// owned by the ClusterPolicy or NVIDIADriver controllers.
694
func isDriverManagedByOperator(ctx context.Context) (bool, error) {
×
695
        kubeConfig, err := rest.InClusterConfig()
×
696
        if err != nil {
×
697
                return false, fmt.Errorf("error getting cluster config: %w", err)
×
698
        }
×
699

700
        kubeClient, err := kubernetes.NewForConfig(kubeConfig)
×
701
        if err != nil {
×
702
                return false, fmt.Errorf("error getting k8s client: %w", err)
×
703
        }
×
704

705
        opts := meta_v1.ListOptions{LabelSelector: labels.Set{appComponentLabelKey: "nvidia-driver"}.AsSelector().String()}
×
706
        dsList, err := kubeClient.AppsV1().DaemonSets(namespaceFlag).List(ctx, opts)
×
707
        if err != nil {
×
708
                return false, fmt.Errorf("error listing daemonsets: %w", err)
×
709
        }
×
710

711
        for i := range dsList.Items {
×
712
                ds := dsList.Items[i]
×
713
                owner := meta_v1.GetControllerOf(&ds)
×
714
                if owner == nil {
×
715
                        continue
×
716
                }
717
                if strings.HasPrefix(owner.APIVersion, "nvidia.com/") && (owner.Kind == nvidiav1.ClusterPolicyCRDName || owner.Kind == nvidiav1alpha1.NVIDIADriverCRDName) {
×
718
                        return true, nil
×
719
                }
×
720
        }
721

722
        return false, nil
×
723
}
724

UNCOV
725
func validateHostDriver(silent bool) error {
×
UNCOV
726
        log.Info("Attempting to validate a pre-installed driver on the host")
×
727
        if fileInfo, err := os.Lstat(filepath.Join("/host", wslNvidiaSMIPath)); err == nil && fileInfo.Size() != 0 {
×
728
                log.Infof("WSL2 system detected, assuming driver is pre-installed")
×
729
                disableDevCharSymlinkCreation = true
×
730
                return nil
×
731
        }
×
732

NEW
733
        fileInfo, err := driverroot.ResolveHostNvidiaSMI("/host")
×
734
        if err != nil {
×
735
                return err
×
736
        }
×
737
        if fileInfo.Size() == 0 {
×
738
                return fmt.Errorf("empty 'nvidia-smi' file found on the host")
×
739
        }
×
740
        command := "chroot"
×
741
        args := []string{"/host", "nvidia-smi"}
×
742

×
743
        return runCommand(command, args, silent)
×
744
}
745

746
func validateDriverContainer(silent bool, driverManagedByOperator bool) error {
×
747
        if driverManagedByOperator {
×
748
                log.Infof("Driver is not pre-installed on the host and is managed by GPU Operator. Checking driver container status.")
×
749
                if err := assertDriverContainerReady(silent); err != nil {
×
750
                        return fmt.Errorf("error checking driver container status: %w", err)
×
751
                }
×
752
        }
753

NEW
754
        driverRoot := driverroot.Root(driverInstallDirCtrPathFlag)
×
755

×
756
        validateDriver := func(silent bool) error {
×
NEW
757
                driverLibraryPath, err := driverRoot.GetDriverLibraryPath()
×
758
                if err != nil {
×
759
                        return fmt.Errorf("failed to locate driver libraries: %w", err)
×
760
                }
×
761

NEW
762
                nvidiaSMIPath, err := driverRoot.GetNvidiaSMIPath()
×
763
                if err != nil {
×
764
                        return fmt.Errorf("failed to locate nvidia-smi: %w", err)
×
765
                }
×
766
                cmd := exec.Command(nvidiaSMIPath)
×
767
                // In order for nvidia-smi to run, we need to update LD_PRELOAD to include the path to libnvidia-ml.so.1.
×
NEW
768
                cmd.Env = utils.SetEnvVar(os.Environ(), "LD_PRELOAD", utils.PrependPathListEnvvar("LD_PRELOAD", driverLibraryPath))
×
769
                if !silent {
×
770
                        cmd.Stdout = os.Stdout
×
771
                        cmd.Stderr = os.Stderr
×
772
                }
×
773
                return cmd.Run()
×
774
        }
775

776
        for {
×
777
                log.Info("Attempting to validate a driver container installation")
×
778
                err := validateDriver(silent)
×
779
                if err != nil {
×
780
                        if !withWaitFlag {
×
781
                                return fmt.Errorf("error validating driver: %w", err)
×
782
                        }
×
783
                        log.Warningf("failed to validate the driver, retrying after %d seconds\n", sleepIntervalSecondsFlag)
×
784
                        time.Sleep(time.Duration(sleepIntervalSecondsFlag) * time.Second)
×
785
                        continue
×
786
                }
787
                return nil
×
788
        }
789
}
790

791
func (d *Driver) runValidation(silent bool) (driverInfo, error) {
×
792
        err := validateHostDriver(silent)
×
793
        if err == nil {
×
794
                log.Info("Detected a pre-installed driver on the host")
×
795
                return getDriverInfo(true, hostRootFlag, hostRootFlag, "/host"), nil
×
796
        }
×
797

798
        log.Infof("No pre-installed driver detected on the host: %v", err)
×
799

×
800
        log.Info("Validating containerized driver installation")
×
801

×
802
        driverManagedByOperator, err := isDriverManagedByOperator(d.ctx)
×
803
        if err != nil {
×
804
                return driverInfo{}, fmt.Errorf("error checking if driver is managed by GPU Operator: %w", err)
×
805
        }
×
806

807
        err = validateDriverContainer(silent, driverManagedByOperator)
×
808
        if err != nil {
×
809
                return driverInfo{}, err
×
810
        }
×
811

812
        if driverManagedByOperator {
×
813
                err = validateAdditionalDriverComponents(d.ctx, driverContainerStatusFilePath)
×
814
                if err != nil {
×
815
                        return driverInfo{}, err
×
816
                }
×
817
        }
818
        return getDriverInfo(false, hostRootFlag, driverInstallDirFlag, driverInstallDirCtrPathFlag), nil
×
819
}
820

821
// validateAdditionalDriverComponents validates additional driver components such as
822
// gdrcopy, gds, nvidia-peermem(rdma) if they are enabled.
823
func validateAdditionalDriverComponents(ctx context.Context, statusFilePath string) error {
1✔
824
        data, err := os.ReadFile(statusFilePath)
1✔
825
        if err != nil {
2✔
826
                return err
1✔
827
        }
1✔
828

829
        supportedFeatures := map[string]string{
1✔
830
                "GDRCOPY_ENABLED":         GDRCOPY,
1✔
831
                "GDS_ENABLED":             NVIDIAFS,
1✔
832
                "GPU_DIRECT_RDMA_ENABLED": NVIDIAPEERMEM,
1✔
833
        }
1✔
834

1✔
835
        features := map[string]bool{}
1✔
836
        if err := yaml.Unmarshal(data, &features); err != nil {
2✔
837
                return err
1✔
838
        }
1✔
839

840
        for k, enabled := range features {
2✔
841
                if !enabled {
2✔
842
                        log.Debugf("%s is disabled, skipping...", k)
1✔
843
                        continue
1✔
844
                }
845

846
                component, ok := supportedFeatures[k]
1✔
847
                if !ok {
2✔
848
                        log.Infof("unsupported feature flag: %s, skipping...", k)
1✔
849
                        continue
1✔
850
                }
851

852
                log.Infof("Validating additional driver component: %s", component)
1✔
853
                if err := validateComponent(ctx, component); err != nil {
2✔
854
                        return err
1✔
855
                }
1✔
856
        }
857

858
        return nil
1✔
859
}
860

861
func (d *Driver) validate() error {
×
862
        // delete driver status file if already present
×
863
        err := deleteStatusFile(outputDirFlag + "/" + driverStatusFile)
×
864
        if err != nil {
×
865
                return err
×
866
        }
×
867

868
        driverInfo, err := d.runValidation(false)
×
869
        if err != nil {
×
870
                log.Errorf("driver is not ready: %v", err)
×
871
                return err
×
872
        }
×
873

874
        err = createDevCharSymlinks(driverInfo, disableDevCharSymlinkCreation)
×
875
        if err != nil {
×
876
                msg := strings.Join([]string{
×
877
                        "Failed to create symlinks under /dev/char that point to all possible NVIDIA character devices.",
×
878
                        "The existence of these symlinks is required to address the following bug:",
×
879
                        "",
×
880
                        "    https://github.com/NVIDIA/gpu-operator/issues/430",
×
881
                        "",
×
882
                        "This bug impacts container runtimes configured with systemd cgroup management enabled.",
×
883
                        "To disable the symlink creation, set the following envvar in ClusterPolicy:",
×
884
                        "",
×
885
                        "    validator:",
×
886
                        "      driver:",
×
887
                        "        env:",
×
888
                        "        - name: DISABLE_DEV_CHAR_SYMLINK_CREATION",
×
889
                        "          value: \"true\""}, "\n")
×
890
                return fmt.Errorf("%w\n\n%s", err, msg)
×
891
        }
×
892

893
        return d.createStatusFile(driverInfo)
×
894
}
895

896
func (d *Driver) createStatusFile(driverInfo driverInfo) error {
×
897
        statusFileContent := strings.Join([]string{
×
898
                fmt.Sprintf("IS_HOST_DRIVER=%t", driverInfo.isHostDriver),
×
899
                fmt.Sprintf("NVIDIA_DRIVER_ROOT=%s", driverInfo.driverRoot),
×
900
                fmt.Sprintf("DRIVER_ROOT_CTR_PATH=%s", driverInfo.driverRootCtrPath),
×
901
                fmt.Sprintf("NVIDIA_DEV_ROOT=%s", driverInfo.devRoot),
×
902
                fmt.Sprintf("DEV_ROOT_CTR_PATH=%s", driverInfo.devRootCtrPath),
×
903
        }, "\n") + "\n"
×
904

×
905
        // create driver status file
×
NEW
906
        return utils.WriteFileAtomically(outputDirFlag+"/"+driverStatusFile, statusFileContent)
×
907
}
×
908

909
// isNvidiaModuleLoaded checks if NVIDIA kernel module is already loaded in kernel memory.
910
func isNvidiaModuleLoaded() bool {
×
911
        // Check if the nvidia module is loaded by checking if nvidiaModuleRefcntPath exists
×
912
        if _, err := os.Stat(nvidiaModuleRefcntPath); err == nil {
×
913
                refcntData, err := os.ReadFile(nvidiaModuleRefcntPath)
×
914
                if err == nil {
×
915
                        refcnt := strings.TrimSpace(string(refcntData))
×
916
                        log.Infof("NVIDIA kernel module already loaded in kernel memory (refcnt=%s)", refcnt)
×
917
                        return true
×
918
                }
×
919
        }
920
        return false
×
921
}
922

923
// createDevCharSymlinks creates symlinks in /host-dev-char that point to all possible NVIDIA devices nodes.
924
func createDevCharSymlinks(driverInfo driverInfo, disableDevCharSymlinkCreation bool) error {
×
925
        if disableDevCharSymlinkCreation {
×
926
                log.WithField("disableDevCharSymlinkCreation", true).
×
927
                        Info("skipping the creation of symlinks under /dev/char that correspond to NVIDIA character devices")
×
928
                return nil
×
929
        }
×
930

931
        log.Info("creating symlinks under /dev/char that correspond to NVIDIA character devices")
×
932

×
933
        // Check if NVIDIA module is already loaded in kernel memory.
×
934
        // If it is, we don't need to run modprobe (which would fail if modules aren't in /lib/modules/).
×
935
        // This handles the case where the driver container performed a userspace-only install
×
936
        // after detecting that module was already loaded from a previous boot.
×
937
        moduleAlreadyLoaded := isNvidiaModuleLoaded()
×
938

×
939
        // Only attempt to load NVIDIA kernel modules when:
×
940
        // 1. Module is not already loaded in kernel memory, AND
×
941
        // 2. We can chroot into driverRoot to run modprobe
×
942
        loadKernelModules := !moduleAlreadyLoaded && (driverInfo.isHostDriver || (driverInfo.devRoot == driverInfo.driverRoot))
×
943

×
944
        // driverRootCtrPath is the path of the driver install dir in the container. This will either be
×
945
        // driverInstallDirCtrPathFlag or '/host'.
×
946
        // Note, if we always mounted the driver install dir to '/driver-root' in the validation container
×
947
        // instead, then we could simplify to always use driverInfo.driverRootCtrPath -- which would be
×
948
        // either '/host' or '/driver-root', both paths would exist in the validation container.
×
949
        driverRootCtrPath := driverInstallDirCtrPathFlag
×
950
        if driverInfo.isHostDriver {
×
951
                driverRootCtrPath = "/host"
×
952
        }
×
953

954
        // We now create the symlinks in /dev/char.
955
        creator, err := devchar.NewSymlinkCreator(
×
956
                devchar.WithDriverRoot(driverRootCtrPath),
×
957
                devchar.WithDevRoot(driverInfo.devRoot),
×
958
                devchar.WithDevCharPath(hostDevCharPath),
×
959
                devchar.WithCreateAll(true),
×
960
                devchar.WithCreateDeviceNodes(true),
×
961
                devchar.WithLoadKernelModules(loadKernelModules),
×
962
        )
×
963
        if err != nil {
×
964
                return fmt.Errorf("error creating symlink creator: %w", err)
×
965
        }
×
966

967
        err = creator.CreateLinks()
×
968
        if err != nil {
×
969
                return fmt.Errorf("error creating symlinks: %w", err)
×
970
        }
×
971

972
        return nil
×
973
}
974

975
func createStatusFile(statusFile string) error {
×
976
        _, err := os.Create(statusFile)
×
977
        if err != nil {
×
978
                return fmt.Errorf("unable to create status file %s: %s", statusFile, err)
×
979
        }
×
980
        return nil
×
981
}
982

983
func deleteStatusFile(statusFile string) error {
1✔
984
        err := os.Remove(statusFile)
1✔
985
        if err != nil {
2✔
986
                if !os.IsNotExist(err) {
1✔
987
                        return fmt.Errorf("unable to remove driver status file %s: %w", statusFile, err)
×
988
                }
×
989
                // status file already removed
990
        }
991
        return nil
1✔
992
}
993

994
func (n *NvidiaFs) validate() error {
1✔
995
        // delete driver status file if already present
1✔
996
        err := deleteStatusFile(outputDirFlag + "/" + nvidiaFsStatusFile)
1✔
997
        if err != nil {
1✔
998
                return err
×
999
        }
×
1000

1001
        err = n.runValidation(false)
1✔
1002
        if err != nil {
2✔
1003
                fmt.Println("nvidia-fs driver is not ready")
1✔
1004
                return err
1✔
1005
        }
1✔
1006

1007
        // create driver status file
1008
        err = createStatusFile(outputDirFlag + "/" + nvidiaFsStatusFile)
×
1009
        if err != nil {
×
1010
                return err
×
1011
        }
×
1012
        return nil
×
1013
}
1014

1015
func (n *NvidiaFs) runValidation(silent bool) error {
1✔
1016
        // check for nvidia_fs module to be loaded
1✔
1017
        command := shell
1✔
1018
        args := []string{"-c", "lsmod | grep nvidia_fs"}
1✔
1019

1✔
1020
        if withWaitFlag {
1✔
1021
                return runCommandWithWait(command, args, sleepIntervalSecondsFlag, silent)
×
1022
        }
×
1023
        return runCommand(command, args, silent)
1✔
1024
}
1025

1026
func (g *GDRCopy) validate() error {
1✔
1027
        // delete driver status file if already present
1✔
1028
        err := deleteStatusFile(outputDirFlag + "/" + gdrCopyStatusFile)
1✔
1029
        if err != nil {
1✔
1030
                return err
×
1031
        }
×
1032

1033
        err = g.runValidation(false)
1✔
1034
        if err != nil {
2✔
1035
                log.Info("gdrcopy driver is not ready")
1✔
1036
                return err
1✔
1037
        }
1✔
1038

1039
        // create driver status file
1040
        err = createStatusFile(outputDirFlag + "/" + gdrCopyStatusFile)
×
1041
        if err != nil {
×
1042
                return err
×
1043
        }
×
1044
        return nil
×
1045
}
1046

1047
func (g *GDRCopy) runValidation(silent bool) error {
1✔
1048
        // check for gdrdrv module to be loaded
1✔
1049
        command := shell
1✔
1050
        args := []string{"-c", "lsmod | grep -E '^gdrdrv\\s'"}
1✔
1051

1✔
1052
        if withWaitFlag {
1✔
1053
                return runCommandWithWait(command, args, sleepIntervalSecondsFlag, silent)
×
1054
        }
×
1055
        return runCommand(command, args, silent)
1✔
1056
}
1057

1058
func (n *NvidiaPeermem) validate() error {
1✔
1059
        // delete driver status file if already present
1✔
1060
        err := deleteStatusFile(outputDirFlag + "/" + nvidiaPeermemStatusFile)
1✔
1061
        if err != nil {
1✔
1062
                return err
×
1063
        }
×
1064

1065
        err = n.runValidation(false)
1✔
1066
        if err != nil {
2✔
1067
                log.Info("nvidia-peermem driver is not ready")
1✔
1068
                return err
1✔
1069
        }
1✔
1070

1071
        // create driver status file
1072
        err = createStatusFile(outputDirFlag + "/" + nvidiaPeermemStatusFile)
×
1073
        if err != nil {
×
1074
                return err
×
1075
        }
×
1076
        return nil
×
1077
}
1078

1079
func (n *NvidiaPeermem) runValidation(silent bool) error {
1✔
1080
        // check for nvidia_peermem module to be loaded
1✔
1081
        command := shell
1✔
1082
        args := []string{"-c", "lsmod | grep -E '^nvidia_peermem\\s'"}
1✔
1083

1✔
1084
        if withWaitFlag {
1✔
1085
                return runCommandWithWait(command, args, sleepIntervalSecondsFlag, silent)
×
1086
        }
×
1087
        return runCommand(command, args, silent)
1✔
1088
}
1089

1090
func (t *Toolkit) validate() error {
×
1091
        // delete status file is already present
×
1092
        err := deleteStatusFile(outputDirFlag + "/" + toolkitStatusFile)
×
1093
        if err != nil {
×
1094
                return err
×
1095
        }
×
1096

1097
        // invoke nvidia-smi command to check if container run with toolkit injected files
1098
        command := "nvidia-smi"
×
1099
        args := []string{}
×
1100
        if withWaitFlag {
×
1101
                err = runCommandWithWait(command, args, sleepIntervalSecondsFlag, false)
×
1102
        } else {
×
1103
                err = runCommand(command, args, false)
×
1104
        }
×
1105
        if err != nil {
×
1106
                fmt.Println("toolkit is not ready")
×
1107
                return err
×
1108
        }
×
1109

1110
        // create toolkit status file
1111
        err = createStatusFile(outputDirFlag + "/" + toolkitStatusFile)
×
1112
        if err != nil {
×
1113
                return err
×
1114
        }
×
1115
        return nil
×
1116
}
1117

1118
func (p *Plugin) validate() error {
×
1119
        // delete status file is already present
×
1120
        err := deleteStatusFile(outputDirFlag + "/" + pluginStatusFile)
×
1121
        if err != nil {
×
1122
                return err
×
1123
        }
×
1124

1125
        // enumerate node resources and ensure GPU devices are discovered.
1126
        kubeConfig, err := rest.InClusterConfig()
×
1127
        if err != nil {
×
1128
                log.Errorf("Error getting config cluster - %s\n", err.Error())
×
1129
                return err
×
1130
        }
×
1131

1132
        kubeClient, err := kubernetes.NewForConfig(kubeConfig)
×
1133
        if err != nil {
×
1134
                log.Errorf("Error getting k8s client - %s\n", err.Error())
×
1135
                return err
×
1136
        }
×
1137

1138
        // update k8s client for the plugin
1139
        p.setKubeClient(kubeClient)
×
1140

×
1141
        err = p.validateGPUResource()
×
1142
        if err != nil {
×
1143
                return err
×
1144
        }
×
1145

1146
        if withWorkloadFlag {
×
1147
                // workload test
×
1148
                err = p.runWorkload()
×
1149
                if err != nil {
×
1150
                        return err
×
1151
                }
×
1152
        }
1153

1154
        // create plugin status file
1155
        err = createStatusFile(outputDirFlag + "/" + pluginStatusFile)
×
1156
        if err != nil {
×
1157
                return err
×
1158
        }
×
1159
        return nil
×
1160
}
1161

1162
func (m *MOFED) validate() error {
×
1163
        // If GPUDirectRDMA is disabled, skip validation
×
1164
        if os.Getenv(GPUDirectRDMAEnabledEnvName) != "true" {
×
1165
                log.Info("GPUDirect RDMA is disabled, skipping MOFED driver validation...")
×
1166
                return nil
×
1167
        }
×
1168

1169
        // Check node labels for Mellanox devices and MOFED driver status file
1170
        kubeConfig, err := rest.InClusterConfig()
×
1171
        if err != nil {
×
1172
                log.Errorf("Error getting config cluster - %s\n", err.Error())
×
1173
                return err
×
1174
        }
×
1175

1176
        kubeClient, err := kubernetes.NewForConfig(kubeConfig)
×
1177
        if err != nil {
×
1178
                log.Errorf("Error getting k8s client - %s\n", err.Error())
×
1179
                return err
×
1180
        }
×
1181

1182
        // update k8s client for the mofed driver validation
1183
        m.setKubeClient(kubeClient)
×
1184

×
1185
        present, err := m.isMellanoxDevicePresent()
×
1186
        if err != nil {
×
1187
                log.Errorf("Error trying to retrieve Mellanox device - %s\n", err.Error())
×
1188
                return err
×
1189
        }
×
1190
        if !present {
×
1191
                log.Info("No Mellanox device label found, skipping MOFED driver validation...")
×
1192
                return nil
×
1193
        }
×
1194

1195
        // delete status file is already present
1196
        err = deleteStatusFile(outputDirFlag + "/" + mofedStatusFile)
×
1197
        if err != nil {
×
1198
                return err
×
1199
        }
×
1200

1201
        err = m.runValidation(false)
×
1202
        if err != nil {
×
1203
                return err
×
1204
        }
×
1205

1206
        // delete status file is already present
1207
        err = createStatusFile(outputDirFlag + "/" + mofedStatusFile)
×
1208
        if err != nil {
×
1209
                return err
×
1210
        }
×
1211
        return nil
×
1212
}
1213

1214
func (m *MOFED) runValidation(silent bool) error {
×
1215
        // check for mlx5_core module to be loaded
×
1216
        command := shell
×
1217
        args := []string{"-c", "lsmod | grep mlx5_core"}
×
1218

×
1219
        // If MOFED container is running then use readiness flag set by the driver container instead
×
1220
        if os.Getenv(UseHostMOFEDEnvname) != "true" {
×
1221
                args = []string{"-c", "stat /run/mellanox/drivers/.driver-ready"}
×
1222
        }
×
1223
        if withWaitFlag {
×
1224
                return runCommandWithWait(command, args, sleepIntervalSecondsFlag, silent)
×
1225
        }
×
1226
        return runCommand(command, args, silent)
×
1227
}
1228

1229
func (m *MOFED) setKubeClient(kubeClient kubernetes.Interface) {
×
1230
        m.kubeClient = kubeClient
×
1231
}
×
1232

1233
func (m *MOFED) isMellanoxDevicePresent() (bool, error) {
×
1234
        node, err := getNode(m.ctx, m.kubeClient)
×
1235
        if err != nil {
×
1236
                return false, fmt.Errorf("unable to fetch node by name %s to check for Mellanox device label: %s", nodeNameFlag, err)
×
1237
        }
×
1238
        for key, value := range node.GetLabels() {
×
1239
                if key == MellanoxDeviceLabelKey && value == "true" {
×
1240
                        return true, nil
×
1241
                }
×
1242
        }
1243
        return false, nil
×
1244
}
1245

1246
func (p *Plugin) runWorkload() error {
×
1247
        ctx := p.ctx
×
1248
        // load podSpec
×
1249
        pod, err := loadPodSpec(pluginWorkloadPodSpecPath)
×
1250
        if err != nil {
×
1251
                return err
×
1252
        }
×
1253

1254
        pod.Namespace = namespaceFlag
×
1255
        image := os.Getenv(validatorImageEnvName)
×
1256
        pod.Spec.Containers[0].Image = image
×
1257
        pod.Spec.InitContainers[0].Image = image
×
1258

×
1259
        imagePullPolicy := os.Getenv(validatorImagePullPolicyEnvName)
×
1260
        if imagePullPolicy != "" {
×
1261
                pod.Spec.Containers[0].ImagePullPolicy = corev1.PullPolicy(imagePullPolicy)
×
1262
                pod.Spec.InitContainers[0].ImagePullPolicy = corev1.PullPolicy(imagePullPolicy)
×
1263
        }
×
1264

1265
        if os.Getenv(validatorImagePullSecretsEnvName) != "" {
×
1266
                pullSecrets := strings.Split(os.Getenv(validatorImagePullSecretsEnvName), ",")
×
1267
                for _, secret := range pullSecrets {
×
1268
                        pod.Spec.ImagePullSecrets = append(pod.Spec.ImagePullSecrets, corev1.LocalObjectReference{Name: secret})
×
1269
                }
×
1270
        }
1271
        if os.Getenv(validatorRuntimeClassEnvName) != "" {
×
1272
                runtimeClass := os.Getenv(validatorRuntimeClassEnvName)
×
1273
                pod.Spec.RuntimeClassName = &runtimeClass
×
1274
        }
×
1275

1276
        validatorDaemonset, err := p.kubeClient.AppsV1().DaemonSets(namespaceFlag).Get(ctx, "nvidia-operator-validator", meta_v1.GetOptions{})
×
1277
        if err != nil {
×
1278
                return fmt.Errorf("unable to retrieve the operator validator daemonset: %w", err)
×
1279
        }
×
1280

1281
        // update owner reference
1282
        pod.SetOwnerReferences(validatorDaemonset.OwnerReferences)
×
1283
        // set pod tolerations
×
1284
        pod.Spec.Tolerations = validatorDaemonset.Spec.Template.Spec.Tolerations
×
1285
        // update podSpec with node name, so it will just run on current node
×
1286
        pod.Spec.NodeName = nodeNameFlag
×
1287

×
1288
        resourceName, err := p.getGPUResourceName()
×
1289
        if err != nil {
×
1290
                return err
×
1291
        }
×
1292

1293
        gpuResource := corev1.ResourceList{
×
1294
                resourceName: resource.MustParse("1"),
×
1295
        }
×
1296

×
1297
        pod.Spec.InitContainers[0].Resources.Limits = gpuResource
×
1298
        pod.Spec.InitContainers[0].Resources.Requests = gpuResource
×
1299
        opts := meta_v1.ListOptions{LabelSelector: labels.Set{"app": pluginValidatorLabelValue}.AsSelector().String(),
×
1300
                FieldSelector: fields.Set{"spec.nodeName": nodeNameFlag}.AsSelector().String()}
×
1301

×
1302
        // check if plugin validation pod is already running and cleanup.
×
1303
        podList, err := p.kubeClient.CoreV1().Pods(namespaceFlag).List(ctx, opts)
×
1304
        if err != nil {
×
1305
                return fmt.Errorf("cannot list existing validation pods: %w", err)
×
1306
        }
×
1307

1308
        if podList != nil && len(podList.Items) > 0 {
×
1309
                propagation := meta_v1.DeletePropagationBackground
×
1310
                gracePeriod := int64(0)
×
1311
                options := meta_v1.DeleteOptions{PropagationPolicy: &propagation, GracePeriodSeconds: &gracePeriod}
×
1312
                err = p.kubeClient.CoreV1().Pods(namespaceFlag).Delete(ctx, podList.Items[0].Name, options)
×
1313
                if err != nil {
×
1314
                        return fmt.Errorf("cannot delete previous validation pod: %w", err)
×
1315
                }
×
1316
        }
1317

1318
        // wait for plugin validation pod to be ready.
1319
        newPod, err := p.kubeClient.CoreV1().Pods(namespaceFlag).Create(ctx, pod, meta_v1.CreateOptions{})
×
1320
        if err != nil {
×
1321
                return fmt.Errorf("failed to create plugin validation pod %s, err %w", pod.Name, err)
×
1322
        }
×
1323

1324
        // make sure it's available
1325
        err = waitForPod(ctx, p.kubeClient, newPod.Name, namespaceFlag)
×
1326
        if err != nil {
×
1327
                return err
×
1328
        }
×
1329
        return nil
×
1330
}
1331

1332
// waits for the pod to be created
1333
func waitForPod(ctx context.Context, kubeClient kubernetes.Interface, name string, namespace string) error {
×
1334
        for i := 0; i < podCreationWaitRetries; i++ {
×
1335
                // check for the existence of the resource
×
1336
                pod, err := kubeClient.CoreV1().Pods(namespace).Get(ctx, name, meta_v1.GetOptions{})
×
1337
                if err != nil {
×
1338
                        return fmt.Errorf("failed to get pod %s, err %w", name, err)
×
1339
                }
×
1340
                if pod.Status.Phase != "Succeeded" {
×
1341
                        log.Infof("pod %s is curently in %s phase", name, pod.Status.Phase)
×
1342
                        time.Sleep(podCreationSleepIntervalSeconds * time.Second)
×
1343
                        continue
×
1344
                }
1345
                log.Infof("pod %s have run successfully", name)
×
1346
                // successfully running
×
1347
                return nil
×
1348
        }
1349
        return fmt.Errorf("gave up waiting for pod %s to be available", name)
×
1350
}
1351

1352
func loadPodSpec(podSpecPath string) (*corev1.Pod, error) {
×
1353
        var pod corev1.Pod
×
1354
        manifest, err := os.ReadFile(podSpecPath)
×
1355
        if err != nil {
×
1356
                panic(err)
×
1357
        }
1358
        s := json.NewSerializerWithOptions(json.DefaultMetaFactory, scheme.Scheme,
×
1359
                scheme.Scheme, json.SerializerOptions{Yaml: true, Pretty: false, Strict: false})
×
1360
        reg := regexp.MustCompile(`\b(\w*kind:\w*)\B.*\b`)
×
1361

×
1362
        kind := reg.FindString(string(manifest))
×
1363
        slice := strings.Split(kind, ":")
×
1364
        kind = strings.TrimSpace(slice[1])
×
1365

×
1366
        log.Debugf("Decoding for Kind %s in path: %s", kind, podSpecPath)
×
1367
        _, _, err = s.Decode(manifest, nil, &pod)
×
1368
        if err != nil {
×
1369
                return nil, err
×
1370
        }
×
1371
        return &pod, nil
×
1372
}
1373

1374
func (p *Plugin) countGPUResources() (int64, error) {
×
1375
        // get node info to check discovered GPU resources
×
1376
        node, err := getNode(p.ctx, p.kubeClient)
×
1377
        if err != nil {
×
1378
                return -1, fmt.Errorf("unable to fetch node by name %s to check for GPU resources: %w", nodeNameFlag, err)
×
1379
        }
×
1380

1381
        count := int64(0)
×
1382

×
1383
        for resourceName, quantity := range node.Status.Capacity {
×
1384
                if !strings.HasPrefix(string(resourceName), migGPUResourcePrefix) && !strings.HasPrefix(string(resourceName), genericGPUResourceType) {
×
1385
                        continue
×
1386
                }
1387

1388
                count += quantity.Value()
×
1389
        }
1390
        return count, nil
×
1391
}
1392

1393
func (p *Plugin) validateGPUResource() error {
×
1394
        for retry := 1; retry <= gpuResourceDiscoveryWaitRetries; retry++ {
×
1395
                // get node info to check discovered GPU resources
×
1396
                node, err := getNode(p.ctx, p.kubeClient)
×
1397
                if err != nil {
×
1398
                        return fmt.Errorf("unable to fetch node by name %s to check for GPU resources: %s", nodeNameFlag, err)
×
1399
                }
×
1400

1401
                if p.availableMIGResourceName(node.Status.Capacity) != "" {
×
1402
                        return nil
×
1403
                }
×
1404

1405
                if p.availableGenericResourceName(node.Status.Capacity) != "" {
×
1406
                        return nil
×
1407
                }
×
1408

1409
                log.Infof("GPU resources are not yet discovered by the node, retry: %d", retry)
×
1410
                time.Sleep(gpuResourceDiscoveryIntervalSeconds * time.Second)
×
1411
        }
1412
        return fmt.Errorf("GPU resources are not discovered by the node")
×
1413
}
1414

1415
func (p *Plugin) availableMIGResourceName(resources corev1.ResourceList) corev1.ResourceName {
×
1416
        for resourceName, quantity := range resources {
×
1417
                if strings.HasPrefix(string(resourceName), migGPUResourcePrefix) && quantity.Value() >= 1 {
×
1418
                        log.Debugf("Found MIG GPU resource name %s quantity %d", resourceName, quantity.Value())
×
1419
                        return resourceName
×
1420
                }
×
1421
        }
1422
        return ""
×
1423
}
1424

1425
func (p *Plugin) availableGenericResourceName(resources corev1.ResourceList) corev1.ResourceName {
×
1426
        for resourceName, quantity := range resources {
×
1427
                if strings.HasPrefix(string(resourceName), genericGPUResourceType) && quantity.Value() >= 1 {
×
1428
                        log.Debugf("Found GPU resource name %s quantity %d", resourceName, quantity.Value())
×
1429
                        return resourceName
×
1430
                }
×
1431
        }
1432
        return ""
×
1433
}
1434

1435
func (p *Plugin) getGPUResourceName() (corev1.ResourceName, error) {
×
1436
        // get node info to check allocatable GPU resources
×
1437
        node, err := getNode(p.ctx, p.kubeClient)
×
1438
        if err != nil {
×
1439
                return "", fmt.Errorf("unable to fetch node by name %s to check for GPU resources: %s", nodeNameFlag, err)
×
1440
        }
×
1441

1442
        // use mig resource if one is available to run workload
1443
        if resourceName := p.availableMIGResourceName(node.Status.Allocatable); resourceName != "" {
×
1444
                return resourceName, nil
×
1445
        }
×
1446

1447
        if resourceName := p.availableGenericResourceName(node.Status.Allocatable); resourceName != "" {
×
1448
                return resourceName, nil
×
1449
        }
×
1450

1451
        return "", fmt.Errorf("unable to find any allocatable GPU resource")
×
1452
}
1453

1454
func (p *Plugin) setKubeClient(kubeClient kubernetes.Interface) {
×
1455
        p.kubeClient = kubeClient
×
1456
}
×
1457

1458
func getNode(ctx context.Context, kubeClient kubernetes.Interface) (*corev1.Node, error) {
×
1459
        node, err := kubeClient.CoreV1().Nodes().Get(ctx, nodeNameFlag, meta_v1.GetOptions{})
×
1460
        if err != nil {
×
1461
                log.Errorf("unable to get node with name %s, err %v", nodeNameFlag, err)
×
1462
                return nil, err
×
1463
        }
×
1464
        return node, nil
×
1465
}
1466

1467
func (c *CUDA) validate() error {
×
1468
        // delete status file is already present
×
1469
        err := deleteStatusFile(outputDirFlag + "/" + cudaStatusFile)
×
1470
        if err != nil {
×
1471
                return err
×
1472
        }
×
1473

1474
        // deploy workload pod for cuda validation
1475
        kubeConfig, err := rest.InClusterConfig()
×
1476
        if err != nil {
×
1477
                log.Errorf("Error getting config cluster - %s\n", err.Error())
×
1478
                return err
×
1479
        }
×
1480

1481
        kubeClient, err := kubernetes.NewForConfig(kubeConfig)
×
1482
        if err != nil {
×
1483
                log.Errorf("Error getting k8s client - %s\n", err.Error())
×
1484
                return err
×
1485
        }
×
1486

1487
        // update k8s client for the plugin
1488
        c.setKubeClient(kubeClient)
×
1489

×
1490
        if withWorkloadFlag {
×
1491
                // workload test
×
1492
                err = c.runWorkload()
×
1493
                if err != nil {
×
1494
                        return err
×
1495
                }
×
1496
        }
1497

1498
        // create plugin status file
1499
        err = createStatusFile(outputDirFlag + "/" + cudaStatusFile)
×
1500
        if err != nil {
×
1501
                return err
×
1502
        }
×
1503
        return nil
×
1504
}
1505

1506
func (c *CUDA) setKubeClient(kubeClient kubernetes.Interface) {
×
1507
        c.kubeClient = kubeClient
×
1508
}
×
1509

1510
func (c *CUDA) runWorkload() error {
×
1511
        ctx := c.ctx
×
1512

×
1513
        // load podSpec
×
1514
        pod, err := loadPodSpec(cudaWorkloadPodSpecPath)
×
1515
        if err != nil {
×
1516
                return err
×
1517
        }
×
1518
        pod.Namespace = namespaceFlag
×
1519
        image := os.Getenv(validatorImageEnvName)
×
1520
        pod.Spec.Containers[0].Image = image
×
1521
        pod.Spec.InitContainers[0].Image = image
×
1522

×
1523
        imagePullPolicy := os.Getenv(validatorImagePullPolicyEnvName)
×
1524
        if imagePullPolicy != "" {
×
1525
                pod.Spec.Containers[0].ImagePullPolicy = corev1.PullPolicy(imagePullPolicy)
×
1526
                pod.Spec.InitContainers[0].ImagePullPolicy = corev1.PullPolicy(imagePullPolicy)
×
1527
        }
×
1528

1529
        if os.Getenv(validatorImagePullSecretsEnvName) != "" {
×
1530
                pullSecrets := strings.Split(os.Getenv(validatorImagePullSecretsEnvName), ",")
×
1531
                for _, secret := range pullSecrets {
×
1532
                        pod.Spec.ImagePullSecrets = append(pod.Spec.ImagePullSecrets, corev1.LocalObjectReference{Name: secret})
×
1533
                }
×
1534
        }
1535
        if os.Getenv(validatorRuntimeClassEnvName) != "" {
×
1536
                runtimeClass := os.Getenv(validatorRuntimeClassEnvName)
×
1537
                pod.Spec.RuntimeClassName = &runtimeClass
×
1538
        }
×
1539

1540
        validatorDaemonset, err := c.kubeClient.AppsV1().DaemonSets(namespaceFlag).Get(ctx, "nvidia-operator-validator", meta_v1.GetOptions{})
×
1541
        if err != nil {
×
1542
                return fmt.Errorf("unable to retrieve the operator validator daemonset: %w", err)
×
1543
        }
×
1544

1545
        // update owner reference
1546
        pod.SetOwnerReferences(validatorDaemonset.OwnerReferences)
×
1547
        // set pod tolerations
×
1548
        pod.Spec.Tolerations = validatorDaemonset.Spec.Template.Spec.Tolerations
×
1549
        // update podSpec with node name, so it will just run on current node
×
1550
        pod.Spec.NodeName = nodeNameFlag
×
1551

×
1552
        opts := meta_v1.ListOptions{LabelSelector: labels.Set{"app": cudaValidatorLabelValue}.AsSelector().String(),
×
1553
                FieldSelector: fields.Set{"spec.nodeName": nodeNameFlag}.AsSelector().String()}
×
1554

×
1555
        // check if cuda workload pod is already running and cleanup.
×
1556
        podList, err := c.kubeClient.CoreV1().Pods(namespaceFlag).List(ctx, opts)
×
1557
        if err != nil {
×
1558
                return fmt.Errorf("cannot list existing validation pods: %s", err)
×
1559
        }
×
1560

1561
        if podList != nil && len(podList.Items) > 0 {
×
1562
                propagation := meta_v1.DeletePropagationBackground
×
1563
                gracePeriod := int64(0)
×
1564
                options := meta_v1.DeleteOptions{PropagationPolicy: &propagation, GracePeriodSeconds: &gracePeriod}
×
1565
                err = c.kubeClient.CoreV1().Pods(namespaceFlag).Delete(ctx, podList.Items[0].Name, options)
×
1566
                if err != nil {
×
1567
                        return fmt.Errorf("cannot delete previous validation pod: %s", err)
×
1568
                }
×
1569
        }
1570

1571
        // wait for cuda workload pod to be ready.
1572
        newPod, err := c.kubeClient.CoreV1().Pods(namespaceFlag).Create(ctx, pod, meta_v1.CreateOptions{})
×
1573
        if err != nil {
×
1574
                return fmt.Errorf("failed to create cuda validation pod %s, err %+v", pod.Name, err)
×
1575
        }
×
1576

1577
        // make sure it's available
1578
        err = waitForPod(ctx, c.kubeClient, newPod.Name, namespaceFlag)
×
1579
        if err != nil {
×
1580
                return err
×
1581
        }
×
1582
        return nil
×
1583
}
1584

1585
func (c *Metrics) run() error {
×
1586
        m := NewNodeMetrics(c.ctx, metricsPort)
×
1587

×
1588
        return m.Run()
×
1589
}
×
1590

1591
func (v *VfioPCI) validate() error {
×
1592
        ctx := v.ctx
×
1593

×
1594
        gpuWorkloadConfig, err := getWorkloadConfig(ctx)
×
1595
        if err != nil {
×
1596
                return fmt.Errorf("error getting gpu workload config: %w", err)
×
1597
        }
×
1598
        log.Infof("GPU workload configuration: %s", gpuWorkloadConfig)
×
1599

×
NEW
1600
        err = utils.WriteFileAtomically(filepath.Join(outputDirFlag, workloadTypeStatusFile), gpuWorkloadConfig+"\n")
×
1601
        if err != nil {
×
1602
                return fmt.Errorf("error updating %s status file: %w", workloadTypeStatusFile, err)
×
1603
        }
×
1604

1605
        if gpuWorkloadConfig != gpuWorkloadConfigVMPassthrough {
×
1606
                log.WithFields(log.Fields{
×
1607
                        "gpuWorkloadConfig": gpuWorkloadConfig,
×
1608
                }).Info("vfio-pci not required on the node. Skipping validation.")
×
1609
                return nil
×
1610
        }
×
1611

1612
        // delete status file if already present
1613
        err = deleteStatusFile(outputDirFlag + "/" + vfioPCIStatusFile)
×
1614
        if err != nil {
×
1615
                return err
×
1616
        }
×
1617

1618
        err = v.runValidation()
×
1619
        if err != nil {
×
1620
                return err
×
1621
        }
×
1622
        log.Info("Validation completed successfully - all devices are bound to vfio-pci")
×
1623

×
1624
        // delete status file is already present
×
1625
        err = createStatusFile(outputDirFlag + "/" + vfioPCIStatusFile)
×
1626
        if err != nil {
×
1627
                return err
×
1628
        }
×
1629
        return nil
×
1630
}
1631

1632
func (v *VfioPCI) runValidation() error {
×
1633
        nvpci := nvpci.New()
×
1634
        nvdevices, err := nvpci.GetGPUs()
×
1635
        if err != nil {
×
1636
                return fmt.Errorf("error getting NVIDIA PCI devices: %w", err)
×
1637
        }
×
1638

1639
        for _, dev := range nvdevices {
×
1640
                // TODO: Do not hardcode a list of VFIO driver names. This would be possible if we
×
1641
                // added an API to go-nvlib which returns the most suitable VFIO driver for a GPU,
×
1642
                // using logic similar to https://github.com/NVIDIA/k8s-driver-manager/commit/874c8cd26d775db437f16a42c3e44e74301b3a35
×
1643
                if dev.Driver != "vfio-pci" && dev.Driver != "nvgrace_gpu_vfio_pci" {
×
1644
                        return fmt.Errorf("device not bound to 'vfio-pci'; device: %s driver: '%s'", dev.Address, dev.Driver)
×
1645
                }
×
1646
        }
1647

1648
        return nil
×
1649
}
1650

1651
func (v *VGPUManager) validate() error {
×
1652
        ctx := v.ctx
×
1653

×
1654
        gpuWorkloadConfig, err := getWorkloadConfig(ctx)
×
1655
        if err != nil {
×
1656
                return fmt.Errorf("error getting gpu workload config: %w", err)
×
1657
        }
×
1658
        log.Infof("GPU workload configuration: %s", gpuWorkloadConfig)
×
1659

×
NEW
1660
        err = utils.WriteFileAtomically(filepath.Join(outputDirFlag, workloadTypeStatusFile), gpuWorkloadConfig+"\n")
×
1661
        if err != nil {
×
1662
                return fmt.Errorf("error updating %s status file: %w", workloadTypeStatusFile, err)
×
1663
        }
×
1664

1665
        if gpuWorkloadConfig != gpuWorkloadConfigVMVgpu {
×
1666
                log.WithFields(log.Fields{
×
1667
                        "gpuWorkloadConfig": gpuWorkloadConfig,
×
1668
                }).Info("vGPU Manager not required on the node. Skipping validation.")
×
1669
                return nil
×
1670
        }
×
1671

1672
        // delete status file if already present
1673
        err = deleteStatusFile(outputDirFlag + "/" + vGPUManagerStatusFile)
×
1674
        if err != nil {
×
1675
                return err
×
1676
        }
×
1677

1678
        // delete status file if already present
1679
        err = deleteStatusFile(outputDirFlag + "/" + hostVGPUManagerStatusFile)
×
1680
        if err != nil {
×
1681
                return err
×
1682
        }
×
1683

1684
        hostDriver, err := v.runValidation(false)
×
1685
        if err != nil {
×
1686
                fmt.Println("vGPU Manager is not ready")
×
1687
                return err
×
1688
        }
×
1689

1690
        log.Info("Waiting for VFs to be available...")
×
1691
        if err := waitForVFs(ctx, defaultVFWaitTimeout); err != nil {
×
1692
                return fmt.Errorf("vGPU Manager VFs not ready: %w", err)
×
1693
        }
×
1694

1695
        statusFile := vGPUManagerStatusFile
×
1696
        if hostDriver {
×
1697
                statusFile = hostVGPUManagerStatusFile
×
1698
        }
×
1699

1700
        // create driver status file
1701
        err = createStatusFile(outputDirFlag + "/" + statusFile)
×
1702
        if err != nil {
×
1703
                return err
×
1704
        }
×
1705
        return nil
×
1706
}
1707

1708
func (v *VGPUManager) runValidation(silent bool) (hostDriver bool, err error) {
×
1709
        // invoke validation command
×
1710
        command := "chroot"
×
1711
        args := []string{"/run/nvidia/driver", "nvidia-smi"}
×
1712

×
1713
        // check if driver is pre-installed on the host and use host path for validation
×
NEW
1714
        if _, err := driverroot.ResolveHostNvidiaSMI("/host"); err == nil {
×
1715
                args = []string{"/host", "nvidia-smi"}
×
1716
                hostDriver = true
×
1717
        }
×
1718

1719
        if withWaitFlag {
×
1720
                return hostDriver, runCommandWithWait(command, args, sleepIntervalSecondsFlag, silent)
×
1721
        }
×
1722

1723
        return hostDriver, runCommand(command, args, silent)
×
1724
}
1725

1726
// waitForVFs waits for Virtual Functions to be created on all NVIDIA GPUs.
1727
// It polls sriov_numvfs until all GPUs have their full VF count enabled.
1728
func waitForVFs(ctx context.Context, timeout time.Duration) error {
×
1729
        pollInterval := time.Duration(sleepIntervalSecondsFlag) * time.Second
×
1730
        nvpciLib := nvpci.New()
×
1731

×
1732
        return wait.PollUntilContextTimeout(ctx, pollInterval, timeout, true, func(ctx context.Context) (bool, error) {
×
1733
                gpus, err := nvpciLib.GetGPUs()
×
1734
                if err != nil {
×
1735
                        log.Warnf("Error getting GPUs: %v", err)
×
1736
                        return false, nil
×
1737
                }
×
1738

1739
                var totalExpected, totalEnabled uint64
×
1740
                var pfCount int
×
1741
                for _, gpu := range gpus {
×
1742
                        sriovInfo := gpu.SriovInfo
×
1743
                        if sriovInfo.IsPF() {
×
1744
                                pfCount++
×
1745
                                totalExpected += sriovInfo.PhysicalFunction.TotalVFs
×
1746
                                totalEnabled += sriovInfo.PhysicalFunction.NumVFs
×
1747
                        }
×
1748
                }
1749

1750
                if totalExpected == 0 {
×
1751
                        log.Info("No SR-IOV capable GPUs found, skipping VF wait")
×
1752
                        return true, nil
×
1753
                }
×
1754

1755
                if totalEnabled == totalExpected {
×
1756
                        log.Infof("All %d VF(s) enabled on %d NVIDIA GPU(s)", totalEnabled, pfCount)
×
1757
                        return true, nil
×
1758
                }
×
1759

1760
                log.Infof("Waiting for VFs: %d/%d enabled across %d GPU(s)", totalEnabled, totalExpected, pfCount)
×
1761
                return false, nil
×
1762
        })
1763
}
1764

1765
func (c *CCManager) validate() error {
×
1766
        // delete status file if already present
×
1767
        err := deleteStatusFile(outputDirFlag + "/" + ccManagerStatusFile)
×
1768
        if err != nil {
×
1769
                return err
×
1770
        }
×
1771

1772
        kubeConfig, err := rest.InClusterConfig()
×
1773
        if err != nil {
×
1774
                return fmt.Errorf("error getting cluster config - %w", err)
×
1775
        }
×
1776

1777
        kubeClient, err := kubernetes.NewForConfig(kubeConfig)
×
1778
        if err != nil {
×
1779
                log.Errorf("Error getting k8s client - %v\n", err)
×
1780
                return err
×
1781
        }
×
1782

1783
        // update k8s client for fetching node labels
1784
        c.setKubeClient(kubeClient)
×
1785

×
1786
        err = c.runValidation(false)
×
1787
        if err != nil {
×
1788
                fmt.Println("CC Manager is not ready")
×
1789
                return err
×
1790
        }
×
1791

1792
        // create driver status file
1793
        err = createStatusFile(outputDirFlag + "/" + ccManagerStatusFile)
×
1794
        if err != nil {
×
1795
                return err
×
1796
        }
×
1797
        return nil
×
1798
}
1799

1800
func (c *CCManager) runValidation(silent bool) error {
×
1801
        node, err := getNode(c.ctx, c.kubeClient)
×
1802
        if err != nil {
×
1803
                return fmt.Errorf("unable to fetch node by name %s to check for %s label: %w",
×
1804
                        nodeNameFlag, CCCapableLabelKey, err)
×
1805
        }
×
1806

1807
        // make sure this is a CC capable node
1808
        nodeLabels := node.GetLabels()
×
1809
        if enabled, ok := nodeLabels[CCCapableLabelKey]; !ok || enabled != "true" {
×
1810
                log.Info("Not a CC capable node, skipping CC Manager validation")
×
1811
                return nil
×
1812
        }
×
1813

1814
        // check if the ccManager container is ready
1815
        err = assertCCManagerContainerReady(silent, withWaitFlag)
×
1816
        if err != nil {
×
1817
                return err
×
1818
        }
×
1819
        return nil
×
1820
}
1821

1822
func (c *CCManager) setKubeClient(kubeClient kubernetes.Interface) {
×
1823
        c.kubeClient = kubeClient
×
1824
}
×
1825

1826
// Check that the ccManager container is ready after applying required ccMode
1827
func assertCCManagerContainerReady(silent, withWaitFlag bool) error {
×
1828
        command := shell
×
1829
        args := []string{"-c", "stat /run/nvidia/validations/.cc-manager-ctr-ready"}
×
1830

×
1831
        if withWaitFlag {
×
1832
                return runCommandWithWait(command, args, sleepIntervalSecondsFlag, silent)
×
1833
        }
×
1834

1835
        return runCommand(command, args, silent)
×
1836
}
1837

1838
func (v *VGPUDevices) validate() error {
×
1839
        ctx := v.ctx
×
1840

×
1841
        gpuWorkloadConfig, err := getWorkloadConfig(ctx)
×
1842
        if err != nil {
×
1843
                return fmt.Errorf("error getting gpu workload config: %w", err)
×
1844
        }
×
1845
        log.Infof("GPU workload configuration: %s", gpuWorkloadConfig)
×
1846

×
NEW
1847
        err = utils.WriteFileAtomically(filepath.Join(outputDirFlag, workloadTypeStatusFile), gpuWorkloadConfig+"\n")
×
1848
        if err != nil {
×
1849
                return fmt.Errorf("error updating %s status file: %w", workloadTypeStatusFile, err)
×
1850
        }
×
1851

1852
        if gpuWorkloadConfig != gpuWorkloadConfigVMVgpu {
×
1853
                log.WithFields(log.Fields{
×
1854
                        "gpuWorkloadConfig": gpuWorkloadConfig,
×
1855
                }).Info("vgpu devices not required on the node. Skipping validation.")
×
1856
                return nil
×
1857
        }
×
1858

1859
        // delete status file if already present
1860
        err = deleteStatusFile(outputDirFlag + "/" + vGPUDevicesStatusFile)
×
1861
        if err != nil {
×
1862
                return err
×
1863
        }
×
1864

1865
        err = v.runValidation()
×
1866
        if err != nil {
×
1867
                return err
×
1868
        }
×
1869
        log.Info("Validation completed successfully - vGPU devices present on the host")
×
1870

×
1871
        // create status file
×
1872
        err = createStatusFile(outputDirFlag + "/" + vGPUDevicesStatusFile)
×
1873
        if err != nil {
×
1874
                return err
×
1875
        }
×
1876

1877
        return nil
×
1878
}
1879

1880
func (v *VGPUDevices) runValidation() error {
×
1881
        nvmdev := nvmdev.New()
×
1882
        vGPUDevices, err := nvmdev.GetAllDevices()
×
1883
        if err != nil {
×
1884
                return fmt.Errorf("error checking for vGPU devices on the host: %w", err)
×
1885
        }
×
1886

1887
        if !withWaitFlag {
×
1888
                numDevices := len(vGPUDevices)
×
1889
                if numDevices == 0 {
×
1890
                        return fmt.Errorf("no vGPU devices found")
×
1891
                }
×
1892

1893
                log.Infof("Found %d vGPU devices", numDevices)
×
1894
                return nil
×
1895
        }
1896

1897
        for {
×
1898
                numDevices := len(vGPUDevices)
×
1899
                if numDevices > 0 {
×
1900
                        log.Infof("Found %d vGPU devices", numDevices)
×
1901
                        return nil
×
1902
                }
×
1903
                log.Infof("No vGPU devices found, retrying after %d seconds", sleepIntervalSecondsFlag)
×
1904
                time.Sleep(time.Duration(sleepIntervalSecondsFlag) * time.Second)
×
1905

×
1906
                vGPUDevices, err = nvmdev.GetAllDevices()
×
1907
                if err != nil {
×
1908
                        return fmt.Errorf("error checking for vGPU devices on the host: %w", err)
×
1909
                }
×
1910
        }
1911
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc