Update tags.cncf.io/container-device-interface to v0.7.1

This also bumps the maximum supported CDI specification to v0.7.0.

Signed-off-by: Evan Lezar <elezar@nvidia.com>
This commit is contained in:
Evan Lezar 2024-04-10 15:42:52 +02:00
parent 29f24a828b
commit 745e2356ab
15 changed files with 272 additions and 140 deletions

View file

@ -110,7 +110,7 @@ require (
google.golang.org/protobuf v1.33.0
gotest.tools/v3 v3.5.1
resenje.org/singleflight v0.4.1
tags.cncf.io/container-device-interface v0.6.2
tags.cncf.io/container-device-interface v0.7.1
)
require (
@ -228,5 +228,5 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.90.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
tags.cncf.io/container-device-interface/specs-go v0.6.0 // indirect
tags.cncf.io/container-device-interface/specs-go v0.7.0 // indirect
)

View file

@ -1106,7 +1106,7 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
tags.cncf.io/container-device-interface v0.6.2 h1:dThE6dtp/93ZDGhqaED2Pu374SOeUkBfuvkLuiTdwzg=
tags.cncf.io/container-device-interface v0.6.2/go.mod h1:Shusyhjs1A5Na/kqPVLL0KqnHQHuunol9LFeUNkuGVE=
tags.cncf.io/container-device-interface/specs-go v0.6.0 h1:V+tJJN6dqu8Vym6p+Ru+K5mJ49WL6Aoc5SJFSY0RLsQ=
tags.cncf.io/container-device-interface/specs-go v0.6.0/go.mod h1:hMAwAbMZyBLdmYqWgYcKH0F/yctNpV3P35f+/088A80=
tags.cncf.io/container-device-interface v0.7.1 h1:MATNCbAD1su9U6zwQe5BrQ2vGGp1GBayD70bYaxYCNE=
tags.cncf.io/container-device-interface v0.7.1/go.mod h1:h1JVuOqTQVORp8DziaWKUCDNzAmN+zeCbqbqD30D0ZQ=
tags.cncf.io/container-device-interface/specs-go v0.7.0 h1:w/maMGVeLP6TIQJVYT5pbqTi8SCw/iHZ+n4ignuGHqg=
tags.cncf.io/container-device-interface/specs-go v0.7.0/go.mod h1:hMAwAbMZyBLdmYqWgYcKH0F/yctNpV3P35f+/088A80=

7
vendor/modules.txt vendored
View file

@ -1600,13 +1600,12 @@ resenje.org/singleflight
# sigs.k8s.io/yaml v1.3.0
## explicit; go 1.12
sigs.k8s.io/yaml
# tags.cncf.io/container-device-interface v0.6.2
## explicit; go 1.19
tags.cncf.io/container-device-interface/internal/multierror
# tags.cncf.io/container-device-interface v0.7.1
## explicit; go 1.20
tags.cncf.io/container-device-interface/internal/validation
tags.cncf.io/container-device-interface/internal/validation/k8s
tags.cncf.io/container-device-interface/pkg/cdi
tags.cncf.io/container-device-interface/pkg/parser
# tags.cncf.io/container-device-interface/specs-go v0.6.0
# tags.cncf.io/container-device-interface/specs-go v0.7.0
## explicit; go 1.19
tags.cncf.io/container-device-interface/specs-go

View file

@ -1,82 +0,0 @@
/*
Copyright © 2022 The CDI Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package multierror
import (
"strings"
)
// New combines several errors into a single error. Parameters that are nil are
// ignored. If no errors are passed in or all parameters are nil, then the
// result is also nil.
func New(errors ...error) error {
// Filter out nil entries.
numErrors := 0
for _, err := range errors {
if err != nil {
errors[numErrors] = err
numErrors++
}
}
if numErrors == 0 {
return nil
}
return multiError(errors[0:numErrors])
}
// multiError is the underlying implementation used by New.
//
// Beware that a null multiError is not the same as a nil error.
type multiError []error
// multiError returns all individual error strings concatenated with "\n"
func (e multiError) Error() string {
var builder strings.Builder
for i, err := range e {
if i > 0 {
_, _ = builder.WriteString("\n")
}
_, _ = builder.WriteString(err.Error())
}
return builder.String()
}
// Append returns a new multi error all errors concatenated. Errors that are
// multi errors get flattened, nil is ignored.
func Append(err error, errors ...error) error {
var result multiError
if m, ok := err.(multiError); ok {
result = m
} else if err != nil {
result = append(result, err)
}
for _, e := range errors {
if e == nil {
continue
}
if m, ok := e.(multiError); ok {
result = append(result, m...)
} else {
result = append(result, e)
}
}
if len(result) == 0 {
return nil
}
return result
}

View file

@ -20,10 +20,9 @@ limitations under the License.
package k8s
import (
"errors"
"fmt"
"strings"
"tags.cncf.io/container-device-interface/internal/multierror"
)
// TotalAnnotationSizeLimitB defines the maximum size of all annotations in characters.
@ -31,17 +30,17 @@ const TotalAnnotationSizeLimitB int = 256 * (1 << 10) // 256 kB
// ValidateAnnotations validates that a set of annotations are correctly defined.
func ValidateAnnotations(annotations map[string]string, path string) error {
errors := multierror.New()
errs := []error{}
for k := range annotations {
// The rule is QualifiedName except that case doesn't matter, so convert to lowercase before checking.
for _, msg := range IsQualifiedName(strings.ToLower(k)) {
errors = multierror.Append(errors, fmt.Errorf("%v.%v is invalid: %v", path, k, msg))
errs = append(errs, fmt.Errorf("%v.%v is invalid: %v", path, k, msg))
}
}
if err := ValidateAnnotationsSize(annotations); err != nil {
errors = multierror.Append(errors, fmt.Errorf("%v is too long: %v", path, err))
errs = append(errs, fmt.Errorf("%v is too long: %v", path, err))
}
return errors
return errors.Join(errs...)
}
// ValidateAnnotationsSize validates that a set of annotations is not too large.

View file

@ -28,12 +28,11 @@ import (
"github.com/fsnotify/fsnotify"
oci "github.com/opencontainers/runtime-spec/specs-go"
"tags.cncf.io/container-device-interface/internal/multierror"
cdi "tags.cncf.io/container-device-interface/specs-go"
)
// Option is an option to change some aspect of default CDI behavior.
type Option func(*Cache) error
type Option func(*Cache)
// Cache stores CDI Specs loaded from Spec directories.
type Cache struct {
@ -54,16 +53,27 @@ type Cache struct {
// 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 {
return func(c *Cache) {
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.
//
// Note:
//
// The error returned by this function is always nil and it is only
// returned to maintain API compatibility with consumers.
func NewCache(options ...Option) (*Cache, error) {
return newCache(options...), nil
}
// newCache creates a CDI cache with the supplied options.
// This function allows testing without handling the nil error returned by the
// NewCache function.
func newCache(options ...Option) *Cache {
c := &Cache{
autoRefresh: true,
watch: &watch{},
@ -73,7 +83,8 @@ func NewCache(options ...Option) (*Cache, error) {
c.Lock()
defer c.Unlock()
return c, c.configure(options...)
c.configure(options...)
return c
}
// Configure applies options to the Cache. Updates and refreshes the
@ -86,18 +97,16 @@ func (c *Cache) Configure(options ...Option) error {
c.Lock()
defer c.Unlock()
return c.configure(options...)
c.configure(options...)
return nil
}
// Configure the Cache. Start/stop CDI Spec directory watch, refresh
// the Cache if necessary.
func (c *Cache) configure(options ...Option) error {
var err error
func (c *Cache) configure(options ...Option) {
for _, o := range options {
if err = o(c); err != nil {
return fmt.Errorf("failed to apply cache options: %w", err)
}
o(c)
}
c.dirErrors = make(map[string]error)
@ -108,8 +117,6 @@ func (c *Cache) configure(options ...Option) error {
c.watch.start(&c.Mutex, c.refresh, c.dirErrors)
}
c.refresh()
return nil
}
// Refresh rescans the CDI Spec directories and refreshes the Cache.
@ -125,11 +132,11 @@ func (c *Cache) Refresh() error {
}
// collect and return cached errors, much like refresh() does it
var result error
for _, errors := range c.errors {
result = multierror.Append(result, errors...)
errs := []error{}
for _, specErrs := range c.errors {
errs = append(errs, errors.Join(specErrs...))
}
return result
return errors.Join(errs...)
}
// Refresh the Cache by rescanning CDI Spec directories and files.
@ -139,12 +146,10 @@ func (c *Cache) refresh() error {
devices = map[string]*Device{}
conflicts = map[string]struct{}{}
specErrors = map[string][]error{}
result []error
)
// collect errors per spec file path and once globally
collectError := func(err error, paths ...string) {
result = append(result, err)
for _, path := range paths {
specErrors[path] = append(specErrors[path], err)
}
@ -197,7 +202,11 @@ func (c *Cache) refresh() error {
c.devices = devices
c.errors = specErrors
return multierror.New(result...)
errs := []error{}
for _, specErrs := range specErrors {
errs = append(errs, errors.Join(specErrs...))
}
return errors.Join(errs...)
}
// RefreshIfRequired triggers a refresh if necessary.

View file

@ -144,6 +144,20 @@ func (e *ContainerEdits) Apply(spec *oci.Spec) error {
}
}
if e.IntelRdt != nil {
// The specgen is missing functionality to set all parameters so we
// just piggy-back on it to initialize all structs and the copy over.
specgen.SetLinuxIntelRdtClosID(e.IntelRdt.ClosID)
spec.Linux.IntelRdt = e.IntelRdt.ToOCI()
}
for _, additionalGID := range e.AdditionalGIDs {
if additionalGID == 0 {
continue
}
specgen.AddProcessAdditionalGid(additionalGID)
}
return nil
}
@ -171,6 +185,11 @@ func (e *ContainerEdits) Validate() error {
return err
}
}
if e.IntelRdt != nil {
if err := ValidateIntelRdt(e.IntelRdt); err != nil {
return err
}
}
return nil
}
@ -192,6 +211,10 @@ func (e *ContainerEdits) Append(o *ContainerEdits) *ContainerEdits {
e.DeviceNodes = append(e.DeviceNodes, o.DeviceNodes...)
e.Hooks = append(e.Hooks, o.Hooks...)
e.Mounts = append(e.Mounts, o.Mounts...)
if o.IntelRdt != nil {
e.IntelRdt = o.IntelRdt
}
e.AdditionalGIDs = append(e.AdditionalGIDs, o.AdditionalGIDs...)
return e
}
@ -202,7 +225,25 @@ func (e *ContainerEdits) isEmpty() bool {
if e == nil {
return false
}
return len(e.Env)+len(e.DeviceNodes)+len(e.Hooks)+len(e.Mounts) == 0
if len(e.Env) > 0 {
return false
}
if len(e.DeviceNodes) > 0 {
return false
}
if len(e.Hooks) > 0 {
return false
}
if len(e.Mounts) > 0 {
return false
}
if len(e.AdditionalGIDs) > 0 {
return false
}
if e.IntelRdt != nil {
return false
}
return true
}
// ValidateEnv validates the given environment variables.
@ -280,6 +321,15 @@ func (m *Mount) Validate() error {
return nil
}
// ValidateIntelRdt validates the IntelRdt configuration.
func ValidateIntelRdt(i *specs.IntelRdt) error {
// ClosID must be a valid Linux filename
if len(i.ClosID) >= 4096 || i.ClosID == "." || i.ClosID == ".." || strings.ContainsAny(i.ClosID, "/\n") {
return errors.New("invalid ClosID")
}
return nil
}
// Ensure OCI Spec hooks are not nil so we can add hooks.
func ensureOCIHooks(spec *oci.Spec) {
if spec.Hooks == nil {

View file

@ -0,0 +1,70 @@
/*
Copyright © 2024 The CDI Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cdi
import (
"sync"
oci "github.com/opencontainers/runtime-spec/specs-go"
)
var (
defaultCache *Cache
getDefaultOnce sync.Once
)
func getOrCreateDefaultCache(options ...Option) (*Cache, bool) {
var created bool
getDefaultOnce.Do(func() {
defaultCache = newCache(options...)
created = true
})
return defaultCache, created
}
// GetDefaultCache returns the default CDI cache instance.
func GetDefaultCache() *Cache {
cache, _ := getOrCreateDefaultCache()
return cache
}
// Configure applies options to the default CDI cache. Updates and refreshes
// the default cache if options are not empty.
func Configure(options ...Option) error {
cache, created := getOrCreateDefaultCache(options...)
if len(options) == 0 || created {
return nil
}
return cache.Configure(options...)
}
// Refresh explicitly refreshes the default CDI cache instance.
func Refresh() error {
return GetDefaultCache().Refresh()
}
// InjectDevices injects the given qualified devices to the given OCI Spec.
// using the default CDI cache instance to resolve devices.
func InjectDevices(ociSpec *oci.Spec, devices ...string) ([]string, error) {
return GetDefaultCache().InjectDevices(ociSpec, devices...)
}
// GetErrors returns all errors encountered during the last refresh of
// the default CDI cache instance.
func GetErrors() map[string][]error {
return GetDefaultCache().GetErrors()
}

View file

@ -29,8 +29,22 @@
// 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. Moreover, the registry also operates on the
// same default cache. We plan to deprecate the registry and eventually
// remove it in a future release.
//
// # CDI Registry
//
// Note: the Registry and its related interfaces are deprecated and will
// be removed in a future version. Please use the default cache and its
// related package-level function instead.
//
// 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,

View file

@ -29,6 +29,11 @@ import (
//
// The most commonly used Registry functions are for refreshing the
// registry and injecting CDI devices into an OCI Spec.
//
// Deprecated: Registry is deprecated and will be removed in a future
// version. Please update your code to use the corresponding package-
// level functions Configure(), Refresh(), InjectDevices(), GetErrors(),
// and GetDefaultCache().
type Registry interface {
RegistryResolver
RegistryRefresher
@ -54,6 +59,10 @@ type Registry interface {
//
// GetSpecDirErrors returns any errors related to the configured
// Spec directories.
//
// Deprecated: RegistryRefresher is deprecated and will be removed
// in a future version. Please use the default cache and its related
// package-level functions instead.
type RegistryRefresher interface {
Configure(...Option) error
Refresh() error
@ -68,6 +77,10 @@ type RegistryRefresher interface {
// 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.
//
// Deprecated: RegistryRefresher is deprecated and will be removed
// in a future version. Please use the default cache and its related
// package-level functions instead.
type RegistryResolver interface {
InjectDevices(spec *oci.Spec, device ...string) (unresolved []string, err error)
}
@ -79,6 +92,12 @@ type RegistryResolver interface {
//
// ListDevices returns a slice with the names of qualified device
// known. The returned slice is sorted.
//
// Deprecated: RegistryDeviceDB is deprecated and will be removed
// in a future version. Please use the default cache and its related
// package-level functions instead.
// and will be removed in a future version. Please use the default
// cache and its related package-level functions instead.
type RegistryDeviceDB interface {
GetDevice(device string) *Device
ListDevices() []string
@ -99,6 +118,10 @@ type RegistryDeviceDB interface {
//
// WriteSpec writes the Spec with the given content and name to the
// last Spec directory.
//
// Deprecated: RegistrySpecDB is deprecated and will be removed
// in a future version. Please use the default cache and its related
// package-level functions instead.
type RegistrySpecDB interface {
ListVendors() []string
ListClasses() []string
@ -121,30 +144,35 @@ var (
// GetRegistry returns the CDI registry. If any options are given, those
// are applied to the registry.
//
// Deprecated: GetRegistry is deprecated and will be removed in a future
// version. Please use the default cache and its related package-level
// functions instead.
func GetRegistry(options ...Option) Registry {
var new bool
initOnce.Do(func() {
reg, _ = getRegistry(options...)
new = true
reg = &registry{GetDefaultCache()}
})
if !new && len(options) > 0 {
reg.Configure(options...)
reg.Refresh()
if len(options) > 0 {
// We don't care about errors here
_ = reg.Configure(options...)
}
return reg
}
// DeviceDB returns the registry interface for querying devices.
//
// Deprecated: DeviceDB is deprecated and will be removed in a future
// version. Please use the default cache and its related package-level
// functions instead.
func (r *registry) DeviceDB() RegistryDeviceDB {
return r
}
// SpecDB returns the registry interface for querying Specs.
//
// Deprecated: SpecDB is deprecated and will be removed in a future
// version. Please use the default cache and its related package-level
// functions instead.
func (r *registry) SpecDB() RegistrySpecDB {
return r
}
func getRegistry(options ...Option) (*registry, error) {
c, err := NewCache(options...)
return &registry{c}, err
}

View file

@ -44,13 +44,12 @@ var (
// WithSpecDirs returns an option to override the CDI Spec directories.
func WithSpecDirs(dirs ...string) Option {
return func(c *Cache) error {
return func(c *Cache) {
specDirs := make([]string, len(dirs))
for i, dir := range dirs {
specDirs[i] = filepath.Clean(dir)
}
c.specDirs = specDirs
return nil
}
}

View file

@ -19,7 +19,6 @@ package cdi
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
@ -62,7 +61,7 @@ type Spec struct {
// assigned the given priority. If reading or parsing the Spec
// data fails ReadSpec returns a nil Spec and an error.
func ReadSpec(path string, priority int) (*Spec, error) {
data, err := ioutil.ReadFile(path)
data, err := os.ReadFile(path)
switch {
case os.IsNotExist(err):
return nil, err

View file

@ -39,6 +39,7 @@ const (
v040 version = "v0.4.0"
v050 version = "v0.5.0"
v060 version = "v0.6.0"
v070 version = "v0.7.0"
// vEarliest is the earliest supported version of the CDI specification
vEarliest version = v030
@ -54,6 +55,7 @@ var validSpecVersions = requiredVersionMap{
v040: requiresV040,
v050: requiresV050,
v060: requiresV060,
v070: requiresV070,
}
// MinimumRequiredVersion determines the minimum spec version for the input spec.
@ -118,6 +120,29 @@ func (r requiredVersionMap) requiredVersion(spec *cdi.Spec) version {
return minVersion
}
// requiresV070 returns true if the spec uses v0.7.0 features
func requiresV070(spec *cdi.Spec) bool {
if spec.ContainerEdits.IntelRdt != nil {
return true
}
// The v0.7.0 spec allows additional GIDs to be specified at a spec level.
if len(spec.ContainerEdits.AdditionalGIDs) > 0 {
return true
}
for _, d := range spec.Devices {
if d.ContainerEdits.IntelRdt != nil {
return true
}
// The v0.7.0 spec allows additional GIDs to be specified at a device level.
if len(d.ContainerEdits.AdditionalGIDs) > 0 {
return true
}
}
return false
}
// requiresV060 returns true if the spec uses v0.6.0 features
func requiresV060(spec *cdi.Spec) bool {
// The v0.6.0 spec allows annotations to be specified at a spec level

View file

@ -3,7 +3,7 @@ package specs
import "os"
// CurrentVersion is the current version of the Spec.
const CurrentVersion = "0.6.0"
const CurrentVersion = "0.7.0"
// Spec is the base configuration for CDI
type Spec struct {
@ -25,10 +25,12 @@ type Device struct {
// ContainerEdits are edits a container runtime must make to the OCI spec to expose the device.
type ContainerEdits struct {
Env []string `json:"env,omitempty"`
DeviceNodes []*DeviceNode `json:"deviceNodes,omitempty"`
Hooks []*Hook `json:"hooks,omitempty"`
Mounts []*Mount `json:"mounts,omitempty"`
Env []string `json:"env,omitempty"`
DeviceNodes []*DeviceNode `json:"deviceNodes,omitempty"`
Hooks []*Hook `json:"hooks,omitempty"`
Mounts []*Mount `json:"mounts,omitempty"`
IntelRdt *IntelRdt `json:"intelRdt,omitempty"`
AdditionalGIDs []uint32 `json:"additionalGids,omitempty"`
}
// DeviceNode represents a device node that needs to be added to the OCI spec.
@ -60,3 +62,12 @@ type Hook struct {
Env []string `json:"env,omitempty"`
Timeout *int `json:"timeout,omitempty"`
}
// IntelRdt describes the Linux IntelRdt parameters to set in the OCI spec.
type IntelRdt struct {
ClosID string `json:"closID,omitempty"`
L3CacheSchema string `json:"l3CacheSchema,omitempty"`
MemBwSchema string `json:"memBwSchema,omitempty"`
EnableCMT bool `json:"enableCMT,omitempty"`
EnableMBM bool `json:"enableMBM,omitempty"`
}

View file

@ -36,3 +36,14 @@ func (d *DeviceNode) ToOCI() spec.LinuxDevice {
GID: d.GID,
}
}
// ToOCI returns the opencontainers runtime Spec LinuxIntelRdt for this IntelRdt config.
func (i *IntelRdt) ToOCI() *spec.LinuxIntelRdt {
return &spec.LinuxIntelRdt{
ClosID: i.ClosID,
L3CacheSchema: i.L3CacheSchema,
MemBwSchema: i.MemBwSchema,
EnableCMT: i.EnableCMT,
EnableMBM: i.EnableMBM,
}
}