mirror of
https://github.com/NVIDIA/nvidia-container-toolkit
synced 2025-06-26 18:18:24 +00:00
Make CDI device requests consistent with other methods
Following the refactoring of device request extraction, we can now make CDI device requests consistent with other methods. This change moves to using image.VisibleDevices instead of separate calls to CDIDevicesFromMounts and VisibleDevicesFromEnvVar. Signed-off-by: Evan Lezar <elezar@nvidia.com>
This commit is contained in:
parent
f17d424248
commit
dc87dcf786
@ -56,8 +56,12 @@ type CUDA struct {
|
|||||||
// NewCUDAImageFromSpec creates a CUDA image from the input OCI runtime spec.
|
// NewCUDAImageFromSpec creates a CUDA image from the input OCI runtime spec.
|
||||||
// The process environment is read (if present) to construc the CUDA Image.
|
// The process environment is read (if present) to construc the CUDA Image.
|
||||||
func NewCUDAImageFromSpec(spec *specs.Spec, opts ...Option) (CUDA, error) {
|
func NewCUDAImageFromSpec(spec *specs.Spec, opts ...Option) (CUDA, error) {
|
||||||
|
if spec == nil {
|
||||||
|
return New(opts...)
|
||||||
|
}
|
||||||
|
|
||||||
var env []string
|
var env []string
|
||||||
if spec != nil && spec.Process != nil {
|
if spec.Process != nil {
|
||||||
env = spec.Process.Env
|
env = spec.Process.Env
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,19 +223,12 @@ func parseMajorMinorVersion(version string) (string, error) {
|
|||||||
// OnlyFullyQualifiedCDIDevices returns true if all devices requested in the image are requested as CDI devices/
|
// OnlyFullyQualifiedCDIDevices returns true if all devices requested in the image are requested as CDI devices/
|
||||||
func (i CUDA) OnlyFullyQualifiedCDIDevices() bool {
|
func (i CUDA) OnlyFullyQualifiedCDIDevices() bool {
|
||||||
var hasCDIdevice bool
|
var hasCDIdevice bool
|
||||||
for _, device := range i.VisibleDevicesFromEnvVar() {
|
for _, device := range i.VisibleDevices() {
|
||||||
if !parser.IsQualifiedName(device) {
|
if !parser.IsQualifiedName(device) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
hasCDIdevice = true
|
hasCDIdevice = true
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, device := range i.DevicesFromMounts() {
|
|
||||||
if !strings.HasPrefix(device, "cdi/") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
hasCDIdevice = true
|
|
||||||
}
|
|
||||||
return hasCDIdevice
|
return hasCDIdevice
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -309,20 +306,27 @@ func (i CUDA) VisibleDevicesFromEnvVar() []string {
|
|||||||
// visibleDevicesFromMounts returns the set of visible devices requested as mounts.
|
// visibleDevicesFromMounts returns the set of visible devices requested as mounts.
|
||||||
func (i CUDA) visibleDevicesFromMounts() []string {
|
func (i CUDA) visibleDevicesFromMounts() []string {
|
||||||
var devices []string
|
var devices []string
|
||||||
for _, device := range i.DevicesFromMounts() {
|
for _, device := range i.requestsFromMounts() {
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(device, volumeMountDevicePrefixCDI):
|
|
||||||
continue
|
|
||||||
case strings.HasPrefix(device, volumeMountDevicePrefixImex):
|
case strings.HasPrefix(device, volumeMountDevicePrefixImex):
|
||||||
continue
|
continue
|
||||||
|
case strings.HasPrefix(device, volumeMountDevicePrefixCDI):
|
||||||
|
name, err := cdiDeviceMountRequest(device).qualifiedName()
|
||||||
|
if err != nil {
|
||||||
|
i.logger.Warningf("Ignoring invalid mount request for CDI device %v: %v", device, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
devices = append(devices, name)
|
||||||
|
default:
|
||||||
devices = append(devices, device)
|
devices = append(devices, device)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
return devices
|
return devices
|
||||||
}
|
}
|
||||||
|
|
||||||
// DevicesFromMounts returns a list of device specified as mounts.
|
// requestsFromMounts returns a list of device specified as mounts.
|
||||||
func (i CUDA) DevicesFromMounts() []string {
|
func (i CUDA) requestsFromMounts() []string {
|
||||||
root := filepath.Clean(DeviceListAsVolumeMountsRoot)
|
root := filepath.Clean(DeviceListAsVolumeMountsRoot)
|
||||||
seen := make(map[string]bool)
|
seen := make(map[string]bool)
|
||||||
var devices []string
|
var devices []string
|
||||||
@ -354,23 +358,30 @@ func (i CUDA) DevicesFromMounts() []string {
|
|||||||
return devices
|
return devices
|
||||||
}
|
}
|
||||||
|
|
||||||
// CDIDevicesFromMounts returns a list of CDI devices specified as mounts on the image.
|
// a cdiDeviceMountRequest represents a CDI device requests as a mount.
|
||||||
func (i CUDA) CDIDevicesFromMounts() []string {
|
// Here the host path /dev/null is mounted to a particular path in the container.
|
||||||
var devices []string
|
// The container path has the form:
|
||||||
for _, mountDevice := range i.DevicesFromMounts() {
|
// /var/run/nvidia-container-devices/cdi/<vendor>/<class>/<device>
|
||||||
if !strings.HasPrefix(mountDevice, volumeMountDevicePrefixCDI) {
|
// or
|
||||||
continue
|
// /var/run/nvidia-container-devices/cdi/<vendor>/<class>=<device>
|
||||||
|
type cdiDeviceMountRequest string
|
||||||
|
|
||||||
|
// qualifiedName returns the fully-qualified name of the CDI device.
|
||||||
|
func (m cdiDeviceMountRequest) qualifiedName() (string, error) {
|
||||||
|
if !strings.HasPrefix(string(m), volumeMountDevicePrefixCDI) {
|
||||||
|
return "", fmt.Errorf("invalid mount CDI device request: %s", m)
|
||||||
}
|
}
|
||||||
parts := strings.SplitN(strings.TrimPrefix(mountDevice, volumeMountDevicePrefixCDI), "/", 3)
|
|
||||||
|
requestedDevice := strings.TrimPrefix(string(m), volumeMountDevicePrefixCDI)
|
||||||
|
if parser.IsQualifiedName(requestedDevice) {
|
||||||
|
return requestedDevice, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(requestedDevice, "/", 3)
|
||||||
if len(parts) != 3 {
|
if len(parts) != 3 {
|
||||||
continue
|
return "", fmt.Errorf("invalid mount CDI device request: %s", m)
|
||||||
}
|
}
|
||||||
vendor := parts[0]
|
return fmt.Sprintf("%s/%s=%s", parts[0], parts[1], parts[2]), nil
|
||||||
class := parts[1]
|
|
||||||
device := parts[2]
|
|
||||||
devices = append(devices, fmt.Sprintf("%s/%s=%s", vendor, class, device))
|
|
||||||
}
|
|
||||||
return devices
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImexChannelsFromEnvVar returns the list of IMEX channels requested for the image.
|
// ImexChannelsFromEnvVar returns the list of IMEX channels requested for the image.
|
||||||
@ -385,7 +396,7 @@ func (i CUDA) ImexChannelsFromEnvVar() []string {
|
|||||||
// ImexChannelsFromMounts returns the list of IMEX channels requested for the image.
|
// ImexChannelsFromMounts returns the list of IMEX channels requested for the image.
|
||||||
func (i CUDA) ImexChannelsFromMounts() []string {
|
func (i CUDA) ImexChannelsFromMounts() []string {
|
||||||
var channels []string
|
var channels []string
|
||||||
for _, mountDevice := range i.DevicesFromMounts() {
|
for _, mountDevice := range i.requestsFromMounts() {
|
||||||
if !strings.HasPrefix(mountDevice, volumeMountDevicePrefixImex) {
|
if !strings.HasPrefix(mountDevice, volumeMountDevicePrefixImex) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -487,9 +487,9 @@ func TestGetVisibleDevicesFromMounts(t *testing.T) {
|
|||||||
expectedDevices: []string{"GPU0-MIG0/0/1", "GPU1-MIG0/0/1"},
|
expectedDevices: []string{"GPU0-MIG0/0/1", "GPU1-MIG0/0/1"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "cdi devices are ignored",
|
description: "cdi devices are included",
|
||||||
mounts: makeTestMounts("GPU0", "cdi/nvidia.com/gpu=all", "GPU1"),
|
mounts: makeTestMounts("GPU0", "nvidia.com/gpu=all", "GPU1"),
|
||||||
expectedDevices: []string{"GPU0", "GPU1"},
|
expectedDevices: []string{"GPU0", "nvidia.com/gpu=all", "GPU1"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "imex devices are ignored",
|
description: "imex devices are ignored",
|
||||||
|
@ -184,7 +184,7 @@ func TestResolveAutoMode(t *testing.T) {
|
|||||||
expectedMode: "legacy",
|
expectedMode: "legacy",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "cdi mount and non-CDI envvar resolves to legacy",
|
description: "cdi mount and non-CDI envvar resolves to cdi",
|
||||||
mode: "auto",
|
mode: "auto",
|
||||||
envmap: map[string]string{
|
envmap: map[string]string{
|
||||||
"NVIDIA_VISIBLE_DEVICES": "0",
|
"NVIDIA_VISIBLE_DEVICES": "0",
|
||||||
@ -197,6 +197,22 @@ func TestResolveAutoMode(t *testing.T) {
|
|||||||
"tegra": false,
|
"tegra": false,
|
||||||
"nvgpu": false,
|
"nvgpu": false,
|
||||||
},
|
},
|
||||||
|
expectedMode: "cdi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "non-cdi mount and CDI envvar resolves to legacy",
|
||||||
|
mode: "auto",
|
||||||
|
envmap: map[string]string{
|
||||||
|
"NVIDIA_VISIBLE_DEVICES": "nvidia.com/gpu=0",
|
||||||
|
},
|
||||||
|
mounts: []string{
|
||||||
|
"/var/run/nvidia-container-devices/0",
|
||||||
|
},
|
||||||
|
info: map[string]bool{
|
||||||
|
"nvml": true,
|
||||||
|
"tegra": false,
|
||||||
|
"nvgpu": false,
|
||||||
|
},
|
||||||
expectedMode: "legacy",
|
expectedMode: "legacy",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -232,6 +248,8 @@ func TestResolveAutoMode(t *testing.T) {
|
|||||||
image, _ := image.New(
|
image, _ := image.New(
|
||||||
image.WithEnvMap(tc.envmap),
|
image.WithEnvMap(tc.envmap),
|
||||||
image.WithMounts(mounts),
|
image.WithMounts(mounts),
|
||||||
|
image.WithAcceptDeviceListAsVolumeMounts(true),
|
||||||
|
image.WithAcceptEnvvarUnprivileged(true),
|
||||||
)
|
)
|
||||||
mode := resolveMode(logger, tc.mode, image, properties)
|
mode := resolveMode(logger, tc.mode, image, properties)
|
||||||
require.EqualValues(t, tc.expectedMode, mode)
|
require.EqualValues(t, tc.expectedMode, mode)
|
||||||
|
@ -34,10 +34,12 @@ import (
|
|||||||
// CDI specifications available on the system. The NVIDIA_VISIBLE_DEVICES environment variable is
|
// CDI specifications available on the system. The NVIDIA_VISIBLE_DEVICES environment variable is
|
||||||
// used to select the devices to include.
|
// used to select the devices to include.
|
||||||
func NewCDIModifier(logger logger.Interface, cfg *config.Config, image image.CUDA) (oci.SpecModifier, error) {
|
func NewCDIModifier(logger logger.Interface, cfg *config.Config, image image.CUDA) (oci.SpecModifier, error) {
|
||||||
devices, err := getDevicesFromImage(logger, cfg, image)
|
deviceRequestor := newCDIDeviceRequestor(
|
||||||
if err != nil {
|
logger,
|
||||||
return nil, fmt.Errorf("failed to get required devices from OCI specification: %v", err)
|
image,
|
||||||
}
|
cfg.NVIDIAContainerRuntimeConfig.Modes.CDI.DefaultKind,
|
||||||
|
)
|
||||||
|
devices := deviceRequestor.DeviceRequests()
|
||||||
if len(devices) == 0 {
|
if len(devices) == 0 {
|
||||||
logger.Debugf("No devices requested; no modification required.")
|
logger.Debugf("No devices requested; no modification required.")
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -64,46 +66,47 @@ func NewCDIModifier(logger logger.Interface, cfg *config.Config, image image.CUD
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDevicesFromImage(logger logger.Interface, cfg *config.Config, container image.CUDA) ([]string, error) {
|
type deviceRequestor interface {
|
||||||
annotationDevices, err := getAnnotationDevices(container)
|
DeviceRequests() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type cdiDeviceRequestor struct {
|
||||||
|
image image.CUDA
|
||||||
|
logger logger.Interface
|
||||||
|
defaultKind string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCDIDeviceRequestor(logger logger.Interface, image image.CUDA, defaultKind string) deviceRequestor {
|
||||||
|
c := &cdiDeviceRequestor{
|
||||||
|
logger: logger,
|
||||||
|
image: image,
|
||||||
|
defaultKind: defaultKind,
|
||||||
|
}
|
||||||
|
return withUniqueDevices(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cdiDeviceRequestor) DeviceRequests() []string {
|
||||||
|
if c == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
annotationDevices, err := getAnnotationDevices(c.image)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse container annotations: %v", err)
|
c.logger.Warningf("failed to get device requests from container annotations: %v; ignoring.", err)
|
||||||
|
annotationDevices = nil
|
||||||
}
|
}
|
||||||
if len(annotationDevices) > 0 {
|
if len(annotationDevices) > 0 {
|
||||||
return annotationDevices, nil
|
return annotationDevices
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.AcceptDeviceListAsVolumeMounts {
|
|
||||||
mountDevices := container.CDIDevicesFromMounts()
|
|
||||||
if len(mountDevices) > 0 {
|
|
||||||
return mountDevices, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var devices []string
|
var devices []string
|
||||||
seen := make(map[string]bool)
|
for _, name := range c.image.VisibleDevices() {
|
||||||
for _, name := range container.VisibleDevicesFromEnvVar() {
|
|
||||||
if !parser.IsQualifiedName(name) {
|
if !parser.IsQualifiedName(name) {
|
||||||
name = fmt.Sprintf("%s=%s", cfg.NVIDIAContainerRuntimeConfig.Modes.CDI.DefaultKind, name)
|
name = fmt.Sprintf("%s=%s", c.defaultKind, name)
|
||||||
}
|
|
||||||
if seen[name] {
|
|
||||||
logger.Debugf("Ignoring duplicate device %q", name)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
devices = append(devices, name)
|
devices = append(devices, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(devices) == 0 {
|
return devices
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.AcceptEnvvarUnprivileged || container.IsPrivileged() {
|
|
||||||
return devices, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Warningf("Ignoring devices specified in NVIDIA_VISIBLE_DEVICES: %v", devices)
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getAnnotationDevices returns a list of devices specified in the annotations.
|
// getAnnotationDevices returns a list of devices specified in the annotations.
|
||||||
@ -111,16 +114,11 @@ func getDevicesFromImage(logger logger.Interface, cfg *config.Config, container
|
|||||||
// fully-qualified CDI devices names. If any device name is not fully-quality an error is returned.
|
// fully-qualified CDI devices names. If any device name is not fully-quality an error is returned.
|
||||||
// The list of returned devices is deduplicated.
|
// The list of returned devices is deduplicated.
|
||||||
func getAnnotationDevices(image image.CUDA) ([]string, error) {
|
func getAnnotationDevices(image image.CUDA) ([]string, error) {
|
||||||
seen := make(map[string]bool)
|
|
||||||
var annotationDevices []string
|
var annotationDevices []string
|
||||||
for _, device := range image.CDIDeviceRequestsFromAnnotations() {
|
for _, device := range image.CDIDeviceRequestsFromAnnotations() {
|
||||||
if !parser.IsQualifiedName(device) {
|
if !parser.IsQualifiedName(device) {
|
||||||
return nil, fmt.Errorf("invalid device name %q in annotations", device)
|
return nil, fmt.Errorf("invalid device name %q in annotations", device)
|
||||||
}
|
}
|
||||||
if seen[device] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[device] = true
|
|
||||||
annotationDevices = append(annotationDevices, device)
|
annotationDevices = append(annotationDevices, device)
|
||||||
}
|
}
|
||||||
return annotationDevices, nil
|
return annotationDevices, nil
|
||||||
@ -147,7 +145,7 @@ func newAutomaticCDISpecModifier(logger logger.Interface, cfg *config.Config, de
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to generate CDI spec: %w", err)
|
return nil, fmt.Errorf("failed to generate CDI spec: %w", err)
|
||||||
}
|
}
|
||||||
cdiModifier, err := cdi.New(
|
cdiDeviceRequestor, err := cdi.New(
|
||||||
cdi.WithLogger(logger),
|
cdi.WithLogger(logger),
|
||||||
cdi.WithSpec(spec.Raw()),
|
cdi.WithSpec(spec.Raw()),
|
||||||
)
|
)
|
||||||
@ -155,7 +153,7 @@ func newAutomaticCDISpecModifier(logger logger.Interface, cfg *config.Config, de
|
|||||||
return nil, fmt.Errorf("failed to construct CDI modifier: %w", err)
|
return nil, fmt.Errorf("failed to construct CDI modifier: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cdiModifier, nil
|
return cdiDeviceRequestor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateAutomaticCDISpec(logger logger.Interface, cfg *config.Config, devices []string) (spec.Interface, error) {
|
func generateAutomaticCDISpec(logger logger.Interface, cfg *config.Config, devices []string) (spec.Interface, error) {
|
||||||
@ -193,3 +191,42 @@ func generateAutomaticCDISpec(logger logger.Interface, cfg *config.Config, devic
|
|||||||
spec.WithClass("gpu"),
|
spec.WithClass("gpu"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deviceRequestorFromImage(image image.CUDA) deviceRequestor {
|
||||||
|
return &fromImage{image}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fromImage struct {
|
||||||
|
image.CUDA
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fromImage) DeviceRequests() []string {
|
||||||
|
if f == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return f.CUDA.VisibleDevices()
|
||||||
|
}
|
||||||
|
|
||||||
|
type deduplicatedDeviceRequestor struct {
|
||||||
|
deviceRequestor
|
||||||
|
}
|
||||||
|
|
||||||
|
func withUniqueDevices(deviceRequestor deviceRequestor) deviceRequestor {
|
||||||
|
return &deduplicatedDeviceRequestor{deviceRequestor: deviceRequestor}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deduplicatedDeviceRequestor) DeviceRequests() []string {
|
||||||
|
if d == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
var devices []string
|
||||||
|
for _, device := range d.deviceRequestor.DeviceRequests() {
|
||||||
|
if seen[device] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[device] = true
|
||||||
|
devices = append(devices, device)
|
||||||
|
}
|
||||||
|
return devices
|
||||||
|
}
|
||||||
|
@ -20,6 +20,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
testlog "github.com/sirupsen/logrus/hooks/test"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/NVIDIA/nvidia-container-toolkit/internal/config/image"
|
"github.com/NVIDIA/nvidia-container-toolkit/internal/config/image"
|
||||||
@ -67,7 +69,7 @@ func TestGetAnnotationDevices(t *testing.T) {
|
|||||||
"prefix/foo": "example.com/device=bar",
|
"prefix/foo": "example.com/device=bar",
|
||||||
"another-prefix/bar": "example.com/device=bar",
|
"another-prefix/bar": "example.com/device=bar",
|
||||||
},
|
},
|
||||||
expectedDevices: []string{"example.com/device=bar"},
|
expectedDevices: []string{"example.com/device=bar", "example.com/device=bar"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "invalid devices",
|
description: "invalid devices",
|
||||||
@ -98,3 +100,67 @@ func TestGetAnnotationDevices(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeviceRequests(t *testing.T) {
|
||||||
|
logger, _ := testlog.NewNullLogger()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
description string
|
||||||
|
input cdiDeviceRequestor
|
||||||
|
spec *specs.Spec
|
||||||
|
expectedDevices []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "empty spec yields no devices",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "cdi devices from mounts",
|
||||||
|
input: cdiDeviceRequestor{
|
||||||
|
defaultKind: "nvidia.com/gpu",
|
||||||
|
},
|
||||||
|
spec: &specs.Spec{
|
||||||
|
Mounts: []specs.Mount{
|
||||||
|
{
|
||||||
|
Destination: "/var/run/nvidia-container-devices/cdi/nvidia.com/gpu/0",
|
||||||
|
Source: "/dev/null",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Destination: "/var/run/nvidia-container-devices/cdi/nvidia.com/gpu/1",
|
||||||
|
Source: "/dev/null",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedDevices: []string{"nvidia.com/gpu=0", "nvidia.com/gpu=1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "cdi devices from envvar",
|
||||||
|
input: cdiDeviceRequestor{
|
||||||
|
defaultKind: "nvidia.com/gpu",
|
||||||
|
},
|
||||||
|
spec: &specs.Spec{
|
||||||
|
Process: &specs.Process{
|
||||||
|
Env: []string{"NVIDIA_VISIBLE_DEVICES=0,example.com/class=device"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedDevices: []string{"nvidia.com/gpu=0", "example.com/class=device"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc.input.logger = logger
|
||||||
|
|
||||||
|
image, err := image.NewCUDAImageFromSpec(
|
||||||
|
tc.spec,
|
||||||
|
image.WithAcceptDeviceListAsVolumeMounts(true),
|
||||||
|
image.WithAcceptEnvvarUnprivileged(true),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
tc.input.image = image
|
||||||
|
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
devices := tc.input.DeviceRequests()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.EqualValues(t, tc.expectedDevices, devices)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user