This commit is contained in:
Evan Lezar 2024-09-23 14:53:20 +02:00 committed by GitHub
commit 90614051db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 513 additions and 147 deletions

View File

@ -60,6 +60,11 @@ type options struct {
files cli.StringSlice files cli.StringSlice
ignorePatterns cli.StringSlice ignorePatterns cli.StringSlice
} }
// spec represents the CDI spec options.
spec struct {
allowAdditionalGIDs bool
}
} }
// NewCommand constructs a generate-cdi command with the specified logger // NewCommand constructs a generate-cdi command with the specified logger
@ -169,6 +174,11 @@ func (m command) build() *cli.Command {
Usage: "Specify a pattern the CSV mount specifications.", Usage: "Specify a pattern the CSV mount specifications.",
Destination: &opts.csv.ignorePatterns, Destination: &opts.csv.ignorePatterns,
}, },
&cli.BoolFlag{
Name: "--allow-additional-gids",
Usage: "Allow the use of the additionalGIDs field for generated CDI specifications. Note this will generate a v0.7.0 CDI specification.",
Destination: &opts.spec.allowAdditionalGIDs,
},
} }
return &c return &c
@ -273,6 +283,7 @@ func (m command) generateSpec(opts *options) (spec.Interface, error) {
nvcdi.WithLibrarySearchPaths(opts.librarySearchPaths.Value()), nvcdi.WithLibrarySearchPaths(opts.librarySearchPaths.Value()),
nvcdi.WithCSVFiles(opts.csv.files.Value()), nvcdi.WithCSVFiles(opts.csv.files.Value()),
nvcdi.WithCSVIgnorePatterns(opts.csv.ignorePatterns.Value()), nvcdi.WithCSVIgnorePatterns(opts.csv.ignorePatterns.Value()),
nvcdi.WithAllowAdditionalGIDs(opts.spec.allowAdditionalGIDs),
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create CDI library: %v", err) return nil, fmt.Errorf("failed to create CDI library: %v", err)

View File

@ -17,12 +17,17 @@
package config package config
type featureName string type featureName string
type feature bool
const ( const (
FeatureGDS = featureName("gds") FeatureAllowAdditionalGIDs = featureName("allow-additional-gids")
FeatureMOFED = featureName("mofed") FeatureGDRCopy = featureName("gdrcopy")
FeatureNVSWITCH = featureName("nvswitch") FeatureGDS = featureName("gds")
FeatureGDRCopy = featureName("gdrcopy") FeatureMOFED = featureName("mofed")
FeatureNVSWITCH = featureName("nvswitch")
featureEnabled feature = true
featureDisabled feature = false
) )
// features specifies a set of named features. // features specifies a set of named features.
@ -31,19 +36,23 @@ type features struct {
MOFED *feature `toml:"mofed,omitempty"` MOFED *feature `toml:"mofed,omitempty"`
NVSWITCH *feature `toml:"nvswitch,omitempty"` NVSWITCH *feature `toml:"nvswitch,omitempty"`
GDRCopy *feature `toml:"gdrcopy,omitempty"` GDRCopy *feature `toml:"gdrcopy,omitempty"`
// AllowAdditionalGIDs triggers the additionalGIDs field in internal CDI
// specifications to be populated if required. This can be useful when
// running the container as a user id that may not have access to device
// nodes.
AllowAdditionalGIDs *feature `toml:"allow-additional-gids,omitempty"`
} }
type feature bool
// IsEnabled checks whether a specified named feature is enabled. // IsEnabled checks whether a specified named feature is enabled.
// An optional list of environments to check for feature-specific environment // An optional list of environments to check for feature-specific environment
// variables can also be supplied. // variables can also be supplied.
func (fs features) IsEnabled(n featureName, in ...getenver) bool { func (fs features) IsEnabled(n featureName, in ...getenver) bool {
featureEnvvars := map[featureName]string{ featureEnvvars := map[featureName]string{
FeatureGDS: "NVIDIA_GDS", FeatureGDS: "NVIDIA_GDS",
FeatureMOFED: "NVIDIA_MOFED", FeatureMOFED: "NVIDIA_MOFED",
FeatureNVSWITCH: "NVIDIA_NVSWITCH", FeatureNVSWITCH: "NVIDIA_NVSWITCH",
FeatureGDRCopy: "NVIDIA_GDRCOPY", FeatureGDRCopy: "NVIDIA_GDRCOPY",
FeatureAllowAdditionalGIDs: "NVIDIA_ALLOW_ADDITIONAL_GIDS",
} }
envvar := featureEnvvars[n] envvar := featureEnvvars[n]
@ -56,6 +65,8 @@ func (fs features) IsEnabled(n featureName, in ...getenver) bool {
return fs.NVSWITCH.isEnabled(envvar, in...) return fs.NVSWITCH.isEnabled(envvar, in...)
case FeatureGDRCopy: case FeatureGDRCopy:
return fs.GDRCopy.isEnabled(envvar, in...) return fs.GDRCopy.isEnabled(envvar, in...)
case FeatureAllowAdditionalGIDs:
return fs.AllowAdditionalGIDs.isEnabled(envvar, in...)
default: default:
return false return false
} }
@ -66,17 +77,19 @@ func (fs features) IsEnabled(n featureName, in ...getenver) bool {
// associated envvar is checked in the specified getenver for the string "enabled" // associated envvar is checked in the specified getenver for the string "enabled"
// A CUDA container / image can be passed here. // A CUDA container / image can be passed here.
func (f *feature) isEnabled(envvar string, ins ...getenver) bool { func (f *feature) isEnabled(envvar string, ins ...getenver) bool {
if envvar != "" {
for _, in := range ins {
switch in.Getenv(envvar) {
case "enabled", "true":
return true
case "disabled", "false":
return false
}
}
}
if f != nil { if f != nil {
return bool(*f) return bool(*f)
} }
if envvar == "" {
return false
}
for _, in := range ins {
if in.Getenv(envvar) == "enabled" {
return true
}
}
return false return false
} }

View File

@ -0,0 +1,150 @@
/**
# Copyright 2024 NVIDIA CORPORATION
#
# 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 (
"testing"
"github.com/stretchr/testify/require"
)
func TestFeatures(t *testing.T) {
testCases := []struct {
description string
features features
expected map[featureName]bool
envs []getenver
}{
{
description: "empty features",
expected: map[featureName]bool{
FeatureAllowAdditionalGIDs: false,
FeatureGDRCopy: false,
FeatureGDS: false,
FeatureMOFED: false,
FeatureNVSWITCH: false,
},
},
{
description: "envvar sets features if enabled",
expected: map[featureName]bool{
FeatureAllowAdditionalGIDs: true,
FeatureGDRCopy: false,
FeatureGDS: false,
FeatureMOFED: false,
FeatureNVSWITCH: false,
},
envs: []getenver{
mockEnver{
"NVIDIA_ALLOW_ADDITIONAL_GIDS": "enabled",
},
},
},
{
description: "envvar sets features if true",
expected: map[featureName]bool{
FeatureAllowAdditionalGIDs: true,
FeatureGDRCopy: false,
FeatureGDS: false,
FeatureMOFED: false,
FeatureNVSWITCH: false,
},
envs: []getenver{
mockEnver{
"NVIDIA_ALLOW_ADDITIONAL_GIDS": "true",
},
},
},
{
description: "feature sets feature",
features: features{
AllowAdditionalGIDs: ptr(featureEnabled),
},
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
for n, v := range tc.expected {
t.Run(tc.description+"-"+string(n), func(t *testing.T) {
require.EqualValues(t, v, tc.features.IsEnabled(n, tc.envs...))
})
}
})
}
}
func TestFeature(t *testing.T) {
testCases := []struct {
description string
feature *feature
envvar string
envs []getenver
expected bool
}{
{
description: "nil feature is false",
feature: nil,
expected: false,
},
{
description: "feature enables",
feature: ptr(featureEnabled),
expected: true,
},
{
description: "feature disabled",
feature: ptr(featureDisabled),
expected: false,
},
{
description: "envvar overrides feature disabled",
feature: ptr(featureDisabled),
envvar: "FEATURE",
envs: []getenver{
mockEnver{"FEATURE": "enabled"},
},
expected: true,
},
{
description: "envvar overrides feature enabled",
feature: ptr(featureEnabled),
envvar: "FEATURE",
envs: []getenver{
mockEnver{"FEATURE": "disabled"},
},
expected: false,
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
require.EqualValues(t, tc.expected, tc.feature.isEnabled(tc.envvar, tc.envs...))
})
}
}
type mockEnver map[string]string
func (m mockEnver) Getenv(k string) string {
return m[k]
}
func ptr[T any](x T) *T {
return &x
}

29
internal/edits/api.go Normal file
View File

@ -0,0 +1,29 @@
/**
# Copyright 2024 NVIDIA CORPORATION
#
# 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 edits
import (
"tags.cncf.io/container-device-interface/pkg/cdi"
"github.com/NVIDIA/nvidia-container-toolkit/internal/discover"
"github.com/NVIDIA/nvidia-container-toolkit/internal/oci"
)
type Interface interface {
EditsFromDiscoverer(discover.Discover) (*cdi.ContainerEdits, error)
SpecModifierFromDiscoverer(discover.Discover) (oci.SpecModifier, error)
}

View File

@ -17,6 +17,9 @@
package edits package edits
import ( import (
"os"
"golang.org/x/sys/unix"
"tags.cncf.io/container-device-interface/pkg/cdi" "tags.cncf.io/container-device-interface/pkg/cdi"
"tags.cncf.io/container-device-interface/specs-go" "tags.cncf.io/container-device-interface/specs-go"
@ -26,15 +29,23 @@ import (
type device discover.Device type device discover.Device
// toEdits converts a discovered device to CDI Container Edits. // toEdits converts a discovered device to CDI Container Edits.
func (d device) toEdits() (*cdi.ContainerEdits, error) { func (d device) toEdits(allowAdditionalGIDs bool) (*cdi.ContainerEdits, error) {
deviceNode, err := d.toSpec() deviceNode, err := d.toSpec()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var additionalGIDs []uint32
if allowAdditionalGIDs {
if requiredGID := d.getRequiredGID(); requiredGID != 0 {
additionalGIDs = append(additionalGIDs, requiredGID)
}
}
e := cdi.ContainerEdits{ e := cdi.ContainerEdits{
ContainerEdits: &specs.ContainerEdits{ ContainerEdits: &specs.ContainerEdits{
DeviceNodes: []*specs.DeviceNode{deviceNode}, DeviceNodes: []*specs.DeviceNode{deviceNode},
AdditionalGIDs: additionalGIDs,
}, },
} }
return &e, nil return &e, nil
@ -52,6 +63,7 @@ func (d device) toSpec() (*specs.DeviceNode, error) {
if hostPath == d.Path { if hostPath == d.Path {
hostPath = "" hostPath = ""
} }
s := specs.DeviceNode{ s := specs.DeviceNode{
HostPath: hostPath, HostPath: hostPath,
Path: d.Path, Path: d.Path,
@ -59,3 +71,29 @@ func (d device) toSpec() (*specs.DeviceNode, error) {
return &s, nil return &s, nil
} }
// getRequiredGID returns the group id of the device if the device is not world read/writable.
// If the information cannot be extracted or an error occurs, 0 is returned.
func (d device) getRequiredGID() uint32 {
path := d.HostPath
if path == "" {
path = d.Path
}
if path == "" {
return 0
}
var stat unix.Stat_t
if err := unix.Lstat(path, &stat); err != nil {
return 0
}
// This is only supported for char devices
if stat.Mode&unix.S_IFMT != unix.S_IFCHR {
return 0
}
if permissionsForOther := os.FileMode(stat.Mode).Perm(); permissionsForOther&06 == 0 {
return stat.Gid
}
return 0
}

View File

@ -17,15 +17,11 @@
package edits package edits
import ( import (
"fmt"
ociSpecs "github.com/opencontainers/runtime-spec/specs-go" ociSpecs "github.com/opencontainers/runtime-spec/specs-go"
"tags.cncf.io/container-device-interface/pkg/cdi" "tags.cncf.io/container-device-interface/pkg/cdi"
"tags.cncf.io/container-device-interface/specs-go" "tags.cncf.io/container-device-interface/specs-go"
"github.com/NVIDIA/nvidia-container-toolkit/internal/discover"
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger" "github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
"github.com/NVIDIA/nvidia-container-toolkit/internal/oci"
) )
type edits struct { type edits struct {
@ -33,58 +29,6 @@ type edits struct {
logger logger.Interface logger logger.Interface
} }
// NewSpecEdits creates a SpecModifier that defines the required OCI spec edits (as CDI ContainerEdits) from the specified
// discoverer.
func NewSpecEdits(logger logger.Interface, d discover.Discover) (oci.SpecModifier, error) {
c, err := FromDiscoverer(d)
if err != nil {
return nil, fmt.Errorf("error constructing container edits: %v", err)
}
e := edits{
ContainerEdits: *c,
logger: logger,
}
return &e, nil
}
// FromDiscoverer creates CDI container edits for the specified discoverer.
func FromDiscoverer(d discover.Discover) (*cdi.ContainerEdits, error) {
devices, err := d.Devices()
if err != nil {
return nil, fmt.Errorf("failed to discover devices: %v", err)
}
mounts, err := d.Mounts()
if err != nil {
return nil, fmt.Errorf("failed to discover mounts: %v", err)
}
hooks, err := d.Hooks()
if err != nil {
return nil, fmt.Errorf("failed to discover hooks: %v", err)
}
c := NewContainerEdits()
for _, d := range devices {
edits, err := device(d).toEdits()
if err != nil {
return nil, fmt.Errorf("failed to created container edits for device: %v", err)
}
c.Append(edits)
}
for _, m := range mounts {
c.Append(mount(m).toEdits())
}
for _, h := range hooks {
c.Append(hook(h).toEdits())
}
return c, nil
}
// NewContainerEdits is a utility function to create a CDI ContainerEdits struct. // NewContainerEdits is a utility function to create a CDI ContainerEdits struct.
func NewContainerEdits() *cdi.ContainerEdits { func NewContainerEdits() *cdi.ContainerEdits {
c := cdi.ContainerEdits{ c := cdi.ContainerEdits{

View File

@ -25,7 +25,7 @@ import (
) )
func TestFromDiscovererAllowsMountsToIterate(t *testing.T) { func TestFromDiscovererAllowsMountsToIterate(t *testing.T) {
edits, err := FromDiscoverer(discover.None{}) edits, err := New().EditsFromDiscoverer(discover.None{})
require.NoError(t, err) require.NoError(t, err)
require.Empty(t, edits.Mounts) require.Empty(t, edits.Mounts)

92
internal/edits/lib.go Normal file
View File

@ -0,0 +1,92 @@
/**
# Copyright 2024 NVIDIA CORPORATION
#
# 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 edits
import (
"fmt"
"tags.cncf.io/container-device-interface/pkg/cdi"
"github.com/NVIDIA/nvidia-container-toolkit/internal/discover"
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
"github.com/NVIDIA/nvidia-container-toolkit/internal/oci"
)
// New creates an Interface from the supplied options.
func New(opts ...Option) Interface {
o := &options{}
for _, opt := range opts {
opt(o)
}
if o.logger == nil {
o.logger = logger.New()
}
return o
}
// EditsFromDiscoverer creates CDI container edits for the specified discoverer.
func (o *options) EditsFromDiscoverer(d discover.Discover) (*cdi.ContainerEdits, error) {
devices, err := d.Devices()
if err != nil {
return nil, fmt.Errorf("failed to discover devices: %w", err)
}
mounts, err := d.Mounts()
if err != nil {
return nil, fmt.Errorf("failed to discover mounts: %w", err)
}
hooks, err := d.Hooks()
if err != nil {
return nil, fmt.Errorf("failed to discover hooks: %w", err)
}
c := NewContainerEdits()
for _, d := range devices {
edits, err := device(d).toEdits(o.allowAdditionalGIDs)
if err != nil {
return nil, fmt.Errorf("failed to create container edits for device: %w", err)
}
c.Append(edits)
}
for _, m := range mounts {
c.Append(mount(m).toEdits())
}
for _, h := range hooks {
c.Append(hook(h).toEdits())
}
return c, nil
}
// SpecModifierFromDiscoverer creates a SpecModifier that defines the required OCI spec edits (as CDI ContainerEdits) from the specified
// discoverer.
func (o *options) SpecModifierFromDiscoverer(d discover.Discover) (oci.SpecModifier, error) {
c, err := o.EditsFromDiscoverer(d)
if err != nil {
return nil, fmt.Errorf("error constructing container edits: %w", err)
}
e := edits{
ContainerEdits: *c,
logger: o.logger,
}
return &e, nil
}

38
internal/edits/options.go Normal file
View File

@ -0,0 +1,38 @@
/**
# Copyright 2024 NVIDIA CORPORATION
#
# 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 edits
import "github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
type options struct {
logger logger.Interface
allowAdditionalGIDs bool
}
type Option func(*options)
func WithLogger(logger logger.Interface) Option {
return func(o *options) {
o.logger = logger
}
}
func WithAllowAdditionalGIDs(allowAdditionalGIDs bool) Option {
return func(o *options) {
o.allowAdditionalGIDs = allowAdditionalGIDs
}
}

View File

@ -0,0 +1,30 @@
/**
# Copyright 2024 NVIDIA CORPORATION
#
# 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 edits
import (
"tags.cncf.io/container-device-interface/pkg/cdi"
"tags.cncf.io/container-device-interface/specs-go"
)
// NewResource creates a CDI resource (Device) with the specified name.
func NewResource(name string, edits *cdi.ContainerEdits) specs.Device {
return specs.Device{
Name: name,
ContainerEdits: *edits.ContainerEdits,
}
}

View File

@ -28,16 +28,18 @@ import (
) )
type discoverModifier struct { type discoverModifier struct {
logger logger.Interface logger logger.Interface
discoverer discover.Discover discoverer discover.Discover
allowAdditionalGIDs bool
} }
// NewModifierFromDiscoverer creates a modifier that applies the discovered // NewModifierFromDiscoverer creates a modifier that applies the discovered
// modifications to an OCI spec if required by the runtime wrapper. // modifications to an OCI spec if required by the runtime wrapper.
func NewModifierFromDiscoverer(logger logger.Interface, d discover.Discover) (oci.SpecModifier, error) { func NewModifierFromDiscoverer(logger logger.Interface, d discover.Discover, allowAdditionalGIDs bool) (oci.SpecModifier, error) {
m := discoverModifier{ m := discoverModifier{
logger: logger, logger: logger,
discoverer: d, discoverer: d,
allowAdditionalGIDs: allowAdditionalGIDs,
} }
return &m, nil return &m, nil
} }
@ -45,7 +47,12 @@ func NewModifierFromDiscoverer(logger logger.Interface, d discover.Discover) (oc
// Modify applies the modifications required by discoverer to the incomming OCI spec. // Modify applies the modifications required by discoverer to the incomming OCI spec.
// These modifications are applied in-place. // These modifications are applied in-place.
func (m discoverModifier) Modify(spec *specs.Spec) error { func (m discoverModifier) Modify(spec *specs.Spec) error {
specEdits, err := edits.NewSpecEdits(m.logger, m.discoverer) e := edits.New(
edits.WithLogger(m.logger),
edits.WithAllowAdditionalGIDs(m.allowAdditionalGIDs),
)
specEdits, err := e.SpecModifierFromDiscoverer(m.discoverer)
if err != nil { if err != nil {
return fmt.Errorf("failed to get required container edits: %v", err) return fmt.Errorf("failed to get required container edits: %v", err)
} }

View File

@ -132,7 +132,7 @@ func TestDiscoverModifier(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) { t.Run(tc.description, func(t *testing.T) {
m, err := NewModifierFromDiscoverer(logger, tc.discover) m, err := NewModifierFromDiscoverer(logger, tc.discover, true)
require.NoError(t, err) require.NoError(t, err)
err = m.Modify(tc.spec) err = m.Modify(tc.spec)

View File

@ -78,5 +78,7 @@ func NewFeatureGatedModifier(logger logger.Interface, cfg *config.Config, image
discoverers = append(discoverers, d) discoverers = append(discoverers, d)
} }
return NewModifierFromDiscoverer(logger, discover.Merge(discoverers...)) allowAdditionalGIDs := !cfg.Features.IsEnabled(config.FeatureAllowAdditionalGIDs, image)
return NewModifierFromDiscoverer(logger, discover.Merge(discoverers...), allowAdditionalGIDs)
} }

View File

@ -62,7 +62,9 @@ func NewGraphicsModifier(logger logger.Interface, cfg *config.Config, image imag
drmNodes, drmNodes,
mounts, mounts,
) )
return NewModifierFromDiscoverer(logger, d)
allowAdditionalGIDs := cfg.Features.IsEnabled(config.FeatureAllowAdditionalGIDs, image)
return NewModifierFromDiscoverer(logger, d, allowAdditionalGIDs)
} }
// requiresGraphicsModifier determines whether a graphics modifier is required. // requiresGraphicsModifier determines whether a graphics modifier is required.

View File

@ -4,9 +4,8 @@
package oci package oci
import ( import (
"sync"
"github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-spec/specs-go"
"sync"
) )
// Ensure, that SpecMock does implement Spec. // Ensure, that SpecMock does implement Spec.

View File

@ -30,7 +30,7 @@ import (
// GetGPUDeviceSpecs returns the CDI device specs for the full GPU represented by 'device'. // GetGPUDeviceSpecs returns the CDI device specs for the full GPU represented by 'device'.
func (l *nvmllib) GetGPUDeviceSpecs(i int, d device.Device) ([]specs.Device, error) { func (l *nvmllib) GetGPUDeviceSpecs(i int, d device.Device) ([]specs.Device, error) {
edits, err := l.GetGPUDeviceEdits(d) e, err := l.GetGPUDeviceEdits(d)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get edits for device: %v", err) return nil, fmt.Errorf("failed to get edits for device: %v", err)
} }
@ -41,10 +41,7 @@ func (l *nvmllib) GetGPUDeviceSpecs(i int, d device.Device) ([]specs.Device, err
return nil, fmt.Errorf("failed to get device name: %v", err) return nil, fmt.Errorf("failed to get device name: %v", err)
} }
for _, name := range names { for _, name := range names {
spec := specs.Device{ spec := edits.NewResource(name, e)
Name: name,
ContainerEdits: *edits.ContainerEdits,
}
deviceSpecs = append(deviceSpecs, spec) deviceSpecs = append(deviceSpecs, spec)
} }
@ -58,12 +55,7 @@ func (l *nvmllib) GetGPUDeviceEdits(d device.Device) (*cdi.ContainerEdits, error
return nil, fmt.Errorf("failed to create device discoverer: %v", err) return nil, fmt.Errorf("failed to create device discoverer: %v", err)
} }
editsForDevice, err := edits.FromDiscoverer(device) return (*nvcdilib)(l).editsFromDiscoverer(device)
if err != nil {
return nil, fmt.Errorf("failed to create container edits for device: %v", err)
}
return editsForDevice, nil
} }
// newFullGPUDiscoverer creates a discoverer for the full GPU defined by the specified device. // newFullGPUDiscoverer creates a discoverer for the full GPU defined by the specified device.

View File

@ -38,22 +38,19 @@ func (l *gdslib) GetAllDeviceSpecs() ([]specs.Device, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create GPUDirect Storage discoverer: %v", err) return nil, fmt.Errorf("failed to create GPUDirect Storage discoverer: %v", err)
} }
edits, err := edits.FromDiscoverer(discoverer) e, err := (*nvcdilib)(l).editsFromDiscoverer(discoverer)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create container edits for GPUDirect Storage: %v", err) return nil, fmt.Errorf("failed to create container edits for GPUDirect Storage: %v", err)
} }
deviceSpec := specs.Device{ deviceSpec := edits.NewResource("all", e)
Name: "all",
ContainerEdits: *edits.ContainerEdits,
}
return []specs.Device{deviceSpec}, nil return []specs.Device{deviceSpec}, nil
} }
// GetCommonEdits generates a CDI specification that can be used for ANY devices // GetCommonEdits generates a CDI specification that can be used for ANY devices
func (l *gdslib) GetCommonEdits() (*cdi.ContainerEdits, error) { func (l *gdslib) GetCommonEdits() (*cdi.ContainerEdits, error) {
return edits.FromDiscoverer(discover.None{}) return edits.NewContainerEdits(), nil
} }
// GetSpec is unsppported for the gdslib specs. // GetSpec is unsppported for the gdslib specs.

View File

@ -23,7 +23,6 @@ import (
"tags.cncf.io/container-device-interface/pkg/cdi" "tags.cncf.io/container-device-interface/pkg/cdi"
"tags.cncf.io/container-device-interface/specs-go" "tags.cncf.io/container-device-interface/specs-go"
"github.com/NVIDIA/nvidia-container-toolkit/internal/discover"
"github.com/NVIDIA/nvidia-container-toolkit/internal/edits" "github.com/NVIDIA/nvidia-container-toolkit/internal/edits"
"github.com/NVIDIA/nvidia-container-toolkit/internal/platform-support/tegra" "github.com/NVIDIA/nvidia-container-toolkit/internal/platform-support/tegra"
"github.com/NVIDIA/nvidia-container-toolkit/pkg/nvcdi/spec" "github.com/NVIDIA/nvidia-container-toolkit/pkg/nvcdi/spec"
@ -53,7 +52,7 @@ func (l *csvlib) GetAllDeviceSpecs() ([]specs.Device, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create discoverer for CSV files: %v", err) return nil, fmt.Errorf("failed to create discoverer for CSV files: %v", err)
} }
e, err := edits.FromDiscoverer(d) e, err := (*nvcdilib)(l).editsFromDiscoverer(d)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create container edits for CSV files: %v", err) return nil, fmt.Errorf("failed to create container edits for CSV files: %v", err)
} }
@ -64,10 +63,7 @@ func (l *csvlib) GetAllDeviceSpecs() ([]specs.Device, error) {
} }
var deviceSpecs []specs.Device var deviceSpecs []specs.Device
for _, name := range names { for _, name := range names {
deviceSpec := specs.Device{ deviceSpec := edits.NewResource(name, e)
Name: name,
ContainerEdits: *e.ContainerEdits,
}
deviceSpecs = append(deviceSpecs, deviceSpec) deviceSpecs = append(deviceSpecs, deviceSpec)
} }
@ -76,8 +72,7 @@ func (l *csvlib) GetAllDeviceSpecs() ([]specs.Device, error) {
// GetCommonEdits generates a CDI specification that can be used for ANY devices // GetCommonEdits generates a CDI specification that can be used for ANY devices
func (l *csvlib) GetCommonEdits() (*cdi.ContainerEdits, error) { func (l *csvlib) GetCommonEdits() (*cdi.ContainerEdits, error) {
d := discover.None{} return edits.NewContainerEdits(), nil
return edits.FromDiscoverer(d)
} }
// GetGPUDeviceEdits generates a CDI specification that can be used for GPU devices // GetGPUDeviceEdits generates a CDI specification that can be used for GPU devices

View File

@ -26,7 +26,6 @@ import (
"tags.cncf.io/container-device-interface/pkg/cdi" "tags.cncf.io/container-device-interface/pkg/cdi"
"tags.cncf.io/container-device-interface/specs-go" "tags.cncf.io/container-device-interface/specs-go"
"github.com/NVIDIA/nvidia-container-toolkit/internal/edits"
"github.com/NVIDIA/nvidia-container-toolkit/pkg/nvcdi/spec" "github.com/NVIDIA/nvidia-container-toolkit/pkg/nvcdi/spec"
) )
@ -74,7 +73,7 @@ func (l *nvmllib) GetCommonEdits() (*cdi.ContainerEdits, error) {
return nil, fmt.Errorf("failed to create discoverer for common entities: %v", err) return nil, fmt.Errorf("failed to create discoverer for common entities: %v", err)
} }
return edits.FromDiscoverer(common) return (*nvcdilib)(l).editsFromDiscoverer(common)
} }
// GetDeviceSpecsByID returns the CDI device specs for the GPU(s) represented by // GetDeviceSpecsByID returns the CDI device specs for the GPU(s) represented by

View File

@ -39,15 +39,12 @@ func (l *wsllib) GetSpec() (spec.Interface, error) {
// GetAllDeviceSpecs returns the device specs for all available devices. // GetAllDeviceSpecs returns the device specs for all available devices.
func (l *wsllib) GetAllDeviceSpecs() ([]specs.Device, error) { func (l *wsllib) GetAllDeviceSpecs() ([]specs.Device, error) {
device := newDXGDeviceDiscoverer(l.logger, l.devRoot) device := newDXGDeviceDiscoverer(l.logger, l.devRoot)
deviceEdits, err := edits.FromDiscoverer(device) e, err := (*nvcdilib)(l).editsFromDiscoverer(device)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create container edits for DXG device: %v", err) return nil, fmt.Errorf("failed to create container edits for DXG device: %v", err)
} }
deviceSpec := specs.Device{ deviceSpec := edits.NewResource("all", e)
Name: "all",
ContainerEdits: *deviceEdits.ContainerEdits,
}
return []specs.Device{deviceSpec}, nil return []specs.Device{deviceSpec}, nil
} }
@ -59,7 +56,7 @@ func (l *wsllib) GetCommonEdits() (*cdi.ContainerEdits, error) {
return nil, fmt.Errorf("failed to create discoverer for WSL driver: %v", err) return nil, fmt.Errorf("failed to create discoverer for WSL driver: %v", err)
} }
return edits.FromDiscoverer(driver) return (*nvcdilib)(l).editsFromDiscoverer(driver)
} }
// GetGPUDeviceEdits generates a CDI specification that can be used for GPU devices // GetGPUDeviceEdits generates a CDI specification that can be used for GPU devices

View File

@ -24,6 +24,8 @@ import (
"github.com/NVIDIA/go-nvml/pkg/nvml" "github.com/NVIDIA/go-nvml/pkg/nvml"
"tags.cncf.io/container-device-interface/pkg/cdi" "tags.cncf.io/container-device-interface/pkg/cdi"
"github.com/NVIDIA/nvidia-container-toolkit/internal/discover"
"github.com/NVIDIA/nvidia-container-toolkit/internal/edits"
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger" "github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup/root" "github.com/NVIDIA/nvidia-container-toolkit/internal/lookup/root"
"github.com/NVIDIA/nvidia-container-toolkit/internal/platform-support/tegra/csv" "github.com/NVIDIA/nvidia-container-toolkit/internal/platform-support/tegra/csv"
@ -63,6 +65,8 @@ type nvcdilib struct {
infolib info.Interface infolib info.Interface
mergedDeviceOptions []transform.MergedDeviceOption mergedDeviceOptions []transform.MergedDeviceOption
allowAdditionalGIDs bool
} }
// New creates a new nvcdi library // New creates a new nvcdi library
@ -190,6 +194,15 @@ func (m *wrapper) GetCommonEdits() (*cdi.ContainerEdits, error) {
return edits, nil return edits, nil
} }
// editsFromDiscoverer
func (l *nvcdilib) editsFromDiscoverer(d discover.Discover) (*cdi.ContainerEdits, error) {
e := edits.New(
edits.WithLogger(l.logger),
edits.WithAllowAdditionalGIDs(l.allowAdditionalGIDs),
)
return e.EditsFromDiscoverer(d)
}
// resolveMode resolves the mode for CDI spec generation based on the current system. // resolveMode resolves the mode for CDI spec generation based on the current system.
func (l *nvcdilib) resolveMode() (rmode string) { func (l *nvcdilib) resolveMode() (rmode string) {
if l.mode != ModeAuto { if l.mode != ModeAuto {

View File

@ -43,19 +43,16 @@ func (m *managementlib) GetAllDeviceSpecs() ([]specs.Device, error) {
return nil, fmt.Errorf("failed to create device discoverer: %v", err) return nil, fmt.Errorf("failed to create device discoverer: %v", err)
} }
edits, err := edits.FromDiscoverer(devices) e, err := (*nvcdilib)(m).editsFromDiscoverer(devices)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create edits from discoverer: %v", err) return nil, fmt.Errorf("failed to create edits from discoverer: %v", err)
} }
if len(edits.DeviceNodes) == 0 { if len(e.DeviceNodes) == 0 {
return nil, fmt.Errorf("no NVIDIA device nodes found") return nil, fmt.Errorf("no NVIDIA device nodes found")
} }
device := specs.Device{ device := edits.NewResource("all", e)
Name: "all",
ContainerEdits: *edits.ContainerEdits,
}
return []specs.Device{device}, nil return []specs.Device{device}, nil
} }
@ -71,7 +68,7 @@ func (m *managementlib) GetCommonEdits() (*cdi.ContainerEdits, error) {
return nil, fmt.Errorf("failed to create driver library discoverer: %v", err) return nil, fmt.Errorf("failed to create driver library discoverer: %v", err)
} }
edits, err := edits.FromDiscoverer(driver) edits, err := (*nvcdilib)(m).editsFromDiscoverer(driver)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create edits from discoverer: %v", err) return nil, fmt.Errorf("failed to create edits from discoverer: %v", err)
} }

View File

@ -29,7 +29,7 @@ import (
// GetMIGDeviceSpecs returns the CDI device specs for the full GPU represented by 'device'. // GetMIGDeviceSpecs returns the CDI device specs for the full GPU represented by 'device'.
func (l *nvmllib) GetMIGDeviceSpecs(i int, d device.Device, j int, mig device.MigDevice) ([]specs.Device, error) { func (l *nvmllib) GetMIGDeviceSpecs(i int, d device.Device, j int, mig device.MigDevice) ([]specs.Device, error) {
edits, err := l.GetMIGDeviceEdits(d, mig) e, err := l.GetMIGDeviceEdits(d, mig)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get edits for device: %v", err) return nil, fmt.Errorf("failed to get edits for device: %v", err)
} }
@ -40,10 +40,7 @@ func (l *nvmllib) GetMIGDeviceSpecs(i int, d device.Device, j int, mig device.Mi
} }
var deviceSpecs []specs.Device var deviceSpecs []specs.Device
for _, name := range names { for _, name := range names {
spec := specs.Device{ spec := edits.NewResource(name, e)
Name: name,
ContainerEdits: *edits.ContainerEdits,
}
deviceSpecs = append(deviceSpecs, spec) deviceSpecs = append(deviceSpecs, spec)
} }
return deviceSpecs, nil return deviceSpecs, nil
@ -60,7 +57,7 @@ func (l *nvmllib) GetMIGDeviceEdits(parent device.Device, mig device.MigDevice)
return nil, fmt.Errorf("failed to create device discoverer: %v", err) return nil, fmt.Errorf("failed to create device discoverer: %v", err)
} }
editsForDevice, err := edits.FromDiscoverer(deviceNodes) editsForDevice, err := (*nvcdilib)(l).editsFromDiscoverer(deviceNodes)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create container edits for Compute Instance: %v", err) return nil, fmt.Errorf("failed to create container edits for Compute Instance: %v", err)
} }

View File

@ -38,22 +38,19 @@ func (l *mofedlib) GetAllDeviceSpecs() ([]specs.Device, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create MOFED discoverer: %v", err) return nil, fmt.Errorf("failed to create MOFED discoverer: %v", err)
} }
edits, err := edits.FromDiscoverer(discoverer) e, err := (*nvcdilib)(l).editsFromDiscoverer(discoverer)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create container edits for MOFED devices: %v", err) return nil, fmt.Errorf("failed to create container edits for MOFED devices: %v", err)
} }
deviceSpec := specs.Device{ deviceSpec := edits.NewResource("all", e)
Name: "all",
ContainerEdits: *edits.ContainerEdits,
}
return []specs.Device{deviceSpec}, nil return []specs.Device{deviceSpec}, nil
} }
// GetCommonEdits generates a CDI specification that can be used for ANY devices // GetCommonEdits generates a CDI specification that can be used for ANY devices
func (l *mofedlib) GetCommonEdits() (*cdi.ContainerEdits, error) { func (l *mofedlib) GetCommonEdits() (*cdi.ContainerEdits, error) {
return edits.FromDiscoverer(discover.None{}) return edits.NewContainerEdits(), nil
} }
// GetSpec is unsppported for the mofedlib specs. // GetSpec is unsppported for the mofedlib specs.

View File

@ -4,9 +4,8 @@
package nvcdi package nvcdi
import ( import (
"sync"
"github.com/NVIDIA/go-nvml/pkg/nvml" "github.com/NVIDIA/go-nvml/pkg/nvml"
"sync"
) )
// Ensure, that nvmlUUIDerMock does implement nvmlUUIDer. // Ensure, that nvmlUUIDerMock does implement nvmlUUIDer.

View File

@ -155,3 +155,11 @@ func WithLibrarySearchPaths(paths []string) Option {
o.librarySearchPaths = paths o.librarySearchPaths = paths
} }
} }
// WithAllowAdditionalGIDs specifies whether a generated CDI spec can contain
// the additionaGIDs field.
func WithAllowAdditionalGIDs(allowAdditionalGIDs bool) Option {
return func(o *nvcdilib) {
o.allowAdditionalGIDs = allowAdditionalGIDs
}
}

View File

@ -17,6 +17,8 @@
package transform package transform
import ( import (
"slices"
"tags.cncf.io/container-device-interface/specs-go" "tags.cncf.io/container-device-interface/specs-go"
) )
@ -50,6 +52,12 @@ func (d dedupe) Transform(spec *specs.Spec) error {
} }
func (d dedupe) transformEdits(edits *specs.ContainerEdits) error { func (d dedupe) transformEdits(edits *specs.ContainerEdits) error {
additionalGIDs, err := d.deduplicateAdditionalGIDs(edits.AdditionalGIDs)
if err != nil {
return err
}
edits.AdditionalGIDs = additionalGIDs
deviceNodes, err := d.deduplicateDeviceNodes(edits.DeviceNodes) deviceNodes, err := d.deduplicateDeviceNodes(edits.DeviceNodes)
if err != nil { if err != nil {
return err return err
@ -150,3 +158,19 @@ func (d dedupe) deduplicateMounts(entities []*specs.Mount) ([]*specs.Mount, erro
} }
return mounts, nil return mounts, nil
} }
func (d dedupe) deduplicateAdditionalGIDs(entities []uint32) ([]uint32, error) {
seen := make(map[uint32]bool)
var additionalGIDs []uint32
for _, e := range entities {
if seen[e] {
continue
}
seen[e] = true
additionalGIDs = append(additionalGIDs, e)
}
slices.Sort(additionalGIDs)
return additionalGIDs, nil
}

View File

@ -109,7 +109,6 @@ func mergeDeviceSpecs(deviceSpecs []specs.Device, mergedDeviceName string) (*spe
} }
mergedEdits := edits.NewContainerEdits() mergedEdits := edits.NewContainerEdits()
for _, d := range deviceSpecs { for _, d := range deviceSpecs {
d := d d := d
edit := cdi.ContainerEdits{ edit := cdi.ContainerEdits{
@ -118,9 +117,6 @@ func mergeDeviceSpecs(deviceSpecs []specs.Device, mergedDeviceName string) (*spe
mergedEdits.Append(&edit) mergedEdits.Append(&edit)
} }
merged := specs.Device{ merged := edits.NewResource(mergedDeviceName, mergedEdits)
Name: mergedDeviceName,
ContainerEdits: *mergedEdits.ContainerEdits,
}
return &merged, nil return &merged, nil
} }