Bump github.com/container-orchestrated-devices/container-device-interface

Bumps [github.com/container-orchestrated-devices/container-device-interface](https://github.com/container-orchestrated-devices/container-device-interface) from 0.5.2 to 0.5.3.
- [Release notes](https://github.com/container-orchestrated-devices/container-device-interface/releases)
- [Commits](https://github.com/container-orchestrated-devices/container-device-interface/compare/v0.5.2...v0.5.3)

---
updated-dependencies:
- dependency-name: github.com/container-orchestrated-devices/container-device-interface
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Valentin Rothberg <vrothberg@redhat.com>
This commit is contained in:
dependabot[bot]
2022-11-09 13:27:16 +00:00
committed by Valentin Rothberg
parent 69ed903b20
commit fa2b4aeefd
8 changed files with 345 additions and 35 deletions

View File

@@ -17,14 +17,18 @@
package cdi
import (
"io/fs"
"os"
"path/filepath"
"sort"
"strings"
"sync"
stderr "errors"
"github.com/container-orchestrated-devices/container-device-interface/internal/multierror"
cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
"github.com/fsnotify/fsnotify"
"github.com/hashicorp/go-multierror"
oci "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
@@ -123,8 +127,8 @@ func (c *Cache) Refresh() error {
// collect and return cached errors, much like refresh() does it
var result error
for _, err := range c.errors {
result = multierror.Append(result, err...)
for _, errors := range c.errors {
result = multierror.Append(result, errors...)
}
return result
}
@@ -194,11 +198,7 @@ func (c *Cache) refresh() error {
c.devices = devices
c.errors = specErrors
if len(result) > 0 {
return multierror.Append(nil, result...)
}
return nil
return multierror.New(result...)
}
// RefreshIfRequired triggers a refresh if necessary.
@@ -255,11 +255,22 @@ func (c *Cache) InjectDevices(ociSpec *oci.Spec, devices ...string) ([]string, e
return nil, nil
}
// WriteSpec writes a Spec file with the given content. Priority is used
// as an index into the list of Spec directories to pick a directory for
// the file, adjusting for any under- or overflows. If name has a "json"
// or "yaml" extension it choses the encoding. Otherwise JSON encoding
// is used with a "json" extension.
// highestPrioritySpecDir returns the Spec directory with highest priority
// and its priority.
func (c *Cache) highestPrioritySpecDir() (string, int) {
if len(c.specDirs) == 0 {
return "", -1
}
prio := len(c.specDirs) - 1
dir := c.specDirs[prio]
return dir, prio
}
// 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.
func (c *Cache) WriteSpec(raw *cdi.Spec, name string) error {
var (
specDir string
@@ -269,23 +280,51 @@ func (c *Cache) WriteSpec(raw *cdi.Spec, name string) error {
err error
)
if len(c.specDirs) == 0 {
specDir, prio = c.highestPrioritySpecDir()
if specDir == "" {
return errors.New("no Spec directories to write to")
}
prio = len(c.specDirs) - 1
specDir = c.specDirs[prio]
path = filepath.Join(specDir, name)
if ext := filepath.Ext(path); ext != ".json" && ext != ".yaml" {
path += ".json"
path += defaultSpecExt
}
spec, err = NewSpec(raw, path, prio)
spec, err = newSpec(raw, path, prio)
if err != nil {
return err
}
return spec.Write(true)
return spec.write(true)
}
// 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 (c *Cache) RemoveSpec(name string) error {
var (
specDir string
path string
err error
)
specDir, _ = c.highestPrioritySpecDir()
if specDir == "" {
return errors.New("no Spec directories to remove from")
}
path = filepath.Join(specDir, name)
if ext := filepath.Ext(path); ext != ".json" && ext != ".yaml" {
path += defaultSpecExt
}
err = os.Remove(path)
if err != nil && stderr.Is(err, fs.ErrNotExist) {
err = nil
}
return err
}
// GetDevice returns the cached device for the given qualified name.

View File

@@ -124,10 +124,15 @@
//
// 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.
// 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
@@ -141,7 +146,111 @@
// 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'.
// 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"
// ...
// "github.com/container-orchestrated-devices/container-device-interface/specs-go"
// "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
// )
//
// func generateDeviceSpecs() error {
// registry := cdi.GetRegistry()
// 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 registry.WriteSpec(spec, specName)
// }
//
// Similary, 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"
// ...
// "github.com/container-orchestrated-devices/container-device-interface/specs-go"
// "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
// )
//
// func generateTransientSpec(ctr Container) error {
// registry := cdi.GetRegistry()
// 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 registry.WriteSpec(spec, specName)
// }
//
// func removeTransientSpec(ctr Container) error {
// registry := cdi.GetRegistry()
// transientID := getSomeSufficientlyUniqueIDForContainer(ctr)
// specName := cdi.GenerateNameForTransientSpec(vendor, class, transientID)
//
// return registry.RemoveSpec(specName)
// }
//
// CDI Spec Validation
//

View File

@@ -107,6 +107,7 @@ type RegistrySpecDB interface {
GetVendorSpecs(vendor string) []*Spec
GetSpecErrors(*Spec) []error
WriteSpec(raw *cdi.Spec, name string) error
RemoveSpec(name string) error
}
type registry struct {

View File

@@ -21,6 +21,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
oci "github.com/opencontainers/runtime-spec/specs-go"
@@ -30,6 +31,14 @@ import (
cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
)
const (
// CurrentVersion is the current vesion of the CDI Spec.
CurrentVersion = cdi.CurrentVersion
// defaultSpecExt is the file extension for the default encoding.
defaultSpecExt = ".yaml"
)
var (
// Valid CDI Spec versions.
validSpecVersions = map[string]struct{}{
@@ -80,7 +89,7 @@ func ReadSpec(path string, priority int) (*Spec, error) {
return nil, errors.Errorf("failed to parse CDI Spec %q, no Spec data", path)
}
spec, err := NewSpec(raw, path, priority)
spec, err := newSpec(raw, path, priority)
if err != nil {
return nil, err
}
@@ -88,11 +97,11 @@ func ReadSpec(path string, priority int) (*Spec, error) {
return spec, nil
}
// NewSpec creates a new Spec from the given CDI Spec data. The
// 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
// priority. If Spec data validation fails newSpec returns a nil
// Spec and an error.
func NewSpec(raw *cdi.Spec, path string, priority int) (*Spec, error) {
func newSpec(raw *cdi.Spec, path string, priority int) (*Spec, error) {
err := validateSpec(raw)
if err != nil {
return nil, err
@@ -104,6 +113,10 @@ func NewSpec(raw *cdi.Spec, path string, priority int) (*Spec, error) {
priority: priority,
}
if ext := filepath.Ext(spec.path); ext != ".yaml" && ext != ".json" {
spec.path += defaultSpecExt
}
spec.vendor, spec.class = ParseQualifier(spec.Kind)
if spec.devices, err = spec.validate(); err != nil {
@@ -114,8 +127,8 @@ func NewSpec(raw *cdi.Spec, path string, priority int) (*Spec, error) {
}
// Write the CDI Spec to the file associated with it during instantiation
// by NewSpec() or ReadSpec().
func (s *Spec) Write(overwrite bool) error {
// by newSpec() or ReadSpec().
func (s *Spec) write(overwrite bool) error {
var (
data []byte
dir string
@@ -249,7 +262,7 @@ func ParseSpec(data []byte) (*cdi.Spec, error) {
// 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 NewSpec()) or written (Spec.Write()).
// loaded (using ReadSpec() or written (using WriteSpec()).
func SetSpecValidator(fn func(*cdi.Spec) error) {
validatorLock.Lock()
defer validatorLock.Unlock()
@@ -270,3 +283,68 @@ func validateSpec(raw *cdi.Spec) error {
}
return nil
}
// 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 GenerateSpecName(vendor, class string) string {
return vendor + "-" + class
}
// 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 tranient 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 GenerateTransientSpecName(vendor, class, transientID string) string {
transientID = strings.ReplaceAll(transientID, "/", "_")
return GenerateSpecName(vendor, class) + "_" + transientID
}
// 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 GenerateNameForSpec(raw *cdi.Spec) (string, error) {
vendor, class := ParseQualifier(raw.Kind)
if vendor == "" {
return "", errors.Errorf("invalid vendor/class %q in Spec", raw.Kind)
}
return GenerateSpecName(vendor, class), nil
}
// 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 GenerateNameForTransientSpec(raw *cdi.Spec, transientID string) (string, error) {
vendor, class := ParseQualifier(raw.Kind)
if vendor == "" {
return "", errors.Errorf("invalid vendor/class %q in Spec", raw.Kind)
}
return GenerateTransientSpecName(vendor, class, transientID), nil
}