aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/container-orchestrated-devices/container-device-interface
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/container-orchestrated-devices/container-device-interface')
-rw-r--r--vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/annotations.go139
-rw-r--r--vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go16
-rw-r--r--vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits.go143
-rw-r--r--vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/device.go10
-rw-r--r--vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go25
-rw-r--r--vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/config.go8
6 files changed, 319 insertions, 22 deletions
diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/annotations.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/annotations.go
new file mode 100644
index 000000000..1055c7df8
--- /dev/null
+++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/annotations.go
@@ -0,0 +1,139 @@
+/*
+ Copyright © 2021-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 cdi
+
+import (
+ "strings"
+
+ "github.com/pkg/errors"
+)
+
+const (
+ // AnnotationPrefix is the prefix for CDI container annotation keys.
+ AnnotationPrefix = "cdi.k8s.io/"
+)
+
+// 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 UpdateAnnotations(annotations map[string]string, plugin string, deviceID string, devices []string) (map[string]string, error) {
+ key, err := AnnotationKey(plugin, deviceID)
+ if err != nil {
+ return annotations, errors.Wrap(err, "CDI annotation failed")
+ }
+ if _, ok := annotations[key]; ok {
+ return annotations, errors.Errorf("CDI annotation failed, key %q used", key)
+ }
+ value, err := AnnotationValue(devices)
+ if err != nil {
+ return annotations, errors.Wrap(err, "CDI annotation failed")
+ }
+
+ if annotations == nil {
+ annotations = make(map[string]string)
+ }
+ annotations[key] = value
+
+ return annotations, nil
+}
+
+// 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 ParseAnnotations(annotations map[string]string) ([]string, []string, error) {
+ var (
+ keys []string
+ devices []string
+ )
+
+ for key, value := range annotations {
+ if !strings.HasPrefix(key, AnnotationPrefix) {
+ continue
+ }
+ for _, d := range strings.Split(value, ",") {
+ if !IsQualifiedName(d) {
+ return nil, nil, errors.Errorf("invalid CDI device name %q", d)
+ }
+ devices = append(devices, d)
+ }
+ keys = append(keys, key)
+ }
+
+ return keys, devices, nil
+}
+
+// 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 AnnotationKey(pluginName, deviceID string) (string, error) {
+ const maxNameLen = 63
+
+ if pluginName == "" {
+ return "", errors.New("invalid plugin name, empty")
+ }
+ if deviceID == "" {
+ return "", errors.New("invalid deviceID, empty")
+ }
+
+ name := pluginName + "_" + strings.ReplaceAll(deviceID, "/", "_")
+
+ if len(name) > maxNameLen {
+ return "", errors.Errorf("invalid plugin+deviceID %q, too long", name)
+ }
+
+ if c := rune(name[0]); !isAlphaNumeric(c) {
+ return "", errors.Errorf("invalid name %q, first '%c' should be alphanumeric",
+ name, c)
+ }
+ if len(name) > 2 {
+ for _, c := range name[1 : len(name)-1] {
+ switch {
+ case isAlphaNumeric(c):
+ case c == '_' || c == '-' || c == '.':
+ default:
+ return "", errors.Errorf("invalid name %q, invalid charcter '%c'",
+ name, c)
+ }
+ }
+ }
+ if c := rune(name[len(name)-1]); !isAlphaNumeric(c) {
+ return "", errors.Errorf("invalid name %q, last '%c' should be alphanumeric",
+ name, c)
+ }
+
+ return AnnotationPrefix + name, nil
+}
+
+// AnnotationValue returns an annotation value for the given devices.
+func AnnotationValue(devices []string) (string, error) {
+ value, sep := "", ""
+ for _, d := range devices {
+ if _, _, _, err := ParseQualifiedName(d); err != nil {
+ return "", err
+ }
+ value += sep + d
+ sep = ","
+ }
+
+ return value, nil
+}
diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go
index c4befc83e..5c3f803f4 100644
--- a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go
+++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/cache.go
@@ -153,10 +153,7 @@ func (c *Cache) Refresh() error {
// returns any unresolvable devices and an error if injection fails for
// any of the devices.
func (c *Cache) InjectDevices(ociSpec *oci.Spec, devices ...string) ([]string, error) {
- var (
- unresolved []string
- specs = map[*Spec]struct{}{}
- )
+ var unresolved []string
if ociSpec == nil {
return devices, errors.Errorf("can't inject devices, nil OCI Spec")
@@ -165,6 +162,9 @@ func (c *Cache) InjectDevices(ociSpec *oci.Spec, devices ...string) ([]string, e
c.Lock()
defer c.Unlock()
+ edits := &ContainerEdits{}
+ specs := map[*Spec]struct{}{}
+
for _, device := range devices {
d := c.devices[device]
if d == nil {
@@ -173,9 +173,9 @@ func (c *Cache) InjectDevices(ociSpec *oci.Spec, devices ...string) ([]string, e
}
if _, ok := specs[d.GetSpec()]; !ok {
specs[d.GetSpec()] = struct{}{}
- d.GetSpec().ApplyEdits(ociSpec)
+ edits.Append(d.GetSpec().edits())
}
- d.ApplyEdits(ociSpec)
+ edits.Append(d.edits())
}
if unresolved != nil {
@@ -183,6 +183,10 @@ func (c *Cache) InjectDevices(ociSpec *oci.Spec, devices ...string) ([]string, e
strings.Join(devices, ", "))
}
+ if err := edits.Apply(ociSpec); err != nil {
+ return nil, errors.Wrap(err, "failed to inject devices")
+ }
+
return nil, nil
}
diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits.go
index 767cba57b..80d88b118 100644
--- a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits.go
+++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/container-edits.go
@@ -17,6 +17,9 @@
package cdi
import (
+ "os"
+ "path/filepath"
+ "sort"
"strings"
"github.com/pkg/errors"
@@ -24,6 +27,8 @@ import (
"github.com/container-orchestrated-devices/container-device-interface/specs-go"
oci "github.com/opencontainers/runtime-spec/specs-go"
ocigen "github.com/opencontainers/runtime-tools/generate"
+
+ runc "github.com/opencontainers/runc/libcontainer/devices"
)
const (
@@ -78,12 +83,44 @@ func (e *ContainerEdits) Apply(spec *oci.Spec) error {
if len(e.Env) > 0 {
specgen.AddMultipleProcessEnv(e.Env)
}
+
for _, d := range e.DeviceNodes {
- specgen.AddDevice(d.ToOCI())
+ dev := d.ToOCI()
+ if err := fillMissingInfo(&dev); err != nil {
+ return err
+ }
+
+ if dev.UID == nil && spec.Process != nil {
+ if uid := spec.Process.User.UID; uid > 0 {
+ dev.UID = &uid
+ }
+ }
+ if dev.GID == nil && spec.Process != nil {
+ if gid := spec.Process.User.GID; gid > 0 {
+ dev.GID = &gid
+ }
+ }
+
+ specgen.RemoveDevice(dev.Path)
+ specgen.AddDevice(dev)
+
+ if dev.Type == "b" || dev.Type == "c" {
+ access := d.Permissions
+ if access == "" {
+ access = "rwm"
+ }
+ specgen.AddLinuxResourcesDevice(true, dev.Type, &dev.Major, &dev.Minor, access)
+ }
}
- for _, m := range e.Mounts {
- specgen.AddMount(m.ToOCI())
+
+ if len(e.Mounts) > 0 {
+ for _, m := range e.Mounts {
+ specgen.RemoveMount(m.ContainerPath)
+ specgen.AddMount(m.ToOCI())
+ }
+ sortMounts(&specgen)
}
+
for _, h := range e.Hooks {
switch h.HookName {
case PrestartHook:
@@ -138,6 +175,27 @@ func (e *ContainerEdits) Validate() error {
return nil
}
+// Append other edits into this one. If called with a nil receiver,
+// allocates and returns newly allocated edits.
+func (e *ContainerEdits) Append(o *ContainerEdits) *ContainerEdits {
+ if o == nil || o.ContainerEdits == nil {
+ return e
+ }
+ if e == nil {
+ e = &ContainerEdits{}
+ }
+ if e.ContainerEdits == nil {
+ e.ContainerEdits = &specs.ContainerEdits{}
+ }
+
+ e.Env = append(e.Env, o.Env...)
+ e.DeviceNodes = append(e.DeviceNodes, o.DeviceNodes...)
+ e.Hooks = append(e.Hooks, o.Hooks...)
+ e.Mounts = append(e.Mounts, o.Mounts...)
+
+ return e
+}
+
// isEmpty returns true if these edits are empty. This is valid in a
// global Spec context but invalid in a Device context.
func (e *ContainerEdits) isEmpty() bool {
@@ -164,10 +222,18 @@ type DeviceNode struct {
// Validate a CDI Spec DeviceNode.
func (d *DeviceNode) Validate() error {
+ validTypes := map[string]struct{}{
+ "": {},
+ "b": {},
+ "c": {},
+ "u": {},
+ "p": {},
+ }
+
if d.Path == "" {
return errors.New("invalid (empty) device path")
}
- if d.Type != "" && d.Type != "b" && d.Type != "c" {
+ if _, ok := validTypes[d.Type]; !ok {
return errors.Errorf("device %q: invalid type %q", d.Path, d.Type)
}
for _, bit := range d.Permissions {
@@ -220,3 +286,72 @@ func ensureOCIHooks(spec *oci.Spec) {
spec.Hooks = &oci.Hooks{}
}
}
+
+// fillMissingInfo fills in missing mandatory attributes from the host device.
+func fillMissingInfo(dev *oci.LinuxDevice) error {
+ if dev.Type != "" && (dev.Major != 0 || dev.Type == "p") {
+ return nil
+ }
+ hostDev, err := runc.DeviceFromPath(dev.Path, "rwm")
+ if err != nil {
+ return errors.Wrapf(err, "failed to stat CDI host device %q", dev.Path)
+ }
+
+ if dev.Type == "" {
+ dev.Type = string(hostDev.Type)
+ } else {
+ if dev.Type != string(hostDev.Type) {
+ return errors.Errorf("CDI device %q, host type mismatch (%s, %s)",
+ dev.Path, dev.Type, string(hostDev.Type))
+ }
+ }
+ if dev.Major == 0 && dev.Type != "p" {
+ dev.Major = hostDev.Major
+ dev.Minor = hostDev.Minor
+ }
+
+ return nil
+}
+
+// sortMounts sorts the mounts in the given OCI Spec.
+func sortMounts(specgen *ocigen.Generator) {
+ mounts := specgen.Mounts()
+ specgen.ClearMounts()
+ sort.Sort(orderedMounts(mounts))
+ specgen.Config.Mounts = mounts
+}
+
+// orderedMounts defines how to sort an OCI Spec Mount slice.
+// This is the almost the same implementation sa used by CRI-O and Docker,
+// with a minor tweak for stable sorting order (easier to test):
+// https://github.com/moby/moby/blob/17.05.x/daemon/volumes.go#L26
+type orderedMounts []oci.Mount
+
+// Len returns the number of mounts. Used in sorting.
+func (m orderedMounts) Len() int {
+ return len(m)
+}
+
+// Less returns true if the number of parts (a/b/c would be 3 parts) in the
+// mount indexed by parameter 1 is less than that of the mount indexed by
+// parameter 2. Used in sorting.
+func (m orderedMounts) Less(i, j int) bool {
+ ip, jp := m.parts(i), m.parts(j)
+ if ip < jp {
+ return true
+ }
+ if jp < ip {
+ return false
+ }
+ return m[i].Destination < m[j].Destination
+}
+
+// Swap swaps two items in an array of mounts. Used in sorting
+func (m orderedMounts) Swap(i, j int) {
+ m[i], m[j] = m[j], m[i]
+}
+
+// parts returns the number of parts in the destination of a mount. Used in sorting.
+func (m orderedMounts) parts(i int) int {
+ return strings.Count(filepath.Clean(m[i].Destination), string(os.PathSeparator))
+}
diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/device.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/device.go
index 84b71429c..0bb1f531b 100644
--- a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/device.go
+++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/device.go
@@ -54,8 +54,12 @@ func (d *Device) GetQualifiedName() string {
// ApplyEdits applies the device-speific container edits to an OCI Spec.
func (d *Device) ApplyEdits(ociSpec *oci.Spec) error {
- e := ContainerEdits{&d.ContainerEdits}
- return e.Apply(ociSpec)
+ return d.edits().Apply(ociSpec)
+}
+
+// edits returns the applicable container edits for this spec.
+func (d *Device) edits() *ContainerEdits {
+ return &ContainerEdits{&d.ContainerEdits}
}
// Validate the device.
@@ -63,7 +67,7 @@ func (d *Device) validate() error {
if err := ValidateDeviceName(d.Name); err != nil {
return err
}
- edits := ContainerEdits{&d.ContainerEdits}
+ edits := d.edits()
if edits.isEmpty() {
return errors.Errorf("invalid device, empty device edits")
}
diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go
index 20f188498..adebc101f 100644
--- a/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go
+++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/pkg/cdi/spec.go
@@ -33,6 +33,7 @@ var (
validSpecVersions = map[string]struct{}{
"0.1.0": {},
"0.2.0": {},
+ "0.3.0": {},
}
)
@@ -120,14 +121,18 @@ func (s *Spec) GetPriority() int {
// ApplyEdits applies the Spec's global-scope container edits to an OCI Spec.
func (s *Spec) ApplyEdits(ociSpec *oci.Spec) error {
- e := ContainerEdits{&s.ContainerEdits}
- return e.Apply(ociSpec)
+ return s.edits().Apply(ociSpec)
+}
+
+// edits returns the applicable global container edits for this spec.
+func (s *Spec) edits() *ContainerEdits {
+ return &ContainerEdits{&s.ContainerEdits}
}
// Validate the Spec.
func (s *Spec) validate() (map[string]*Device, error) {
- if _, ok := validSpecVersions[s.Version]; !ok {
- return nil, errors.Errorf("invalid version %q", s.Version)
+ if err := validateVersion(s.Version); err != nil {
+ return nil, err
}
if err := ValidateVendorName(s.vendor); err != nil {
return nil, err
@@ -135,8 +140,7 @@ func (s *Spec) validate() (map[string]*Device, error) {
if err := ValidateClassName(s.class); err != nil {
return nil, err
}
- edits := &ContainerEdits{&s.ContainerEdits}
- if err := edits.Validate(); err != nil {
+ if err := s.edits().Validate(); err != nil {
return nil, err
}
@@ -155,6 +159,15 @@ func (s *Spec) validate() (map[string]*Device, error) {
return devices, nil
}
+// 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)
+ }
+
+ return nil
+}
+
// Parse raw CDI Spec file data.
func parseSpec(data []byte) (*cdi.Spec, error) {
raw := &cdi.Spec{}
diff --git a/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/config.go b/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/config.go
index 20914e5b6..090e30e43 100644
--- a/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/config.go
+++ b/vendor/github.com/container-orchestrated-devices/container-device-interface/specs-go/config.go
@@ -2,11 +2,13 @@ package specs
import "os"
+// CurrentVersion is the current version of the Spec.
+const CurrentVersion = "0.3.0"
+
// Spec is the base configuration for CDI
type Spec struct {
- Version string `json:"cdiVersion"`
- Kind string `json:"kind"`
- ContainerRuntime []string `json:"containerRuntime,omitempty"`
+ Version string `json:"cdiVersion"`
+ Kind string `json:"kind"`
Devices []Device `json:"devices"`
ContainerEdits ContainerEdits `json:"containerEdits,omitempty"`