update CDI version to v0.5.4

Signed-off-by: Ed Bartosh <eduard.bartosh@intel.com>
This commit is contained in:
Ed Bartosh
2023-02-22 16:34:43 +02:00
parent 8cb00f45c9
commit 30e4a14092
19 changed files with 738 additions and 172 deletions

View File

@@ -18,29 +18,28 @@ package cdi
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
oci "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"sigs.k8s.io/yaml"
cdi "github.com/container-orchestrated-devices/container-device-interface/specs-go"
)
var (
// Valid CDI Spec versions.
validSpecVersions = map[string]struct{}{
"0.1.0": {},
"0.2.0": {},
"0.3.0": {},
"0.4.0": {},
"0.5.0": {},
}
const (
// defaultSpecExt is the file extension for the default encoding.
defaultSpecExt = ".yaml"
)
var (
// Externally set CDI Spec validation function.
specValidator func(*cdi.Spec) error
validatorLock sync.RWMutex
)
// Spec represents a single CDI Spec. It is usually loaded from a
@@ -67,18 +66,18 @@ func ReadSpec(path string, priority int) (*Spec, error) {
case os.IsNotExist(err):
return nil, err
case err != nil:
return nil, errors.Wrapf(err, "failed to read CDI Spec %q", path)
return nil, fmt.Errorf("failed to read CDI Spec %q: %w", path, err)
}
raw, err := ParseSpec(data)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse CDI Spec %q", path)
return nil, fmt.Errorf("failed to parse CDI Spec %q: %w", path, err)
}
if raw == nil {
return nil, errors.Errorf("failed to parse CDI Spec %q, no Spec data", path)
return nil, fmt.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
}
@@ -86,11 +85,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
@@ -102,18 +101,22 @@ 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 {
return nil, errors.Wrap(err, "invalid CDI Spec")
return nil, fmt.Errorf("invalid CDI Spec: %w", err)
}
return spec, nil
}
// 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
@@ -132,30 +135,30 @@ func (s *Spec) Write(overwrite bool) error {
data, err = json.Marshal(s.Spec)
}
if err != nil {
return errors.Wrap(err, "failed to marshal Spec file")
return fmt.Errorf("failed to marshal Spec file: %w", err)
}
dir = filepath.Dir(s.path)
err = os.MkdirAll(dir, 0o755)
if err != nil {
return errors.Wrap(err, "failed to create Spec dir")
return fmt.Errorf("failed to create Spec dir: %w", err)
}
tmp, err = os.CreateTemp(dir, "spec.*.tmp")
if err != nil {
return errors.Wrap(err, "failed to create Spec file")
return fmt.Errorf("failed to create Spec file: %w", err)
}
_, err = tmp.Write(data)
tmp.Close()
if err != nil {
return errors.Wrap(err, "failed to write Spec file")
return fmt.Errorf("failed to write Spec file: %w", err)
}
err = renameIn(dir, filepath.Base(tmp.Name()), filepath.Base(s.path), overwrite)
if err != nil {
os.Remove(tmp.Name())
err = errors.Wrap(err, "failed to write Spec file")
err = fmt.Errorf("failed to write Spec file: %w", err)
}
return err
@@ -201,6 +204,15 @@ func (s *Spec) validate() (map[string]*Device, error) {
if err := validateVersion(s.Version); err != nil {
return nil, err
}
minVersion, err := MinimumRequiredVersion(s.Spec)
if err != nil {
return nil, fmt.Errorf("could not determine minumum required version: %v", err)
}
if newVersion(minVersion).IsGreaterThan(newVersion(s.Version)) {
return nil, fmt.Errorf("the spec version must be at least v%v", minVersion)
}
if err := ValidateVendorName(s.vendor); err != nil {
return nil, err
}
@@ -215,10 +227,10 @@ func (s *Spec) validate() (map[string]*Device, error) {
for _, d := range s.Devices {
dev, err := newDevice(s, d)
if err != nil {
return nil, errors.Wrapf(err, "failed add device %q", d.Name)
return nil, fmt.Errorf("failed add device %q: %w", d.Name, err)
}
if _, conflict := devices[d.Name]; conflict {
return nil, errors.Errorf("invalid spec, multiple device %q", d.Name)
return nil, fmt.Errorf("invalid spec, multiple device %q", d.Name)
}
devices[d.Name] = dev
}
@@ -228,8 +240,8 @@ func (s *Spec) validate() (map[string]*Device, error) {
// validateVersion checks whether the specified spec version is supported.
func validateVersion(version string) error {
if _, ok := validSpecVersions[version]; !ok {
return errors.Errorf("invalid version %q", version)
if !validSpecVersions.isValidVersion(version) {
return fmt.Errorf("invalid version %q", version)
}
return nil
@@ -240,26 +252,96 @@ func ParseSpec(data []byte) (*cdi.Spec, error) {
var raw *cdi.Spec
err := yaml.UnmarshalStrict(data, &raw)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal CDI Spec")
return nil, fmt.Errorf("failed to unmarshal CDI Spec: %w", err)
}
return raw, nil
}
// 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()
specValidator = fn
}
// validateSpec validates the Spec using the extneral validator.
func validateSpec(raw *cdi.Spec) error {
validatorLock.RLock()
defer validatorLock.RUnlock()
if specValidator == nil {
return nil
}
err := specValidator(raw)
if err != nil {
return errors.Wrap(err, "Spec validation failed")
return fmt.Errorf("Spec validation failed: %w", err)
}
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 "", fmt.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 "", fmt.Errorf("invalid vendor/class %q in Spec", raw.Kind)
}
return GenerateTransientSpecName(vendor, class, transientID), nil
}