cri: implement CDI device injection

Extract the names of requested CDI devices and update the OCI
Spec according to the corresponding CDI device specifications.

CDI devices are requested using container annotations in the
cdi.k8s.io namespace. Once CRI gains dedicated fields for CDI
injection the snippet for extracting CDI names will need an
update.

Signed-off-by: Ed Bartosh <eduard.bartosh@intel.com>
This commit is contained in:
Ed Bartosh
2022-01-25 17:29:40 +02:00
parent ffddd4446c
commit aed0538dac
3 changed files with 189 additions and 0 deletions

View File

@@ -239,6 +239,7 @@ func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateConta
return nil, fmt.Errorf("failed to get runtime options: %w", err)
}
opts = append(opts,
containerd.WithCDI(spec, config.Annotations),
containerd.WithSpec(spec, specOpts...),
containerd.WithRuntime(sandboxInfo.Runtime.Name, runtimeOptions),
containerd.WithContainerLabels(containerLabels),

View File

@@ -20,12 +20,15 @@ import (
"context"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
"github.com/containerd/containerd"
"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/contrib/apparmor"
"github.com/containerd/containerd/contrib/seccomp"
@@ -1485,3 +1488,152 @@ func TestBaseOCISpec(t *testing.T) {
assert.Equal(t, *spec.Linux.Resources.Memory.Limit, containerConfig.Linux.Resources.MemoryLimitInBytes)
}
func writeFilesToTempDir(tmpDirPattern string, content []string) (string, error) {
if len(content) == 0 {
return "", nil
}
dir, err := ioutil.TempDir("", tmpDirPattern)
if err != nil {
return "", err
}
for idx, data := range content {
file := filepath.Join(dir, fmt.Sprintf("spec-%d.yaml", idx))
err := ioutil.WriteFile(file, []byte(data), 0644)
if err != nil {
return "", err
}
}
return dir, cdi.GetRegistry(cdi.WithSpecDirs(dir)).Refresh()
}
func TestCDIInjections(t *testing.T) {
testID := "test-id"
testSandboxID := "sandbox-id"
testContainerName := "container-name"
testPid := uint32(1234)
containerConfig, sandboxConfig, imageConfig, specCheck := getCreateContainerTestData()
ociRuntime := config.Runtime{}
c := newTestCRIService()
for _, test := range []struct {
description string
cdiSpecFiles []string
annotations map[string]string
expectError bool
expectDevices []runtimespec.LinuxDevice
expectEnv []string
}{
{description: "expect no CDI error for nil annotations"},
{description: "expect no CDI error for empty annotations",
annotations: map[string]string{},
},
{description: "expect CDI error for invalid CDI device reference in annotations",
annotations: map[string]string{
cdi.AnnotationPrefix + "devices": "foobar",
},
expectError: true,
},
{description: "expect CDI error for unresolvable devices",
annotations: map[string]string{
cdi.AnnotationPrefix + "vendor1_devices": "vendor1.com/device=no-such-dev",
},
expectError: true,
},
{description: "expect properly injected resolvable CDI devices",
cdiSpecFiles: []string{
`
cdiVersion: "0.2.0"
kind: "vendor1.com/device"
devices:
- name: foo
containerEdits:
deviceNodes:
- path: /dev/loop8
type: b
major: 7
minor: 8
env:
- FOO=injected
containerEdits:
env:
- "VENDOR1=present"
`,
`
cdiVersion: "0.2.0"
kind: "vendor2.com/device"
devices:
- name: bar
containerEdits:
deviceNodes:
- path: /dev/loop9
type: b
major: 7
minor: 9
env:
- BAR=injected
containerEdits:
env:
- "VENDOR2=present"
`,
},
annotations: map[string]string{
cdi.AnnotationPrefix + "vendor1_devices": "vendor1.com/device=foo",
cdi.AnnotationPrefix + "vendor2_devices": "vendor2.com/device=bar",
},
expectDevices: []runtimespec.LinuxDevice{
{
Path: "/dev/loop8",
Type: "b",
Major: 7,
Minor: 8,
},
{
Path: "/dev/loop9",
Type: "b",
Major: 7,
Minor: 9,
},
},
expectEnv: []string{
"FOO=injected",
"VENDOR1=present",
"BAR=injected",
"VENDOR2=present",
},
},
} {
t.Logf("TestCase %q", test.description)
spec, err := c.containerSpec(testID, testSandboxID, testPid, "", testContainerName, testImageName, containerConfig, sandboxConfig, imageConfig, nil, ociRuntime)
require.NoError(t, err)
specCheck(t, testID, testSandboxID, testPid, spec)
cdiDir, err := writeFilesToTempDir("containerd-test-CDI-injections-", test.cdiSpecFiles)
if cdiDir != "" {
defer os.RemoveAll(cdiDir)
}
require.NoError(t, err)
injectFun := containerd.WithCDI(spec, test.annotations)
err = injectFun(nil, nil, nil)
assert.Equal(t, test.expectError, err != nil)
if err != nil {
if test.expectEnv != nil {
for _, expectedEnv := range test.expectEnv {
assert.Contains(t, spec.Process.Env, expectedEnv)
}
}
if test.expectDevices != nil {
for _, expectedDev := range test.expectDevices {
assert.Contains(t, spec.Linux.Devices, expectedDev)
}
}
}
}
}