2023-03-09 14:18:40 +00:00
|
|
|
package container // import "github.com/docker/docker/integration/container"
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2023-07-17 23:50:08 +00:00
|
|
|
"encoding/json"
|
2023-03-09 14:18:40 +00:00
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
containertypes "github.com/docker/docker/api/types/container"
|
|
|
|
"github.com/docker/docker/integration/internal/container"
|
|
|
|
"github.com/docker/docker/pkg/stdcopy"
|
2023-07-14 18:02:38 +00:00
|
|
|
"github.com/docker/docker/testutil"
|
2023-03-09 14:18:40 +00:00
|
|
|
"github.com/docker/docker/testutil/daemon"
|
|
|
|
"gotest.tools/v3/assert"
|
|
|
|
is "gotest.tools/v3/assert/cmp"
|
|
|
|
"gotest.tools/v3/skip"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestCreateWithCDIDevices(t *testing.T) {
|
2023-06-14 09:46:00 +00:00
|
|
|
skip.If(t, testEnv.DaemonInfo.OSType != "linux", "CDI devices are only supported on Linux")
|
2023-03-09 14:18:40 +00:00
|
|
|
skip.If(t, testEnv.IsRemoteDaemon, "cannot run cdi tests with a remote daemon")
|
|
|
|
|
2023-07-14 18:02:38 +00:00
|
|
|
ctx := testutil.StartSpan(baseContext, t)
|
|
|
|
|
2023-03-09 14:18:40 +00:00
|
|
|
cwd, err := os.Getwd()
|
|
|
|
assert.NilError(t, err)
|
2024-01-16 20:12:09 +00:00
|
|
|
configPath := filepath.Join(cwd, "daemon.json")
|
|
|
|
err = os.WriteFile(configPath, []byte(`{"features": {"cdi": true}}`), 0o644)
|
|
|
|
defer os.Remove(configPath)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
d := daemon.New(t)
|
|
|
|
d.StartWithBusybox(ctx, t, "--config-file", configPath, "--cdi-spec-dir="+filepath.Join(cwd, "testdata", "cdi"))
|
2023-03-09 14:18:40 +00:00
|
|
|
defer d.Stop(t)
|
|
|
|
|
2023-08-11 11:45:45 +00:00
|
|
|
apiClient := d.NewClientT(t)
|
2023-03-09 14:18:40 +00:00
|
|
|
|
2023-08-11 11:45:45 +00:00
|
|
|
id := container.Run(ctx, t, apiClient,
|
2023-03-09 14:18:40 +00:00
|
|
|
container.WithCmd("/bin/sh", "-c", "env"),
|
|
|
|
container.WithCDIDevices("vendor1.com/device=foo"),
|
|
|
|
)
|
2023-08-25 18:25:58 +00:00
|
|
|
defer apiClient.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true})
|
2023-03-09 14:18:40 +00:00
|
|
|
|
2023-08-11 11:45:45 +00:00
|
|
|
inspect, err := apiClient.ContainerInspect(ctx, id)
|
2023-03-09 14:18:40 +00:00
|
|
|
assert.NilError(t, err)
|
|
|
|
|
|
|
|
expectedRequests := []containertypes.DeviceRequest{
|
|
|
|
{
|
2023-04-14 08:23:05 +00:00
|
|
|
Driver: "cdi",
|
|
|
|
DeviceIDs: []string{"vendor1.com/device=foo"},
|
2023-03-09 14:18:40 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
assert.Check(t, is.DeepEqual(inspect.HostConfig.DeviceRequests, expectedRequests))
|
|
|
|
|
2023-08-25 22:19:21 +00:00
|
|
|
reader, err := apiClient.ContainerLogs(ctx, id, containertypes.LogsOptions{
|
2023-03-09 14:18:40 +00:00
|
|
|
ShowStdout: true,
|
|
|
|
})
|
|
|
|
assert.NilError(t, err)
|
|
|
|
|
|
|
|
actualStdout := new(bytes.Buffer)
|
|
|
|
actualStderr := io.Discard
|
|
|
|
_, err = stdcopy.StdCopy(actualStdout, actualStderr, reader)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
|
|
|
|
outlines := strings.Split(actualStdout.String(), "\n")
|
|
|
|
assert.Assert(t, is.Contains(outlines, "FOO=injected"))
|
|
|
|
}
|
2023-07-17 23:50:08 +00:00
|
|
|
|
|
|
|
func TestCDISpecDirsAreInSystemInfo(t *testing.T) {
|
|
|
|
skip.If(t, testEnv.DaemonInfo.OSType == "windows") // d.Start fails on Windows with `protocol not available`
|
|
|
|
// TODO: This restriction can be relaxed with https://github.com/moby/moby/pull/46158
|
|
|
|
skip.If(t, testEnv.IsRootless, "the t.TempDir test creates a folder with incorrect permissions for rootless")
|
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
description string
|
|
|
|
config map[string]interface{}
|
|
|
|
specDirs []string
|
|
|
|
expectedInfoCDISpecDirs []string
|
|
|
|
}{
|
|
|
|
{
|
2024-01-16 20:12:09 +00:00
|
|
|
description: "CDI enabled with no spec dirs specified returns default",
|
|
|
|
config: map[string]interface{}{"features": map[string]bool{"cdi": true}},
|
2023-07-17 23:50:08 +00:00
|
|
|
specDirs: nil,
|
|
|
|
expectedInfoCDISpecDirs: []string{"/etc/cdi", "/var/run/cdi"},
|
|
|
|
},
|
|
|
|
{
|
2024-01-16 20:12:09 +00:00
|
|
|
description: "CDI enabled with specified spec dirs are returned",
|
|
|
|
config: map[string]interface{}{"features": map[string]bool{"cdi": true}},
|
2023-07-17 23:50:08 +00:00
|
|
|
specDirs: []string{"/foo/bar", "/baz/qux"},
|
|
|
|
expectedInfoCDISpecDirs: []string{"/foo/bar", "/baz/qux"},
|
|
|
|
},
|
|
|
|
{
|
2024-01-16 20:12:09 +00:00
|
|
|
description: "CDI enabled with empty string as spec dir returns empty slice",
|
|
|
|
config: map[string]interface{}{"features": map[string]bool{"cdi": true}},
|
2023-07-17 23:50:08 +00:00
|
|
|
specDirs: []string{""},
|
|
|
|
expectedInfoCDISpecDirs: []string{},
|
|
|
|
},
|
|
|
|
{
|
2024-01-16 20:12:09 +00:00
|
|
|
description: "CDI enabled with empty config option returns empty slice",
|
|
|
|
config: map[string]interface{}{"features": map[string]bool{"cdi": true}, "cdi-spec-dirs": []string{}},
|
2023-07-17 23:50:08 +00:00
|
|
|
expectedInfoCDISpecDirs: []string{},
|
|
|
|
},
|
|
|
|
{
|
2024-01-16 20:12:09 +00:00
|
|
|
description: "CDI disabled with no spec dirs specified returns empty slice",
|
2023-07-17 23:50:08 +00:00
|
|
|
specDirs: nil,
|
|
|
|
expectedInfoCDISpecDirs: []string{},
|
|
|
|
},
|
|
|
|
{
|
2024-01-16 20:12:09 +00:00
|
|
|
description: "CDI disabled with specified spec dirs returns empty slice",
|
2023-07-17 23:50:08 +00:00
|
|
|
specDirs: []string{"/foo/bar", "/baz/qux"},
|
|
|
|
expectedInfoCDISpecDirs: []string{},
|
|
|
|
},
|
|
|
|
{
|
2024-01-16 20:12:09 +00:00
|
|
|
description: "CDI disabled with empty string as spec dir returns empty slice",
|
2023-07-17 23:50:08 +00:00
|
|
|
specDirs: []string{""},
|
|
|
|
expectedInfoCDISpecDirs: []string{},
|
|
|
|
},
|
|
|
|
{
|
2024-01-16 20:12:09 +00:00
|
|
|
description: "CDI disabled with empty config option returns empty slice",
|
2023-07-17 23:50:08 +00:00
|
|
|
config: map[string]interface{}{"cdi-spec-dirs": []string{}},
|
|
|
|
expectedInfoCDISpecDirs: []string{},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(tc.description, func(t *testing.T) {
|
|
|
|
var opts []daemon.Option
|
|
|
|
d := daemon.New(t, opts...)
|
|
|
|
|
|
|
|
var args []string
|
|
|
|
for _, specDir := range tc.specDirs {
|
|
|
|
args = append(args, "--cdi-spec-dir="+specDir)
|
|
|
|
}
|
|
|
|
if tc.config != nil {
|
|
|
|
configPath := filepath.Join(t.TempDir(), "daemon.json")
|
|
|
|
|
|
|
|
configFile, err := os.Create(configPath)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
defer configFile.Close()
|
|
|
|
|
|
|
|
err = json.NewEncoder(configFile).Encode(tc.config)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
|
|
|
|
args = append(args, "--config-file="+configPath)
|
|
|
|
}
|
|
|
|
d.Start(t, args...)
|
|
|
|
defer d.Stop(t)
|
|
|
|
|
|
|
|
info := d.Info(t)
|
|
|
|
|
|
|
|
assert.Check(t, is.DeepEqual(tc.expectedInfoCDISpecDirs, info.CDISpecDirs))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|