mirror of
https://github.com/NVIDIA/nvidia-container-toolkit
synced 2025-06-15 10:58:45 +00:00
358 lines
12 KiB
Go
358 lines
12 KiB
Go
/*
|
|
*
|
|
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
*
|
|
*/
|
|
package config
|
|
|
|
import (
|
|
"flag"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/NVIDIA/nvidia-container-toolkit/internal/platform-support/tegra/csv"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
cli "github.com/urfave/cli/v2"
|
|
)
|
|
|
|
type mockConfig struct {
|
|
SpecDirs []string
|
|
Mode string
|
|
HookPath string
|
|
LdconfigPath string
|
|
CSVSpecPath string
|
|
}
|
|
|
|
func (m *mockConfig) toConfig() *Config {
|
|
return &Config{
|
|
NVIDIAContainerRuntimeConfig: RuntimeConfig{
|
|
Mode: m.Mode,
|
|
Modes: modesConfig{
|
|
CDI: cdiModeConfig{
|
|
SpecDirs: m.SpecDirs,
|
|
},
|
|
CSV: csvModeConfig{
|
|
MountSpecPath: m.CSVSpecPath,
|
|
},
|
|
},
|
|
},
|
|
NVIDIAContainerRuntimeHookConfig: RuntimeHookConfig{
|
|
Path: m.HookPath,
|
|
},
|
|
NVIDIAContainerCLIConfig: ContainerCLIConfig{
|
|
Ldconfig: ldconfigPath(m.LdconfigPath),
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestResolveCDIListConfig(t *testing.T) {
|
|
app := cli.NewApp()
|
|
app.Flags = []cli.Flag{
|
|
&cli.StringSliceFlag{
|
|
Name: "spec-dir",
|
|
},
|
|
}
|
|
set := func(args ...string) *cli.Context {
|
|
set := flagSet(app, args...)
|
|
return cli.NewContext(app, set, nil)
|
|
}
|
|
t.Run("CLI takes precedence", func(t *testing.T) {
|
|
ctx := set("--spec-dir", "/cli/dir1", "--spec-dir", "/cli/dir2")
|
|
cfg := (&mockConfig{SpecDirs: []string{"/config/dir"}}).toConfig()
|
|
var target struct{ cdiSpecDirs cli.StringSlice }
|
|
ResolveCDIListConfig(ctx, cfg, &target)
|
|
require.Equal(t, []string{"/cli/dir1", "/cli/dir2"}, getStringSliceFieldValue(reflect.ValueOf(target.cdiSpecDirs)))
|
|
})
|
|
t.Run("Config used if CLI not set", func(t *testing.T) {
|
|
ctx := set()
|
|
cfg := (&mockConfig{SpecDirs: []string{"/config/dir1", "/config/dir2"}}).toConfig()
|
|
var target struct{ cdiSpecDirs cli.StringSlice }
|
|
ResolveCDIListConfig(ctx, cfg, &target)
|
|
require.Equal(t, []string{"/config/dir1", "/config/dir2"}, getStringSliceFieldValue(reflect.ValueOf(target.cdiSpecDirs)))
|
|
})
|
|
t.Run("Default used if neither set", func(t *testing.T) {
|
|
ctx := set()
|
|
cfg := (&mockConfig{}).toConfig()
|
|
var target struct{ cdiSpecDirs cli.StringSlice }
|
|
ResolveCDIListConfig(ctx, cfg, &target)
|
|
require.Equal(t, []string{"/etc/cdi", "/var/run/cdi"}, getStringSliceFieldValue(reflect.ValueOf(target.cdiSpecDirs)))
|
|
})
|
|
}
|
|
|
|
// Helper for safely extracting []string from a reflect.Value of cli.StringSlice or *cli.StringSlice
|
|
func getStringSliceFieldValue(v reflect.Value) []string {
|
|
if v.Kind() == reflect.Ptr {
|
|
v = v.Elem()
|
|
}
|
|
if v.Kind() == reflect.Interface {
|
|
v = v.Elem()
|
|
}
|
|
ss, ok := v.Interface().(cli.StringSlice)
|
|
if ok {
|
|
return ss.Value()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Helper for safely extracting string from a reflect.Value of string or *string
|
|
func getStringFieldValue(v reflect.Value) string {
|
|
if v.Kind() == reflect.Ptr {
|
|
v = v.Elem()
|
|
}
|
|
if v.Kind() == reflect.Interface {
|
|
v = v.Elem()
|
|
}
|
|
if v.Kind() == reflect.String {
|
|
return v.String()
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// optsWithSetters is used to test setter-based normalization
|
|
type optsWithSetters struct {
|
|
csvFiles []string
|
|
csvIgnorePatterns []string
|
|
}
|
|
|
|
// Implement the setter methods
|
|
func (o *optsWithSetters) SetCSVFiles(files []string) { o.csvFiles = files }
|
|
func (o *optsWithSetters) SetCSVIgnorePatterns(patterns []string) { o.csvIgnorePatterns = patterns }
|
|
|
|
func TestResolveCDIGenerateOptions(t *testing.T) {
|
|
app := cli.NewApp()
|
|
app.Flags = []cli.Flag{
|
|
&cli.StringSliceFlag{Name: "config-search-path"},
|
|
&cli.StringFlag{Name: "format"},
|
|
&cli.StringFlag{Name: "mode"},
|
|
&cli.StringSliceFlag{Name: "device-name-strategy"},
|
|
&cli.StringFlag{Name: "nvidia-cdi-hook-path"},
|
|
&cli.StringFlag{Name: "ldconfig-path"},
|
|
&cli.StringFlag{Name: "vendor"},
|
|
&cli.StringFlag{Name: "class"},
|
|
&cli.StringSliceFlag{Name: "library-search-path"},
|
|
&cli.StringSliceFlag{Name: "csv.file"},
|
|
&cli.StringSliceFlag{Name: "csv.ignore-pattern"},
|
|
&cli.StringSliceFlag{Name: "disable-hook"},
|
|
&cli.StringFlag{Name: "output"},
|
|
&cli.StringFlag{Name: "driver-root"},
|
|
&cli.StringFlag{Name: "dev-root"},
|
|
}
|
|
set := func(args ...string) *cli.Context {
|
|
set := flagSet(app, args...)
|
|
return cli.NewContext(app, set, nil)
|
|
}
|
|
cfg := (&mockConfig{
|
|
SpecDirs: []string{"/config/dir"},
|
|
Mode: "configmode",
|
|
HookPath: "/config/hook",
|
|
LdconfigPath: "/config/ldconfig",
|
|
CSVSpecPath: "/config/csv",
|
|
}).toConfig()
|
|
|
|
t.Run("All CLI flags", func(t *testing.T) {
|
|
ctx := set(
|
|
"--config-search-path", "/cli/cfg1", "--config-search-path", "/cli/cfg2",
|
|
"--format", "json",
|
|
"--mode", "climode",
|
|
"--device-name-strategy", "uuid",
|
|
"--nvidia-cdi-hook-path", "/cli/hook",
|
|
"--ldconfig-path", "/cli/ldconfig",
|
|
"--vendor", "cli-vendor",
|
|
"--class", "cli-class",
|
|
"--library-search-path", "/cli/lib1",
|
|
"--csv.file", "/cli/csv1",
|
|
"--csv.ignore-pattern", "pat1",
|
|
"--disable-hook", "hook1",
|
|
"--output", "/cli/output",
|
|
"--driver-root", "/cli/driver",
|
|
"--dev-root", "/cli/dev",
|
|
)
|
|
var opts struct {
|
|
Output string
|
|
Format string
|
|
DeviceNameStrategies cli.StringSlice
|
|
DriverRoot string
|
|
DevRoot string
|
|
NvidiaCDIHookPath string
|
|
LdconfigPath string
|
|
Mode string
|
|
Vendor string
|
|
Class string
|
|
ConfigSearchPaths cli.StringSlice
|
|
LibrarySearchPaths cli.StringSlice
|
|
DisabledHooks cli.StringSlice
|
|
Csv struct {
|
|
Files cli.StringSlice
|
|
IgnorePatterns cli.StringSlice
|
|
}
|
|
}
|
|
ResolveCDIGenerateOptions(ctx, cfg, &opts)
|
|
// Use reflection to check values
|
|
v := reflect.ValueOf(&opts).Elem()
|
|
field := v.FieldByName("Format")
|
|
require.Equal(t, "json", getStringFieldValue(field))
|
|
field = v.FieldByName("Mode")
|
|
require.Equal(t, "climode", getStringFieldValue(field))
|
|
field = v.FieldByName("NvidiaCDIHookPath")
|
|
require.Equal(t, "/cli/hook", getStringFieldValue(field))
|
|
field = v.FieldByName("LdconfigPath")
|
|
require.Equal(t, "/cli/ldconfig", getStringFieldValue(field))
|
|
field = v.FieldByName("Vendor")
|
|
require.Equal(t, "cli-vendor", getStringFieldValue(field))
|
|
field = v.FieldByName("Class")
|
|
require.Equal(t, "cli-class", getStringFieldValue(field))
|
|
field = v.FieldByName("Output")
|
|
require.Equal(t, "/cli/output", getStringFieldValue(field))
|
|
field = v.FieldByName("DriverRoot")
|
|
require.Equal(t, "/cli/driver", getStringFieldValue(field))
|
|
field = v.FieldByName("DevRoot")
|
|
require.Equal(t, "/cli/dev", getStringFieldValue(field))
|
|
require.Equal(t, []string{"uuid"}, getStringSliceFieldValue(v.FieldByName("DeviceNameStrategies")))
|
|
require.Equal(t, []string{"/cli/cfg1", "/cli/cfg2"}, getStringSliceFieldValue(v.FieldByName("ConfigSearchPaths")))
|
|
require.Equal(t, []string{"/cli/lib1"}, getStringSliceFieldValue(v.FieldByName("LibrarySearchPaths")))
|
|
require.Equal(t, []string{"hook1"}, getStringSliceFieldValue(v.FieldByName("DisabledHooks")))
|
|
csvField := v.FieldByName("Csv")
|
|
requireStringSliceEqual(t, []string{"/cli/csv1"}, getStringSliceFieldValue(csvField.FieldByName("Files")))
|
|
requireStringSliceEqual(t, []string{"pat1"}, getStringSliceFieldValue(csvField.FieldByName("IgnorePatterns")))
|
|
})
|
|
|
|
t.Run("Config fallback", func(t *testing.T) {
|
|
ctx := set()
|
|
var opts struct {
|
|
Output string
|
|
Format string
|
|
DeviceNameStrategies cli.StringSlice
|
|
DriverRoot string
|
|
DevRoot string
|
|
NvidiaCDIHookPath string
|
|
LdconfigPath string
|
|
Mode string
|
|
Vendor string
|
|
Class string
|
|
ConfigSearchPaths cli.StringSlice
|
|
LibrarySearchPaths cli.StringSlice
|
|
DisabledHooks cli.StringSlice
|
|
Csv struct {
|
|
Files cli.StringSlice
|
|
IgnorePatterns cli.StringSlice
|
|
}
|
|
}
|
|
ResolveCDIGenerateOptions(ctx, cfg, &opts)
|
|
v := reflect.ValueOf(&opts).Elem()
|
|
require.Equal(t, "configmode", getStringFieldValue(v.FieldByName("Mode")))
|
|
require.Equal(t, "/config/hook", getStringFieldValue(v.FieldByName("NvidiaCDIHookPath")))
|
|
require.Equal(t, "/config/ldconfig", getStringFieldValue(v.FieldByName("LdconfigPath")))
|
|
csvField := v.FieldByName("Csv")
|
|
requireStringSliceEqual(t, []string{"/config/csv"}, getStringSliceFieldValue(csvField.FieldByName("Files")))
|
|
requireStringSliceEqual(t, []string{}, getStringSliceFieldValue(csvField.FieldByName("IgnorePatterns")))
|
|
})
|
|
|
|
t.Run("Default fallback", func(t *testing.T) {
|
|
ctx := set()
|
|
cfg := (&mockConfig{}).toConfig()
|
|
var opts struct {
|
|
Output string
|
|
Format string
|
|
DeviceNameStrategies cli.StringSlice
|
|
DriverRoot string
|
|
DevRoot string
|
|
NvidiaCDIHookPath string
|
|
LdconfigPath string
|
|
Mode string
|
|
Vendor string
|
|
Class string
|
|
ConfigSearchPaths cli.StringSlice
|
|
LibrarySearchPaths cli.StringSlice
|
|
DisabledHooks cli.StringSlice
|
|
Csv struct {
|
|
Files cli.StringSlice
|
|
IgnorePatterns cli.StringSlice
|
|
}
|
|
}
|
|
ResolveCDIGenerateOptions(ctx, cfg, &opts)
|
|
v := reflect.ValueOf(&opts).Elem()
|
|
require.Equal(t, "auto", getStringFieldValue(v.FieldByName("Mode")))
|
|
require.Equal(t, "yaml", getStringFieldValue(v.FieldByName("Format")))
|
|
require.Equal(t, []string{"index", "uuid"}, getStringSliceFieldValue(v.FieldByName("DeviceNameStrategies")))
|
|
require.Equal(t, "nvidia.com", getStringFieldValue(v.FieldByName("Vendor")))
|
|
require.Equal(t, "gpu", getStringFieldValue(v.FieldByName("Class")))
|
|
csvField := v.FieldByName("Csv")
|
|
requireStringSliceEqual(t, csv.DefaultFileList(), getStringSliceFieldValue(csvField.FieldByName("Files")))
|
|
requireStringSliceEqual(t, []string{}, getStringSliceFieldValue(csvField.FieldByName("IgnorePatterns")))
|
|
})
|
|
}
|
|
|
|
func TestResolveCDIGenerateOptions_SetterMethods(t *testing.T) {
|
|
app := cli.NewApp()
|
|
app.Flags = []cli.Flag{
|
|
&cli.StringSliceFlag{Name: "csv.file"},
|
|
&cli.StringSliceFlag{Name: "csv.ignore-pattern"},
|
|
}
|
|
set := func(args ...string) *cli.Context {
|
|
set := flagSet(app, args...)
|
|
return cli.NewContext(app, set, nil)
|
|
}
|
|
cfg := (&mockConfig{
|
|
CSVSpecPath: "/config/csv",
|
|
}).toConfig()
|
|
|
|
t.Run("CLI takes precedence", func(t *testing.T) {
|
|
ctx := set("--csv.file", "/cli/csv1", "--csv.file", "/cli/csv2", "--csv.ignore-pattern", "pat1")
|
|
opts := &optsWithSetters{}
|
|
ResolveCDIGenerateOptions(ctx, cfg, opts)
|
|
requireStringSliceEqual(t, []string{"/cli/csv1", "/cli/csv2"}, opts.csvFiles)
|
|
requireStringSliceEqual(t, []string{"pat1"}, opts.csvIgnorePatterns)
|
|
})
|
|
|
|
t.Run("Config fallback", func(t *testing.T) {
|
|
ctx := set()
|
|
opts := &optsWithSetters{}
|
|
ResolveCDIGenerateOptions(ctx, cfg, opts)
|
|
requireStringSliceEqual(t, []string{"/config/csv"}, opts.csvFiles)
|
|
requireStringSliceEqual(t, []string{}, opts.csvIgnorePatterns)
|
|
})
|
|
|
|
t.Run("Default fallback", func(t *testing.T) {
|
|
ctx := set()
|
|
cfg := (&mockConfig{}).toConfig()
|
|
opts := &optsWithSetters{}
|
|
ResolveCDIGenerateOptions(ctx, cfg, opts)
|
|
requireStringSliceEqual(t, csv.DefaultFileList(), opts.csvFiles)
|
|
requireStringSliceEqual(t, []string{}, opts.csvIgnorePatterns)
|
|
})
|
|
}
|
|
|
|
// Helper to create a cli.FlagSet for testing
|
|
func flagSet(app *cli.App, args ...string) *flag.FlagSet {
|
|
set := flag.NewFlagSet(app.Name, flag.ContinueOnError)
|
|
for _, f := range app.Flags {
|
|
_ = f.Apply(set)
|
|
}
|
|
_ = set.Parse(args)
|
|
return set
|
|
}
|
|
|
|
// Helper to compare two string slices, treating nil and empty as equal
|
|
func requireStringSliceEqual(t *testing.T, expected, actual []string, msgAndArgs ...interface{}) {
|
|
if expected == nil {
|
|
expected = []string{}
|
|
}
|
|
if actual == nil {
|
|
actual = []string{}
|
|
}
|
|
require.Equal(t, expected, actual, msgAndArgs...)
|
|
}
|