mirror of
https://github.com/NVIDIA/nvidia-container-toolkit
synced 2025-06-26 18:18:24 +00:00
Add logic to extract annotation device requests to image type
This change updates the image.CUDA type to also extract CDI device requests. These are only relevant IF CDI prefixes are specifically set. Signed-off-by: Evan Lezar <elezar@nvidia.com>
This commit is contained in:
parent
6849ebd621
commit
426186c992
@ -50,7 +50,6 @@ func New(opt ...Option) (CUDA, error) {
|
|||||||
if b.logger == nil {
|
if b.logger == nil {
|
||||||
b.logger = logger.New()
|
b.logger = logger.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.env == nil {
|
if b.env == nil {
|
||||||
b.env = make(map[string]string)
|
b.env = make(map[string]string)
|
||||||
}
|
}
|
||||||
@ -81,6 +80,20 @@ func WithAcceptEnvvarUnprivileged(acceptEnvvarUnprivileged bool) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithAnnotations(annotations map[string]string) Option {
|
||||||
|
return func(b *builder) error {
|
||||||
|
b.annotations = annotations
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithAnnotationsPrefixes(annotationsPrefixes []string) Option {
|
||||||
|
return func(b *builder) error {
|
||||||
|
b.annotationsPrefixes = annotationsPrefixes
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithDisableRequire sets the disable require option.
|
// WithDisableRequire sets the disable require option.
|
||||||
func WithDisableRequire(disableRequire bool) Option {
|
func WithDisableRequire(disableRequire bool) Option {
|
||||||
return func(b *builder) error {
|
return func(b *builder) error {
|
||||||
|
@ -42,10 +42,12 @@ const (
|
|||||||
type CUDA struct {
|
type CUDA struct {
|
||||||
logger logger.Interface
|
logger logger.Interface
|
||||||
|
|
||||||
|
annotations map[string]string
|
||||||
env map[string]string
|
env map[string]string
|
||||||
isPrivileged bool
|
isPrivileged bool
|
||||||
mounts []specs.Mount
|
mounts []specs.Mount
|
||||||
|
|
||||||
|
annotationsPrefixes []string
|
||||||
acceptDeviceListAsVolumeMounts bool
|
acceptDeviceListAsVolumeMounts bool
|
||||||
acceptEnvvarUnprivileged bool
|
acceptEnvvarUnprivileged bool
|
||||||
preferredVisibleDeviceEnvVars []string
|
preferredVisibleDeviceEnvVars []string
|
||||||
@ -60,6 +62,7 @@ func NewCUDAImageFromSpec(spec *specs.Spec, opts ...Option) (CUDA, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
specOpts := []Option{
|
specOpts := []Option{
|
||||||
|
WithAnnotations(spec.Annotations),
|
||||||
WithEnv(env),
|
WithEnv(env),
|
||||||
WithMounts(spec.Mounts),
|
WithMounts(spec.Mounts),
|
||||||
WithPrivileged(IsPrivileged((*OCISpec)(spec))),
|
WithPrivileged(IsPrivileged((*OCISpec)(spec))),
|
||||||
@ -264,6 +267,32 @@ func (i CUDA) VisibleDevices() []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CDIDeviceRequestsFromAnnotations returns a list of devices specified in the annotations.
|
||||||
|
// Keys starting with the specified prefixes are considered and expected to
|
||||||
|
// contain a comma-separated list of fully-qualified CDI devices names.
|
||||||
|
// The format of the requested devices is not checked and the list is not
|
||||||
|
// deduplicated.
|
||||||
|
func (i CUDA) CDIDeviceRequestsFromAnnotations() []string {
|
||||||
|
if len(i.annotationsPrefixes) == 0 || len(i.annotations) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var deviceKeys []string
|
||||||
|
for key := range i.annotations {
|
||||||
|
for _, prefix := range i.annotationsPrefixes {
|
||||||
|
if strings.HasPrefix(key, prefix) {
|
||||||
|
deviceKeys = append(deviceKeys, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var devices []string
|
||||||
|
for _, key := range deviceKeys {
|
||||||
|
devices = append(devices, strings.Split(i.annotations[key], ",")...)
|
||||||
|
}
|
||||||
|
return devices
|
||||||
|
}
|
||||||
|
|
||||||
// VisibleDevicesFromEnvVar returns the set of visible devices requested through environment variables.
|
// VisibleDevicesFromEnvVar returns the set of visible devices requested through environment variables.
|
||||||
// If any of the preferredVisibleDeviceEnvVars are present in the image, they
|
// If any of the preferredVisibleDeviceEnvVars are present in the image, they
|
||||||
// are used to determine the visible devices. If this is not the case, the
|
// are used to determine the visible devices. If this is not the case, the
|
||||||
|
@ -649,6 +649,73 @@ func TestImexChannelsFromEnvVar(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCDIDeviceRequestsFromAnnotations(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
description string
|
||||||
|
prefixes []string
|
||||||
|
annotations map[string]string
|
||||||
|
expectedDevices []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "no annotations",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "no matching annotations",
|
||||||
|
prefixes: []string{"not-prefix/"},
|
||||||
|
annotations: map[string]string{
|
||||||
|
"prefix/foo": "example.com/device=bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "single matching annotation",
|
||||||
|
prefixes: []string{"prefix/"},
|
||||||
|
annotations: map[string]string{
|
||||||
|
"prefix/foo": "example.com/device=bar",
|
||||||
|
},
|
||||||
|
expectedDevices: []string{"example.com/device=bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "multiple matching annotations",
|
||||||
|
prefixes: []string{"prefix/", "another-prefix/"},
|
||||||
|
annotations: map[string]string{
|
||||||
|
"prefix/foo": "example.com/device=bar",
|
||||||
|
"another-prefix/bar": "example.com/device=baz",
|
||||||
|
},
|
||||||
|
expectedDevices: []string{"example.com/device=bar", "example.com/device=baz"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "multiple matching annotations with duplicate devices",
|
||||||
|
prefixes: []string{"prefix/", "another-prefix/"},
|
||||||
|
annotations: map[string]string{
|
||||||
|
"prefix/foo": "example.com/device=bar",
|
||||||
|
"another-prefix/bar": "example.com/device=bar",
|
||||||
|
},
|
||||||
|
expectedDevices: []string{"example.com/device=bar", "example.com/device=bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "invalid devices are returned as is",
|
||||||
|
prefixes: []string{"prefix/"},
|
||||||
|
annotations: map[string]string{
|
||||||
|
"prefix/foo": "example.com/device",
|
||||||
|
},
|
||||||
|
expectedDevices: []string{"example.com/device"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
image, err := New(
|
||||||
|
WithAnnotationsPrefixes(tc.prefixes),
|
||||||
|
WithAnnotations(tc.annotations),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
devices := image.CDIDeviceRequestsFromAnnotations()
|
||||||
|
require.ElementsMatch(t, tc.expectedDevices, devices)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func makeTestMounts(paths ...string) []specs.Mount {
|
func makeTestMounts(paths ...string) []specs.Mount {
|
||||||
var mounts []specs.Mount
|
var mounts []specs.Mount
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
|
@ -18,7 +18,6 @@ package modifier
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"tags.cncf.io/container-device-interface/pkg/parser"
|
"tags.cncf.io/container-device-interface/pkg/parser"
|
||||||
|
|
||||||
@ -71,7 +70,18 @@ func getDevicesFromSpec(logger logger.Interface, ociSpec oci.Spec, cfg *config.C
|
|||||||
return nil, fmt.Errorf("failed to load OCI spec: %v", err)
|
return nil, fmt.Errorf("failed to load OCI spec: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
annotationDevices, err := getAnnotationDevices(cfg.NVIDIAContainerRuntimeConfig.Modes.CDI.AnnotationPrefixes, rawSpec.Annotations)
|
container, err := image.NewCUDAImageFromSpec(
|
||||||
|
rawSpec,
|
||||||
|
image.WithLogger(logger),
|
||||||
|
image.WithAcceptDeviceListAsVolumeMounts(cfg.AcceptDeviceListAsVolumeMounts),
|
||||||
|
image.WithAcceptEnvvarUnprivileged(cfg.AcceptEnvvarUnprivileged),
|
||||||
|
image.WithAnnotationsPrefixes(cfg.NVIDIAContainerRuntimeConfig.Modes.CDI.AnnotationPrefixes),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
annotationDevices, err := getAnnotationDevices(container)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse container annotations: %v", err)
|
return nil, fmt.Errorf("failed to parse container annotations: %v", err)
|
||||||
}
|
}
|
||||||
@ -79,13 +89,6 @@ func getDevicesFromSpec(logger logger.Interface, ociSpec oci.Spec, cfg *config.C
|
|||||||
return annotationDevices, nil
|
return annotationDevices, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
container, err := image.NewCUDAImageFromSpec(
|
|
||||||
rawSpec,
|
|
||||||
image.WithLogger(logger),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if cfg.AcceptDeviceListAsVolumeMounts {
|
if cfg.AcceptDeviceListAsVolumeMounts {
|
||||||
mountDevices := container.CDIDevicesFromMounts()
|
mountDevices := container.CDIDevicesFromMounts()
|
||||||
if len(mountDevices) > 0 {
|
if len(mountDevices) > 0 {
|
||||||
@ -123,31 +126,19 @@ func getDevicesFromSpec(logger logger.Interface, ociSpec oci.Spec, cfg *config.C
|
|||||||
// Keys starting with the specified prefixes are considered and expected to contain a comma-separated list of
|
// Keys starting with the specified prefixes are considered and expected to contain a comma-separated list of
|
||||||
// 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(prefixes []string, annotations map[string]string) ([]string, error) {
|
func getAnnotationDevices(image image.CUDA) ([]string, error) {
|
||||||
devicesByKey := make(map[string][]string)
|
|
||||||
for key, value := range annotations {
|
|
||||||
for _, prefix := range prefixes {
|
|
||||||
if strings.HasPrefix(key, prefix) {
|
|
||||||
devicesByKey[key] = strings.Split(value, ",")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
seen := make(map[string]bool)
|
seen := make(map[string]bool)
|
||||||
var annotationDevices []string
|
var annotationDevices []string
|
||||||
for key, devices := range devicesByKey {
|
for _, device := range image.CDIDeviceRequestsFromAnnotations() {
|
||||||
for _, device := range devices {
|
|
||||||
if !parser.IsQualifiedName(device) {
|
if !parser.IsQualifiedName(device) {
|
||||||
return nil, fmt.Errorf("invalid device name %q in annotation %q", device, key)
|
return nil, fmt.Errorf("invalid device name %q in annotations", device)
|
||||||
}
|
}
|
||||||
if seen[device] {
|
if seen[device] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
annotationDevices = append(annotationDevices, device)
|
|
||||||
seen[device] = true
|
seen[device] = true
|
||||||
|
annotationDevices = append(annotationDevices, device)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return annotationDevices, nil
|
return annotationDevices, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/NVIDIA/nvidia-container-toolkit/internal/config/image"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetAnnotationDevices(t *testing.T) {
|
func TestGetAnnotationDevices(t *testing.T) {
|
||||||
@ -79,7 +81,13 @@ func TestGetAnnotationDevices(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) {
|
||||||
devices, err := getAnnotationDevices(tc.prefixes, tc.annotations)
|
image, err := image.New(
|
||||||
|
image.WithAnnotations(tc.annotations),
|
||||||
|
image.WithAnnotationsPrefixes(tc.prefixes),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
devices, err := getAnnotationDevices(image)
|
||||||
if tc.expectedError != nil {
|
if tc.expectedError != nil {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
return
|
return
|
||||||
|
Loading…
Reference in New Issue
Block a user