cdi

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 22, 2022 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

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://github.com/container-orchestrated-devices/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.

CDI Registry

The primary interface to interact with CDI devices is the Registry. It is essentially a cache of all Specs and devices discovered in standard CDI directories on the host. The registry has two main functionality, injecting devices into an OCI Spec and refreshing the cache of CDI Specs and devices.

Device Injection

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

import (
    "fmt"
    "strings"

    "github.com/pkg/errors"
    log "github.com/sirupsen/logrus"

    "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
    oci "github.com/opencontainers/runtime-spec/specs-go"
)

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

    unresolved, err := cdi.GetRegistry().InjectDevices(spec, devices)
    if err != nil {
        return errors.Wrap(err, "CDI device injection failed")
    }

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

Cache Refresh

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"

    "github.com/pkg/errors"
    log "github.com/sirupsen/logrus"

    "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
    oci "github.com/opencontainers/runtime-spec/specs-go"
)

func injectCDIDevices(spec *oci.Spec, devices []string) error {
    registry := cdi.GetRegistry()

    if err := registry.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 registry 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 := registry.InjectDevices(spec, devices)
    if err != nil {
        return errors.Wrap(err, "CDI device injection failed")
    }

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

Generated Spec Files, Multiple Directories, Device Precedence

There are systems where the set of available or usable CDI devices changes dynamically and this needs to be reflected in the CDI Specs. This is done by dynamically regenerating CDI Spec files which are affected by these changes.

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 chosing 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'.

Index

Constants

View Source
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"
)
View Source
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"
)
View Source
const (
	// AnnotationPrefix is the prefix for CDI container annotation keys.
	AnnotationPrefix = "cdi.k8s.io/"
)

Variables

View Source
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 Registry or
	// other Cache instances.
	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 IsQualifiedName

func IsQualifiedName(device string) bool

IsQualifiedName tests if a device name is qualified.

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 ParseDevice

func ParseDevice(device string) (string, string, string)

ParseDevice tries to split a device name into vendor, class, and name. If this fails, for instance in the case of unqualified device names, ParseDevice returns an empty vendor and class together with name set to the verbatim input.

func ParseQualifiedName

func ParseQualifiedName(device string) (string, string, string, error)

ParseQualifiedName splits a qualified name into device vendor, class, and name. If the device fails to parse as a qualified name, or if any of the split components fail to pass syntax validation, vendor and class are returned as empty, together with the verbatim input as the name and an error describing the reason for failure.

func ParseQualifier

func ParseQualifier(kind string) (string, string)

ParseQualifier splits a device qualifier into vendor and class. The syntax for a device qualifier is

"<vendor>/<class>"

If parsing fails, an empty vendor and the class set to the verbatim input is returned.

func QualifiedName

func QualifiedName(vendor, class, name string) string

QualifiedName returns the qualified name for a device. The syntax for a qualified device names is

"<vendor>/<class>=<name>".

A valid vendor name may contain the following runes:

'A'-'Z', 'a'-'z', '0'-'9', '.', '-', '_'.

A valid class name may contain the following runes:

'A'-'Z', 'a'-'z', '0'-'9', '-', '_'.

A valid device name may containe the following runes:

'A'-'Z', 'a'-'z', '0'-'9', '-', '_', '.', ':'

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 ValidateClassName

func ValidateClassName(class string) error

ValidateClassName checks the validity of class name. A class name may contain the following ASCII characters:

  • upper- and lowercase letters ('A'-'Z', 'a'-'z')
  • digits ('0'-'9')
  • underscore and dash ('_', '-')

func ValidateDeviceName

func ValidateDeviceName(name string) error

ValidateDeviceName checks the validity of a device name. A device name may contain the following ASCII characters:

  • upper- and lowercase letters ('A'-'Z', 'a'-'z')
  • digits ('0'-'9')
  • underscore, dash, dot, colon ('_', '-', '.', ':')

func ValidateEnv

func ValidateEnv(env []string) error

ValidateEnv validates the given environment variables.

func ValidateVendorName

func ValidateVendorName(vendor string) error

ValidateVendorName checks the validity of a vendor name. A vendor name may contain the following ASCII characters:

  • upper- and lowercase letters ('A'-'Z', 'a'-'z')
  • digits ('0'-'9')
  • underscore, dash, and dot ('_', '-', and '.')

Types

type Cache

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

Cache stores CDI Specs loaded from Spec directories.

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.

func (*Cache) Configure

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

Configure applies options to the cache. Updates 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.

func (*Cache) GetErrors

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

GetErrors returns all errors encountered during the last cache refresh.

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.

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.

func (*Cache) ListClasses

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

ListClasses lists all device classes known to the cache.

func (*Cache) ListDevices

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

ListDevices lists all cached devices by qualified name.

func (*Cache) ListVendors

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

ListVendors lists all vendors known to the cache.

func (*Cache) Refresh

func (c *Cache) Refresh() error

Refresh rescans the CDI Spec directories and refreshes the Cache.

type ContainerEdits

type ContainerEdits struct {
	*specs.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 {
	*specs.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 {
	*specs.Hook
}

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

func (*Hook) Validate

func (h *Hook) Validate() error

Validate a hook.

type Mount

type Mount struct {
	*specs.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) error

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

func WithSpecDirs

func WithSpecDirs(dirs ...string) Option

WithSpecDirs returns an option to override the CDI Spec directories.

type Registry

type Registry interface {
	RegistryResolver
	RegistryRefresher
	DeviceDB() RegistryDeviceDB
	SpecDB() RegistrySpecDB
}

Registry keeps a cache of all CDI Specs installed or generated on the host. Registry is the primary interface clients should use to interact with CDI.

The most commonly used Registry functions are for refreshing the registry and injecting CDI devices into an OCI Spec.

func GetRegistry

func GetRegistry(options ...Option) Registry

GetRegistry returns the CDI registry. If any options are given, those are applied to the registry.

type RegistryDeviceDB

type RegistryDeviceDB interface {
	GetDevice(device string) *Device
	ListDevices() []string
}

RegistryDeviceDB is the registry interface for querying devices.

GetDevice returns the CDI device for the given qualified name. If the device is not GetDevice returns nil.

ListDevices returns a slice with the names of qualified device known. The returned slice is sorted.

type RegistryRefresher

type RegistryRefresher interface {
	Refresh() error
	GetErrors() map[string][]error
	GetSpecDirectories() []string
}

RegistryRefresher is the registry interface for refreshing the cache of CDI Specs and devices.

Refresh rescans all CDI Spec directories and updates the state of the cache to reflect any changes. It returns any errors encountered during the refresh.

GetErrors returns all errors encountered for any of the scanned Spec files during the last cache refresh.

GetSpecDirectories returns the set up CDI Spec directories currently in use. The directories are returned in the scan order of Refresh().

type RegistryResolver

type RegistryResolver interface {
	InjectDevices(spec *oci.Spec, device ...string) (unresolved []string, err error)
}

RegistryResolver is the registry interface for injecting CDI devices into an OCI Spec.

InjectDevices takes an OCI Spec and injects into it a set of CDI devices given by qualified name. It returns the names of any unresolved devices and an error if injection fails.

type RegistrySpecDB

type RegistrySpecDB interface {
	ListVendors() []string
	ListClasses() []string
	GetVendorSpecs(vendor string) []*Spec
	GetSpecErrors(*Spec) []error
}

RegistrySpecDB is the registry interface for querying CDI Specs.

ListVendors returns a slice with all vendors known. The returned slice is sorted.

ListClasses returns a slice with all classes known. The returned slice is sorted.

GetVendorSpecs returns a slice of all Specs for the vendor.

GetSpecErrors returns any errors for the Spec encountered during the last cache refresh.

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 NewSpec

func NewSpec(raw *cdi.Spec, path string, priority int) (*Spec, error)

NewSpec creates a new Spec from the given CDI Spec data. The Spec is marked as loaded from the given path with the given priority. If Spec data validation fails NewSpec returns a nil Spec and an error.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL