build(deps): 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.4.0 to 0.5.0.
- [Release notes](https://github.com/container-orchestrated-devices/container-device-interface/releases)
- [Commits](https://github.com/container-orchestrated-devices/container-device-interface/compare/v0.4.0...v0.5.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2022-08-10 12:08:21 +00:00
committed by GitHub
parent 89ab5c9fab
commit e3f029cb83
13 changed files with 485 additions and 53 deletions

View File

@ -22,6 +22,8 @@ import (
"strings"
"sync"
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"
@ -33,30 +35,46 @@ type Option func(*Cache) error
// Cache stores CDI Specs loaded from Spec directories.
type Cache struct {
sync.Mutex
specDirs []string
specs map[string][]*Spec
devices map[string]*Device
errors map[string][]error
specDirs []string
specs map[string][]*Spec
devices map[string]*Device
errors map[string][]error
dirErrors map[string]error
autoRefresh bool
watch *watch
}
// 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 WithAutoRefresh(autoRefresh bool) Option {
return func(c *Cache) error {
c.autoRefresh = autoRefresh
return nil
}
}
// 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 NewCache(options ...Option) (*Cache, error) {
c := &Cache{}
if err := c.Configure(options...); err != nil {
return nil, err
}
if len(c.specDirs) == 0 {
c.Configure(WithSpecDirs(DefaultSpecDirs...))
c := &Cache{
autoRefresh: true,
watch: &watch{},
}
return c, c.Refresh()
WithSpecDirs(DefaultSpecDirs...)(c)
c.Lock()
defer c.Unlock()
return c, c.configure(options...)
}
// Configure applies options to the cache. Updates the cache if options have
// changed.
// Configure applies options to the Cache. Updates and refreshes the
// Cache if options have changed.
func (c *Cache) Configure(options ...Option) error {
if len(options) == 0 {
return nil
@ -65,17 +83,54 @@ func (c *Cache) Configure(options ...Option) error {
c.Lock()
defer c.Unlock()
return c.configure(options...)
}
// Configure the Cache. Start/stop CDI Spec directory watch, refresh
// the Cache if necessary.
func (c *Cache) configure(options ...Option) error {
var err error
for _, o := range options {
if err := o(c); err != nil {
if err = o(c); err != nil {
return errors.Wrapf(err, "failed to apply cache options")
}
}
c.dirErrors = make(map[string]error)
c.watch.stop()
if c.autoRefresh {
c.watch.setup(c.specDirs, c.dirErrors)
c.watch.start(&c.Mutex, c.refresh, c.dirErrors)
}
c.refresh()
return nil
}
// 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 (c *Cache) Refresh() error {
c.Lock()
defer c.Unlock()
// force a refresh in manual mode
if refreshed, err := c.refreshIfRequired(!c.autoRefresh); refreshed {
return err
}
// collect and return cached errors, much like refresh() does it
var result error
for _, err := range c.errors {
result = multierror.Append(result, err...)
}
return result
}
// Refresh the Cache by rescanning CDI Spec directories and files.
func (c *Cache) refresh() error {
var (
specs = map[string][]*Spec{}
devices = map[string]*Device{}
@ -135,9 +190,6 @@ func (c *Cache) Refresh() error {
delete(devices, conflict)
}
c.Lock()
defer c.Unlock()
c.specs = specs
c.devices = devices
c.errors = specErrors
@ -149,6 +201,17 @@ func (c *Cache) Refresh() error {
return nil
}
// RefreshIfRequired triggers a refresh if necessary.
func (c *Cache) refreshIfRequired(force bool) (bool, error) {
// We need to refresh if
// - it's forced by an explicitly call to Refresh() in manual mode
// - a missing Spec dir appears (added to watch) in auto-refresh mode
if force || (c.autoRefresh && c.watch.update(c.dirErrors)) {
return true, c.refresh()
}
return false, nil
}
// 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.
@ -162,6 +225,8 @@ func (c *Cache) InjectDevices(ociSpec *oci.Spec, devices ...string) ([]string, e
c.Lock()
defer c.Unlock()
c.refreshIfRequired(false)
edits := &ContainerEdits{}
specs := map[*Spec]struct{}{}
@ -190,11 +255,46 @@ 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.
func (c *Cache) WriteSpec(raw *cdi.Spec, name string) error {
var (
specDir string
path string
prio int
spec *Spec
err error
)
if len(c.specDirs) == 0 {
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"
}
spec, err = NewSpec(raw, path, prio)
if err != nil {
return err
}
return spec.Write(true)
}
// GetDevice returns the cached device for the given qualified name.
func (c *Cache) GetDevice(device string) *Device {
c.Lock()
defer c.Unlock()
c.refreshIfRequired(false)
return c.devices[device]
}
@ -205,6 +305,8 @@ func (c *Cache) ListDevices() []string {
c.Lock()
defer c.Unlock()
c.refreshIfRequired(false)
for name := range c.devices {
devices = append(devices, name)
}
@ -220,6 +322,8 @@ func (c *Cache) ListVendors() []string {
c.Lock()
defer c.Unlock()
c.refreshIfRequired(false)
for vendor := range c.specs {
vendors = append(vendors, vendor)
}
@ -238,6 +342,8 @@ func (c *Cache) ListClasses() []string {
c.Lock()
defer c.Unlock()
c.refreshIfRequired(false)
for _, specs := range c.specs {
for _, spec := range specs {
cmap[spec.GetClass()] = struct{}{}
@ -256,6 +362,8 @@ func (c *Cache) GetVendorSpecs(vendor string) []*Spec {
c.Lock()
defer c.Unlock()
c.refreshIfRequired(false)
return c.specs[vendor]
}
@ -268,12 +376,158 @@ func (c *Cache) GetSpecErrors(spec *Spec) []error {
// GetErrors returns all errors encountered during the last
// cache refresh.
func (c *Cache) GetErrors() map[string][]error {
return c.errors
c.Lock()
defer c.Unlock()
errors := map[string][]error{}
for path, errs := range c.errors {
errors[path] = errs
}
for path, err := range c.dirErrors {
errors[path] = []error{err}
}
return errors
}
// GetSpecDirectories returns the CDI Spec directories currently in use.
func (c *Cache) GetSpecDirectories() []string {
c.Lock()
defer c.Unlock()
dirs := make([]string, len(c.specDirs))
copy(dirs, c.specDirs)
return dirs
}
// GetSpecDirErrors returns any errors related to configured Spec directories.
func (c *Cache) GetSpecDirErrors() map[string]error {
if c.dirErrors == nil {
return nil
}
c.Lock()
defer c.Unlock()
errors := make(map[string]error)
for dir, err := range c.dirErrors {
errors[dir] = err
}
return errors
}
// Our fsnotify helper wrapper.
type watch struct {
watcher *fsnotify.Watcher
tracked map[string]bool
}
// Setup monitoring for the given Spec directories.
func (w *watch) setup(dirs []string, dirErrors map[string]error) {
var (
dir string
err error
)
w.tracked = make(map[string]bool)
for _, dir = range dirs {
w.tracked[dir] = false
}
w.watcher, err = fsnotify.NewWatcher()
if err != nil {
for _, dir := range dirs {
dirErrors[dir] = errors.Wrap(err, "failed to create watcher")
}
return
}
w.update(dirErrors)
}
// Start watching Spec directories for relevant changes.
func (w *watch) start(m *sync.Mutex, refresh func() error, dirErrors map[string]error) {
go w.watch(m, refresh, dirErrors)
}
// Stop watching directories.
func (w *watch) stop() {
if w.watcher == nil {
return
}
w.watcher.Close()
w.tracked = nil
}
// Watch Spec directory changes, triggering a refresh if necessary.
func (w *watch) watch(m *sync.Mutex, refresh func() error, dirErrors map[string]error) {
watch := w.watcher
if watch == nil {
return
}
for {
select {
case event, ok := <-watch.Events:
if !ok {
return
}
if (event.Op & (fsnotify.Rename | fsnotify.Remove | fsnotify.Write)) == 0 {
continue
}
if event.Op == fsnotify.Write {
if ext := filepath.Ext(event.Name); ext != ".json" && ext != ".yaml" {
continue
}
}
m.Lock()
if event.Op == fsnotify.Remove && w.tracked[event.Name] {
w.update(dirErrors, event.Name)
} else {
w.update(dirErrors)
}
refresh()
m.Unlock()
case _, ok := <-watch.Errors:
if !ok {
return
}
}
}
}
// Update watch with pending/missing or removed directories.
func (w *watch) update(dirErrors map[string]error, removed ...string) bool {
var (
dir string
ok bool
err error
update bool
)
for dir, ok = range w.tracked {
if ok {
continue
}
err = w.watcher.Add(dir)
if err == nil {
w.tracked[dir] = true
delete(dirErrors, dir)
update = true
} else {
w.tracked[dir] = false
dirErrors[dir] = errors.Wrap(err, "failed to monitor for changes")
}
}
for _, dir = range removed {
w.tracked[dir] = false
dirErrors[dir] = errors.New("directory removed")
update = true
}
return update
}