container-device-interfacetags.cncf.io/container-device-interface/pkg/cdi Index | Files

package cdi

import "tags.cncf.io/container-device-interface/pkg/cdi"

Package cdi has the primary purpose of providing an API for interacting with CDI and consuming CDI devices.

For more information about Container Device Interface, please refer to https://tags.cncf.io/container-device-interface

Container Device Interface

Container Device Interface, or CDI for short, provides comprehensive third party device support for container runtimes. CDI uses vendor provided specification files, CDI Specs for short, to describe how a container's runtime environment should be modified when one or more of the vendor-specific devices is injected into the container. Beyond describing the low level platform-specific details of how to gain basic access to a device, CDI Specs allow more fine-grained device initialization, and the automatic injection of any necessary vendor- or device-specific software that might be required for a container to use a device or take full advantage of it.

In the CDI device model containers request access to a device using fully qualified device names, qualified names for short, consisting of a vendor identifier, a device class and a device name or identifier. These pieces of information together uniquely identify a device among all device vendors, classes and device instances.

This package implements an API for easy consumption of CDI. The API implements discovery, loading and caching of CDI Specs and injection of CDI devices into containers. This is the most common functionality the vast majority of CDI consumers need. The API should be usable both by OCI runtime clients and runtime implementations.

Default CDI Cache

There is a default CDI cache instance which is always implicitly available and instantiated the first time it is referenced directly or indirectly. The most frequently used cache functions are available as identically named package level functions which operate on the default cache instance.

Device Injection

Using the Cache one can inject CDI devices into a container with code similar to the following snippet:

import (
    "fmt"
    "strings"

    log "github.com/sirupsen/logrus"

    "tags.cncf.io/container-device-interface/pkg/cdi"
    "github.com/opencontainers/runtime-spec/specs-go"
)

func injectCDIDevices(spec *specs.Spec, devices []string) error {
    log.Debug("pristine OCI Spec: %s", dumpSpec(spec))

    cache := cdi.GetDefaultCache()
    unresolved, err := cache.InjectDevices(spec, devices)
    if err != nil {
        return fmt.Errorf("CDI device injection failed: %w", err)
    }

    log.Debug("CDI-updated OCI Spec: %s", dumpSpec(spec))
    return nil
}

Cache Refresh

By default the CDI Spec cache monitors the configured Spec directories and automatically refreshes itself when necessary. This behavior can be disabled using the WithAutoRefresh(false) option.

Failure to set up monitoring for a Spec directory causes the directory to get ignored and an error to be recorded among the Spec directory errors. These errors can be queried using the GetSpecDirErrors() function. If the error condition is transient, for instance a missing directory which later gets created, the corresponding error will be removed once the condition is over.

With auto-refresh enabled injecting any CDI devices can be done without an explicit call to Refresh(), using a code snippet similar to the following:

In a runtime implementation one typically wants to make sure the CDI Spec cache is up to date before performing device injection. A code snippet similar to the following accmplishes that:

import (
    "fmt"
    "strings"

    log "github.com/sirupsen/logrus"

    "tags.cncf.io/container-device-interface/pkg/cdi"
    "github.com/opencontainers/runtime-spec/specs-go"
)

func injectCDIDevices(spec *specs.Spec, devices []string) error {
    cache := cdi.GetDefaultCache()

    if err := cache.Refresh(); err != nil {
        // Note:
        //   It is up to the implementation to decide whether
        //   to abort injection on errors. A failed Refresh()
        //   does not necessarily render the cache unusable.
        //   For instance, a parse error in a Spec file for
        //   vendor A does not have any effect on devices of
        //   vendor B...
        log.Warnf("pre-injection Refresh() failed: %v", err)
    }

    log.Debug("pristine OCI Spec: %s", dumpSpec(spec))

    unresolved, err := cache.InjectDevices(spec, devices)
    if err != nil {
        return fmt.Errorf("CDI device injection failed: %w", err)
    }

    log.Debug("CDI-updated OCI Spec: %s", dumpSpec(spec))
    return nil
}

Generated Spec Files, Multiple Directories, Device Precedence

It is often necessary to generate Spec files dynamically. On some systems the available or usable set of CDI devices might change dynamically which then needs to be reflected in CDI Specs. For some device classes it makes sense to enumerate the available devices at every boot and generate Spec file entries for each device found. Some CDI devices might need special client- or request-specific configuration which can only be fulfilled by dynamically generated client-specific entries in transient Spec files.

CDI can collect Spec files from multiple directories. Spec files are automatically assigned priorities according to which directory they were loaded from. The later a directory occurs in the list of CDI directories to scan, the higher priority Spec files loaded from that directory are assigned to. When two or more Spec files define the same device, conflict is resolved by choosing the definition from the Spec file with the highest priority.

The default CDI directory configuration is chosen to encourage separating dynamically generated CDI Spec files from static ones. The default directories are '/etc/cdi' and '/var/run/cdi'. By putting dynamically generated Spec files under '/var/run/cdi', those take precedence over static ones in '/etc/cdi'. With this scheme, static Spec files, typically installed by distro-specific packages, go into '/etc/cdi' while all the dynamically generated Spec files, transient or other, go into '/var/run/cdi'.

Spec File Generation

CDI offers two functions for writing and removing dynamically generated Specs from CDI Spec directories. These functions, WriteSpec() and RemoveSpec() implicitly follow the principle of separating dynamic Specs from the rest and therefore always write to and remove Specs from the last configured directory.

Corresponding functions are also provided for generating names for Spec files. These functions follow a simple naming convention to ensure that multiple entities generating Spec files simultaneously on the same host do not end up using conflicting Spec file names. GenerateSpecName(), GenerateNameForSpec(), GenerateTransientSpecName(), and GenerateTransientNameForSpec() all generate names which can be passed as such to WriteSpec() and subsequently to RemoveSpec().

Generating a Spec file for a vendor/device class can be done with a code snippet similar to the following:

import (

"fmt"
...
"tags.cncf.io/container-device-interface/specs-go"
"tags.cncf.io/container-device-interface/pkg/cdi"

)

func generateDeviceSpecs() error {
    cache := specs.GetDefaultCache()
    spec := &specs.Spec{
        Version: specs.CurrentVersion,
        Kind:    vendor+"/"+class,
    }

    for _, dev := range enumerateDevices() {
        spec.Devices = append(spec.Devices, specs.Device{
            Name: dev.Name,
            ContainerEdits: getContainerEditsForDevice(dev),
        })
    }

    specName, err := cdi.GenerateNameForSpec(spec)
    if err != nil {
        return fmt.Errorf("failed to generate Spec name: %w", err)
    }

    return cache.WriteSpec(spec, specName)
}

Similarly, generating and later cleaning up transient Spec files can be done with code fragments similar to the following. These transient Spec files are temporary Spec files with container-specific parametrization. They are typically created before the associated container is created and removed once that container is removed.

import (

"fmt"
...
"tags.cncf.io/container-device-interface/specs-go"
"tags.cncf.io/container-device-interface/pkg/cdi"

)

func generateTransientSpec(ctr Container) error {
    cache := specs.GetDefaultCache()
    devices := getContainerDevs(ctr, vendor, class)
    spec := &specs.Spec{
        Version: specs.CurrentVersion,
        Kind:    vendor+"/"+class,
    }

    for _, dev := range devices {
        spec.Devices = append(spec.Devices, specs.Device{
            // the generated name needs to be unique within the
            // vendor/class domain on the host/node.
            Name: generateUniqueDevName(dev, ctr),
            ContainerEdits: getEditsForContainer(dev),
        })
    }

    // transientID is expected to guarantee that the Spec file name
    // generated using <vendor, class, transientID> is unique within
    // the host/node. If more than one device is allocated with the
    // same vendor/class domain, either all generated Spec entries
    // should go to a single Spec file (like in this sample snippet),
    // or transientID should be unique for each generated Spec file.
    transientID := getSomeSufficientlyUniqueIDForContainer(ctr)
    specName, err := cdi.GenerateNameForTransientSpec(vendor, class, transientID)
    if err != nil {
        return fmt.Errorf("failed to generate Spec name: %w", err)
    }

    return cache.WriteSpec(spec, specName)
}

func removeTransientSpec(ctr Container) error {
    cache := specs.GetDefaultCache()
    transientID := getSomeSufficientlyUniqueIDForContainer(ctr)
    specName := cdi.GenerateNameForTransientSpec(vendor, class, transientID)

    return cache.RemoveSpec(specName)
}

CDI Spec Validation

This package performs both syntactic and semantic validation of CDI Spec file data when a Spec file is loaded via the cache or using the ReadSpec API function. As part of the semantic verification, the Spec file is verified against the CDI Spec JSON validation schema.

If a valid externally provided JSON validation schema is found in the filesystem at /etc/cdi/schema/schema.json it is loaded and used as the default validation schema. If such a file is not found or fails to load, an embedded no-op schema is used.

The used validation schema can also be changed programmatically using the SetSchema API convenience function. This function also accepts the special "builtin" (BuiltinSchemaName) and "none" (NoneSchemaName) schema names which switch the used schema to the in-repo validation schema embedded into the binary or the now default no-op schema correspondingly. Other names are interpreted as the path to the actual validation schema to load and use.

Index

Constants

const (
	// PrestartHook is the name of the OCI "prestart" hook.
	PrestartHook = "prestart"
	// CreateRuntimeHook is the name of the OCI "createRuntime" hook.
	CreateRuntimeHook = "createRuntime"
	// CreateContainerHook is the name of the OCI "createContainer" hook.
	CreateContainerHook = "createContainer"
	// StartContainerHook is the name of the OCI "startContainer" hook.
	StartContainerHook = "startContainer"
	// PoststartHook is the name of the OCI "poststart" hook.
	PoststartHook = "poststart"
	// PoststopHook is the name of the OCI "poststop" hook.
	PoststopHook = "poststop"
)
const (
	// DefaultStaticDir is the default directory for static CDI Specs.
	DefaultStaticDir = "/etc/cdi"
	// DefaultDynamicDir is the default directory for generated CDI Specs
	DefaultDynamicDir = "/var/run/cdi"
)
const (
	// AnnotationPrefix is the prefix for CDI container annotation keys.
	AnnotationPrefix = "cdi.k8s.io/"
)

Variables

var (
	// DefaultSpecDirs is the default Spec directory configuration.
	// While altering this variable changes the package defaults,
	// the preferred way of overriding the default directories is
	// to use a WithSpecDirs options. Otherwise the change is only
	// effective if it takes place before creating the cache instance.
	DefaultSpecDirs = []string{DefaultStaticDir, DefaultDynamicDir}
	// ErrStopScan can be returned from a ScanSpecFunc to stop the scan.
	ErrStopScan = errors.New("stop Spec scan")
)

Functions

func AnnotationKey

func AnnotationKey(pluginName, deviceID string) (string, error)

AnnotationKey returns a unique annotation key for an device allocation by a K8s device plugin. pluginName should be in the format of "vendor.device-type". deviceID is the ID of the device the plugin is allocating. It is used to make sure that the generated key is unique even if multiple allocations by a single plugin needs to be annotated.

func AnnotationValue

func AnnotationValue(devices []string) (string, error)

AnnotationValue returns an annotation value for the given devices.

func Configure

func Configure(options ...Option) error

Configure applies options to the default CDI cache. Updates and refreshes the default cache if options are not empty.

func GenerateNameForSpec

func GenerateNameForSpec(raw *cdi.Spec) (string, error)

GenerateNameForSpec generates a name for the given Spec using GenerateSpecName with the vendor and class taken from the Spec. On success it returns the generated name and a nil error. If the Spec does not contain a valid vendor or class, it returns an empty name and a non-nil error.

func GenerateNameForTransientSpec

func GenerateNameForTransientSpec(raw *cdi.Spec, transientID string) (string, error)

GenerateNameForTransientSpec generates a name for the given transient Spec using GenerateTransientSpecName with the vendor and class taken from the Spec. On success it returns the generated name and a nil error. If the Spec does not contain a valid vendor or class, it returns an an empty name and a non-nil error.

func GenerateSpecName

func GenerateSpecName(vendor, class string) string

GenerateSpecName generates a vendor+class scoped Spec file name. The name can be passed to WriteSpec() to write a Spec file to the file system.

vendor and class should match the vendor and class of the CDI Spec. The file name is generated without a ".json" or ".yaml" extension. The caller can append the desired extension to choose a particular encoding. Otherwise WriteSpec() will use its default encoding.

This function always returns the same name for the same vendor/class combination. Therefore it cannot be used as such to generate multiple Spec file names for a single vendor and class.

func GenerateTransientSpecName

func GenerateTransientSpecName(vendor, class, transientID string) string

GenerateTransientSpecName generates a vendor+class scoped transient Spec file name. The name can be passed to WriteSpec() to write a Spec file to the file system.

Transient Specs are those whose lifecycle is tied to that of some external entity, for instance a container. vendor and class should match the vendor and class of the CDI Spec. transientID should be unique among all CDI users on the same host that might generate transient Spec files using the same vendor/class combination. If the external entity to which the lifecycle of the transient Spec is tied to has a unique ID of its own, then this is usually a good choice for transientID.

The file name is generated without a ".json" or ".yaml" extension. The caller can append the desired extension to choose a particular encoding. Otherwise WriteSpec() will use its default encoding.

func GetErrors

func GetErrors() map[string][]error

GetErrors returns all errors encountered during the last refresh of the default CDI cache instance.

func InjectDevices

func InjectDevices(ociSpec *oci.Spec, devices ...string) ([]string, error)

InjectDevices injects the given qualified devices to the given OCI Spec. using the default CDI cache instance to resolve devices.

func MinimumRequiredVersion

func MinimumRequiredVersion(spec *cdi.Spec) (string, error)

MinimumRequiredVersion determines the minimum spec version for the input spec. Deprecated: use cdi.MinimumRequiredVersion instead

func ParseAnnotations

func ParseAnnotations(annotations map[string]string) ([]string, []string, error)

ParseAnnotations parses annotations for CDI device injection requests. The keys and devices from all such requests are collected into slices which are returned as the result. All devices are expected to be fully qualified CDI device names. If any device fails this check empty slices are returned along with a non-nil error. The annotations are expected to be formatted by, or in a compatible fashion to UpdateAnnotations().

func ParseSpec

func ParseSpec(data []byte) (*cdi.Spec, error)

ParseSpec parses CDI Spec data into a raw CDI Spec.

func Refresh

func Refresh() error

Refresh explicitly refreshes the default CDI cache instance.

func SetSpecValidator

func SetSpecValidator(v validator)

SetSpecValidator sets a CDI Spec validator function. This function is used for extra CDI Spec content validation whenever a Spec file loaded (using ReadSpec() or written (using WriteSpec()).

func UpdateAnnotations

func UpdateAnnotations(annotations map[string]string, plugin string, deviceID string, devices []string) (map[string]string, error)

UpdateAnnotations updates annotations with a plugin-specific CDI device injection request for the given devices. Upon any error a non-nil error is returned and annotations are left intact. By convention plugin should be in the format of "vendor.device-type".

func ValidateEnv

func ValidateEnv(env []string) error

ValidateEnv validates the given environment variables.

func ValidateIntelRdt

func ValidateIntelRdt(i *cdi.IntelRdt) error

ValidateIntelRdt validates the IntelRdt configuration.

Deprecated: ValidateIntelRdt is deprecated use IntelRdt.Validate() instead.

Types

type Cache

type Cache struct {
	sync.Mutex
	// contains filtered or unexported fields
}

Cache stores CDI Specs loaded from Spec directories.

func GetDefaultCache

func GetDefaultCache() *Cache

GetDefaultCache returns the default CDI cache instance.

func NewCache

func NewCache(options ...Option) (*Cache, error)

NewCache creates a new CDI Cache. The cache is populated from a set of CDI Spec directories. These can be specified using a WithSpecDirs option. The default set of directories is exposed in DefaultSpecDirs.

Note:

The error returned by this function is always nil and it is only
returned to maintain API compatibility with consumers.

func (*Cache) Configure

func (c *Cache) Configure(options ...Option) error

Configure applies options to the Cache. Updates and refreshes the Cache if options have changed.

func (*Cache) GetDevice

func (c *Cache) GetDevice(device string) *Device

GetDevice returns the cached device for the given qualified name. Might trigger a cache refresh, in which case any errors encountered can be obtained using GetErrors().

func (*Cache) GetErrors

func (c *Cache) GetErrors() map[string][]error

GetErrors returns all errors encountered during the last cache refresh.

func (*Cache) GetSpecDirErrors

func (c *Cache) GetSpecDirErrors() map[string]error

GetSpecDirErrors returns any errors related to configured Spec directories.

func (*Cache) GetSpecDirectories

func (c *Cache) GetSpecDirectories() []string

GetSpecDirectories returns the CDI Spec directories currently in use.

func (*Cache) GetSpecErrors

func (c *Cache) GetSpecErrors(spec *Spec) []error

GetSpecErrors returns all errors encountered for the spec during the last cache refresh.

func (*Cache) GetVendorSpecs

func (c *Cache) GetVendorSpecs(vendor string) []*Spec

GetVendorSpecs returns all specs for the given vendor. Might trigger a cache refresh, in which case any errors encountered can be obtained using GetErrors().

func (*Cache) InjectDevices

func (c *Cache) InjectDevices(ociSpec *oci.Spec, devices ...string) ([]string, error)

InjectDevices injects the given qualified devices to an OCI Spec. It returns any unresolvable devices and an error if injection fails for any of the devices. Might trigger a cache refresh, in which case any errors encountered can be obtained using GetErrors().

func (*Cache) ListClasses

func (c *Cache) ListClasses() []string

ListClasses lists all device classes known to the cache. Might trigger a cache refresh, in which case any errors encountered can be obtained using GetErrors().

func (*Cache) ListDevices

func (c *Cache) ListDevices() []string

ListDevices lists all cached devices by qualified name. Might trigger a cache refresh, in which case any errors encountered can be obtained using GetErrors().

func (*Cache) ListVendors

func (c *Cache) ListVendors() []string

ListVendors lists all vendors known to the cache. Might trigger a cache refresh, in which case any errors encountered can be obtained using GetErrors().

func (*Cache) Refresh

func (c *Cache) Refresh() error

Refresh rescans the CDI Spec directories and refreshes the Cache. In manual refresh mode the cache is always refreshed. In auto- refresh mode the cache is only refreshed if it is out of date.

func (*Cache) RemoveSpec

func (c *Cache) RemoveSpec(name string) error

RemoveSpec removes a Spec with the given name from the highest priority Spec directory. This function can be used to remove a Spec previously written by WriteSpec(). If the file exists and its removal fails RemoveSpec returns an error.

func (*Cache) WriteSpec

func (c *Cache) WriteSpec(raw *cdi.Spec, name string) error

WriteSpec writes a Spec file with the given content into the highest priority Spec directory. If name has a "json" or "yaml" extension it choses the encoding. Otherwise the default YAML encoding is used.

type ContainerEdits

type ContainerEdits struct {
	*cdi.ContainerEdits
}

ContainerEdits represent updates to be applied to an OCI Spec. These updates can be specific to a CDI device, or they can be specific to a CDI Spec. In the former case these edits should be applied to all OCI Specs where the corresponding CDI device is injected. In the latter case, these edits should be applied to all OCI Specs where at least one devices from the CDI Spec is injected.

func (*ContainerEdits) Append

Append other edits into this one. If called with a nil receiver, allocates and returns newly allocated edits.

func (*ContainerEdits) Apply

func (e *ContainerEdits) Apply(spec *oci.Spec) error

Apply edits to the given OCI Spec. Updates the OCI Spec in place. Returns an error if the update fails.

func (*ContainerEdits) Validate

func (e *ContainerEdits) Validate() error

Validate container edits.

type Device

type Device struct {
	*cdi.Device
	// contains filtered or unexported fields
}

Device represents a CDI device of a Spec.

func (*Device) ApplyEdits

func (d *Device) ApplyEdits(ociSpec *oci.Spec) error

ApplyEdits applies the device-speific container edits to an OCI Spec.

func (*Device) GetQualifiedName

func (d *Device) GetQualifiedName() string

GetQualifiedName returns the qualified name for this device.

func (*Device) GetSpec

func (d *Device) GetSpec() *Spec

GetSpec returns the Spec this device is defined in.

type DeviceNode

type DeviceNode struct {
	*cdi.DeviceNode
}

DeviceNode is a CDI Spec DeviceNode wrapper, used for validating DeviceNodes.

func (*DeviceNode) Validate

func (d *DeviceNode) Validate() error

Validate a CDI Spec DeviceNode.

type Hook

type Hook struct {
	*cdi.Hook
}

Hook is a CDI Spec Hook wrapper, used for validating hooks.

func (*Hook) Validate

func (h *Hook) Validate() error

Validate a hook.

type IntelRdt

type IntelRdt struct {
	*cdi.IntelRdt
}

IntelRdt is a CDI IntelRdt wrapper. This is used for validation and conversion to OCI specifications.

func (*IntelRdt) Validate

func (i *IntelRdt) Validate() error

Validate validates the IntelRdt configuration.

type Mount

type Mount struct {
	*cdi.Mount
}

Mount is a CDI Mount wrapper, used for validating mounts.

func (*Mount) Validate

func (m *Mount) Validate() error

Validate a mount.

type Option

type Option func(*Cache)

Option is an option to change some aspect of default CDI behavior.

func WithAutoRefresh

func WithAutoRefresh(autoRefresh bool) Option

WithAutoRefresh returns an option to control automatic Cache refresh. By default, auto-refresh is enabled, the list of Spec directories are monitored and the Cache is automatically refreshed whenever a change is detected. This option can be used to disable this behavior when a manually refreshed mode is preferable.

func WithSpecDirs

func WithSpecDirs(dirs ...string) Option

WithSpecDirs returns an option to override the CDI Spec directories.

type Spec

type Spec struct {
	*cdi.Spec
	// contains filtered or unexported fields
}

Spec represents a single CDI Spec. It is usually loaded from a file and stored in a cache. The Spec has an associated priority. This priority is inherited from the associated priority of the CDI Spec directory that contains the CDI Spec file and is used to resolve conflicts if multiple CDI Spec files contain entries for the same fully qualified device.

func ReadSpec

func ReadSpec(path string, priority int) (*Spec, error)

ReadSpec reads the given CDI Spec file. The resulting Spec is assigned the given priority. If reading or parsing the Spec data fails ReadSpec returns a nil Spec and an error.

func (*Spec) ApplyEdits

func (s *Spec) ApplyEdits(ociSpec *oci.Spec) error

ApplyEdits applies the Spec's global-scope container edits to an OCI Spec.

func (*Spec) GetClass

func (s *Spec) GetClass() string

GetClass returns the device class of this Spec.

func (*Spec) GetDevice

func (s *Spec) GetDevice(name string) *Device

GetDevice returns the device for the given unqualified name.

func (*Spec) GetPath

func (s *Spec) GetPath() string

GetPath returns the filesystem path of this Spec.

func (*Spec) GetPriority

func (s *Spec) GetPriority() int

GetPriority returns the priority of this Spec.

func (*Spec) GetVendor

func (s *Spec) GetVendor() string

GetVendor returns the vendor of this Spec.

Source Files

annotations.go cache.go cache_test_unix.go container-edits.go container-edits_unix.go default-cache.go device.go doc.go oci.go spec-dirs.go spec.go spec_linux.go

Version
v1.0.1 (latest)
Published
Mar 22, 2025
Platform
linux/amd64
Imports
20 packages
Last checked
2 days ago

Tools for package owners.