Skip to content

Commit 923fa9b

Browse files
authored
Merge pull request #1461 from elezar/dgpu-on-nvgpu
Handle multiple GPUs in CDI spec generation from CSV
2 parents 6f7e8b0 + 4eb4ca6 commit 923fa9b

File tree

21 files changed

+738
-317
lines changed

21 files changed

+738
-317
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/NVIDIA/nvidia-container-toolkit
33
go 1.25.0
44

55
require (
6-
github.com/NVIDIA/go-nvlib v0.9.0
6+
github.com/NVIDIA/go-nvlib v0.9.1-0.20251202135446-d0f42ba016dd
77
github.com/NVIDIA/go-nvml v0.13.0-1
88
github.com/google/uuid v1.6.0
99
github.com/moby/sys/mountinfo v0.7.2

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8=
22
cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
3-
github.com/NVIDIA/go-nvlib v0.9.0 h1:GKLIvLJ0uhCtTLLZp2Q8QIDRxOYH45MM4Y5OO3U5Rho=
4-
github.com/NVIDIA/go-nvlib v0.9.0/go.mod h1:7mzx9FSdO9fXWP9NKuZmWkCwhkEcSWQFe2tmFwtLb9c=
3+
github.com/NVIDIA/go-nvlib v0.9.1-0.20251202135446-d0f42ba016dd h1:MC1w/VYuo9Zt0se4SSx9BVid4a46ai+voN3knRvVWjE=
4+
github.com/NVIDIA/go-nvlib v0.9.1-0.20251202135446-d0f42ba016dd/go.mod h1:7mzx9FSdO9fXWP9NKuZmWkCwhkEcSWQFe2tmFwtLb9c=
55
github.com/NVIDIA/go-nvml v0.13.0-1 h1:OLX8Jq3dONuPOQPC7rndB6+iDmDakw0XTYgzMxObkEw=
66
github.com/NVIDIA/go-nvml v0.13.0-1/go.mod h1:+KNA7c7gIBH7SKSJ1ntlwkfN80zdx8ovl4hrK3LmPt4=
77
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=

internal/info/auto_test.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -215,13 +215,10 @@ func TestResolveAutoMode(t *testing.T) {
215215
HasDXCoreFunc: func() (bool, string) {
216216
return tc.info["dxcore"], "dxcore"
217217
},
218-
IsTegraSystemFunc: func() (bool, string) {
219-
return tc.info["tegra"], "tegra"
220-
},
221218
HasTegraFilesFunc: func() (bool, string) {
222219
return tc.info["tegra"], "tegra"
223220
},
224-
HasOnlyIntegratedGPUsFunc: func() (bool, string) {
221+
HasAnIntegratedGPUFunc: func() (bool, string) {
225222
return tc.info["nvgpu"], "nvgpu"
226223
},
227224
}

internal/platform-support/tegra/csv.go

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,13 @@ import (
2525
"github.com/NVIDIA/nvidia-container-toolkit/internal/platform-support/tegra/csv"
2626
)
2727

28-
// newDiscovererFromCSVFiles creates a discoverer for the specified CSV files. A logger is also supplied.
29-
// The constructed discoverer is comprised of a list, with each element in the list being associated with a
30-
// single CSV files.
31-
func (o tegraOptions) newDiscovererFromCSVFiles() (discover.Discover, error) {
32-
if len(o.csvFiles) == 0 {
33-
o.logger.Warningf("No CSV files specified")
28+
// newDiscovererFromMountSpecs creates a discoverer for the specified mount specs.
29+
func (o options) newDiscovererFromMountSpecs(targetsByType MountSpecPathsByType) (discover.Discover, error) {
30+
if len(targetsByType) == 0 {
31+
o.logger.Warningf("No mount specs specified")
3432
return discover.None{}, nil
3533
}
3634

37-
targetsByType := getTargetsFromCSVFiles(o.logger, o.csvFiles)
38-
3935
devices := discover.NewCharDeviceDiscoverer(
4036
o.logger,
4137
o.devRoot,
@@ -64,15 +60,13 @@ func (o tegraOptions) newDiscovererFromCSVFiles() (discover.Discover, error) {
6460
)
6561

6662
// We process the explicitly requested symlinks.
67-
symlinkTargets := o.ignorePatterns.Apply(targetsByType[csv.MountSpecSym]...)
68-
o.logger.Debugf("Filtered symlink targets: %v", symlinkTargets)
6963
symlinks := discover.NewMounts(
7064
o.logger,
7165
o.symlinkLocator,
7266
o.driverRoot,
73-
symlinkTargets,
67+
targetsByType[csv.MountSpecSym],
7468
)
75-
createSymlinks := o.createCSVSymlinkHooks(symlinkTargets)
69+
createSymlinks := o.createCSVSymlinkHooks(targetsByType[csv.MountSpecSym])
7670

7771
d := discover.Merge(
7872
devices,
@@ -85,23 +79,24 @@ func (o tegraOptions) newDiscovererFromCSVFiles() (discover.Discover, error) {
8579
return d, nil
8680
}
8781

88-
// getTargetsFromCSVFiles returns the list of mount specs from the specified CSV files.
89-
// These are aggregated by mount spec type.
90-
// TODO: We use a function variable here to allow this to be overridden for testing.
91-
// This should be properly mocked.
92-
var getTargetsFromCSVFiles = func(logger logger.Interface, files []string) map[csv.MountSpecType][]string {
93-
targetsByType := make(map[csv.MountSpecType][]string)
94-
for _, filename := range files {
82+
// MountSpecsFromCSVFiles returns a MountSpecPathsByTyper for the specified list
83+
// of CSV files.
84+
func MountSpecsFromCSVFiles(logger logger.Interface, csvFilePaths ...string) MountSpecPathsByType {
85+
var mountSpecs mountSpecPathsByTypers
86+
87+
for _, filename := range csvFilePaths {
9588
targets, err := loadCSVFile(logger, filename)
9689
if err != nil {
9790
logger.Warningf("Skipping CSV file %v: %v", filename, err)
9891
continue
9992
}
93+
targetsByType := make(MountSpecPathsByType)
10094
for _, t := range targets {
10195
targetsByType[t.Type] = append(targetsByType[t.Type], t.Path)
10296
}
97+
mountSpecs = append(mountSpecs, targetsByType)
10398
}
104-
return targetsByType
99+
return mountSpecs.MountSpecPathsByType()
105100
}
106101

107102
// loadCSVFile loads the specified CSV file and returns the list of mount specs

internal/platform-support/tegra/csv_test.go

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,14 @@ import (
2424
"github.com/stretchr/testify/require"
2525

2626
"github.com/NVIDIA/nvidia-container-toolkit/internal/discover"
27-
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
2827
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup"
29-
30-
"github.com/NVIDIA/nvidia-container-toolkit/internal/platform-support/tegra/csv"
3128
)
3229

3330
func TestDiscovererFromCSVFiles(t *testing.T) {
3431
logger, _ := testlog.NewNullLogger()
3532
testCases := []struct {
3633
description string
37-
moutSpecs map[csv.MountSpecType][]string
34+
moutSpecs MountSpecPathsByType
3835
ignorePatterns []string
3936
symlinkLocator lookup.Locator
4037
symlinkChainLocator lookup.Locator
@@ -49,7 +46,7 @@ func TestDiscovererFromCSVFiles(t *testing.T) {
4946
// TODO: This current resolves to two mounts that are the same.
5047
// These are deduplicated at a later stage. We could consider deduplicating earlier in the pipeline.
5148
description: "symlink is resolved to target; mounts and symlink are created",
52-
moutSpecs: map[csv.MountSpecType][]string{
49+
moutSpecs: MountSpecPathsByType{
5350
"lib": {"/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so"},
5451
"sym": {"/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so"},
5552
},
@@ -105,7 +102,7 @@ func TestDiscovererFromCSVFiles(t *testing.T) {
105102
// TODO: This current resolves to two mounts that are the same.
106103
// These are deduplicated at a later stage. We could consider deduplicating earlier in the pipeline.
107104
description: "single glob filter does not remove symlink mounts",
108-
moutSpecs: map[csv.MountSpecType][]string{
105+
moutSpecs: MountSpecPathsByType{
109106
"lib": {"/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so"},
110107
"sym": {"/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so"},
111108
},
@@ -160,7 +157,7 @@ func TestDiscovererFromCSVFiles(t *testing.T) {
160157
},
161158
{
162159
description: "** filter removes symlink mounts",
163-
moutSpecs: map[csv.MountSpecType][]string{
160+
moutSpecs: MountSpecPathsByType{
164161
"lib": {"/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so"},
165162
"sym": {"/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so"},
166163
},
@@ -186,19 +183,20 @@ func TestDiscovererFromCSVFiles(t *testing.T) {
186183
hookCreator := discover.NewHookCreator()
187184
for _, tc := range testCases {
188185
t.Run(tc.description, func(t *testing.T) {
189-
defer setGetTargetsFromCSVFiles(tc.moutSpecs)()
190-
191-
o := tegraOptions{
186+
o := options{
192187
logger: logger,
193188
hookCreator: hookCreator,
194-
csvFiles: []string{"dummy"},
195-
ignorePatterns: tc.ignorePatterns,
196189
symlinkLocator: tc.symlinkLocator,
197190
symlinkChainLocator: tc.symlinkChainLocator,
198191
resolveSymlink: tc.symlinkResolver,
192+
193+
mountSpecs: Transform(
194+
tc.moutSpecs,
195+
IgnoreSymlinkMountSpecsByPattern(tc.ignorePatterns...),
196+
),
199197
}
200198

201-
d, err := o.newDiscovererFromCSVFiles()
199+
d, err := o.newDiscovererFromMountSpecs(o.mountSpecs.MountSpecPathsByType())
202200
require.ErrorIs(t, err, tc.expectedError)
203201

204202
hooks, err := d.Hooks()
@@ -212,14 +210,3 @@ func TestDiscovererFromCSVFiles(t *testing.T) {
212210
})
213211
}
214212
}
215-
216-
func setGetTargetsFromCSVFiles(override map[csv.MountSpecType][]string) func() {
217-
original := getTargetsFromCSVFiles
218-
getTargetsFromCSVFiles = func(logger logger.Interface, files []string) map[csv.MountSpecType][]string {
219-
return override
220-
}
221-
222-
return func() {
223-
getTargetsFromCSVFiles = original
224-
}
225-
}

internal/platform-support/tegra/filter.go

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ package tegra
1919
import (
2020
"path/filepath"
2121
"strings"
22+
23+
"github.com/NVIDIA/nvidia-container-toolkit/internal/platform-support/tegra/csv"
2224
)
2325

24-
type ignoreMountSpecPatterns []string
26+
type ignoreSymlinkMountSpecPatterns []string
2527

26-
func (d ignoreMountSpecPatterns) Match(name string) bool {
28+
func (d ignoreSymlinkMountSpecPatterns) match(name string) bool {
2729
for _, pattern := range d {
2830
target := name
2931
if strings.HasPrefix(pattern, "**/") {
@@ -37,13 +39,109 @@ func (d ignoreMountSpecPatterns) Match(name string) bool {
3739
return false
3840
}
3941

40-
func (d ignoreMountSpecPatterns) Apply(input ...string) []string {
42+
func (d ignoreSymlinkMountSpecPatterns) filter(input ...string) []string {
4143
var filtered []string
4244
for _, name := range input {
43-
if d.Match(name) {
45+
if d.match(name) {
4446
continue
4547
}
4648
filtered = append(filtered, name)
4749
}
4850
return filtered
4951
}
52+
53+
func (d ignoreSymlinkMountSpecPatterns) Apply(input MountSpecPathsByTyper) MountSpecPathsByTyper {
54+
ms := input.MountSpecPathsByType()
55+
56+
if symlinks, ok := ms[csv.MountSpecSym]; ok {
57+
ms[csv.MountSpecSym] = d.filter(symlinks...)
58+
}
59+
60+
return ms
61+
}
62+
63+
// A filter removes elements from an input list and returns the remaining
64+
// elements.
65+
type filter interface {
66+
apply(...string) []string
67+
}
68+
69+
// A stringMatcher implements the MatchString function.
70+
type stringMatcher interface {
71+
MatchString(string) bool
72+
}
73+
74+
// A matcherAsFilter is used to ensure that a string matcher can be used as a filter.
75+
type matcherAsFilter struct {
76+
stringMatcher
77+
}
78+
79+
type filterByMountSpecType map[csv.MountSpecType]filter
80+
81+
type pathPatterns []string
82+
type pathPattern string
83+
type basenamePattern string
84+
85+
// MatchString for a set of path patterns returns true if any of the patterns
86+
// matches against the input string.
87+
func (d pathPatterns) MatchString(input string) bool {
88+
for _, pattern := range d {
89+
if match := pathPattern(pattern).MatchString(input); match {
90+
return true
91+
}
92+
}
93+
return false
94+
}
95+
96+
// MatchString attempts to match a path pattern to the specified input string.
97+
// If the pattern starts with `**/` the input is treated as a path and only
98+
// the basenames are matched using regular glob rules.
99+
func (d pathPattern) MatchString(input string) bool {
100+
if strings.HasPrefix(string(d), "**/") {
101+
return basenamePattern(d).MatchString(input)
102+
}
103+
match, _ := filepath.Match(string(d), input)
104+
return match
105+
}
106+
107+
// MatchString for a basename pattern applies the specified pattern against the
108+
// basename of the input.
109+
// If the pattern starts with **/, this is stripped before attempting to match.
110+
func (d basenamePattern) MatchString(input string) bool {
111+
pattern := strings.TrimPrefix(string(d), "**/")
112+
match, _ := filepath.Match(pattern, filepath.Base(input))
113+
return match
114+
}
115+
116+
// Apply the specified per-type filters to the input mount specs.
117+
func (p filterByMountSpecType) Apply(input MountSpecPathsByTyper) MountSpecPathsByTyper {
118+
ms := input.MountSpecPathsByType()
119+
for t, filter := range p {
120+
if len(ms[t]) == 0 {
121+
continue
122+
}
123+
ms[t] = filter.apply(ms[t]...)
124+
}
125+
return ms
126+
}
127+
128+
// apply uses a matcher to filter an input string.
129+
// Each element in the input that matches is skipped and the remaining elements
130+
// are returned.
131+
func (f *matcherAsFilter) apply(input ...string) []string {
132+
var filtered []string
133+
for _, path := range input {
134+
if f.MatchString(path) {
135+
continue
136+
}
137+
filtered = append(filtered, path)
138+
}
139+
return filtered
140+
}
141+
142+
// removeAll is a filter that will not return any inputs.
143+
type removeAll struct{}
144+
145+
func (a removeAll) apply(...string) []string {
146+
return nil
147+
}

internal/platform-support/tegra/filter_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525
func TestIgnorePatterns(t *testing.T) {
2626
testCases := []struct {
2727
description string
28-
blockedFilter []string
28+
blockedFilter pathPatterns
2929
input []string
3030
expected []string
3131
}{
@@ -50,7 +50,7 @@ func TestIgnorePatterns(t *testing.T) {
5050

5151
for _, tc := range testCases {
5252
t.Run(tc.description, func(t *testing.T) {
53-
filtered := ignoreMountSpecPatterns(tc.blockedFilter).Apply(tc.input...)
53+
filtered := ignoreSymlinkMountSpecPatterns(tc.blockedFilter).filter(tc.input...)
5454
require.ElementsMatch(t, tc.expected, filtered)
5555
})
5656
}

0 commit comments

Comments
 (0)