github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/integration/container/cdi_test.go (about) 1 package container // import "github.com/docker/docker/integration/container" 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "io" 7 "os" 8 "path/filepath" 9 "strings" 10 "testing" 11 12 containertypes "github.com/docker/docker/api/types/container" 13 "github.com/docker/docker/integration/internal/container" 14 "github.com/docker/docker/pkg/stdcopy" 15 "github.com/docker/docker/testutil" 16 "github.com/docker/docker/testutil/daemon" 17 "gotest.tools/v3/assert" 18 is "gotest.tools/v3/assert/cmp" 19 "gotest.tools/v3/skip" 20 ) 21 22 func TestCreateWithCDIDevices(t *testing.T) { 23 skip.If(t, testEnv.DaemonInfo.OSType != "linux", "CDI devices are only supported on Linux") 24 skip.If(t, testEnv.IsRemoteDaemon, "cannot run cdi tests with a remote daemon") 25 26 ctx := testutil.StartSpan(baseContext, t) 27 28 cwd, err := os.Getwd() 29 assert.NilError(t, err) 30 configPath := filepath.Join(cwd, "daemon.json") 31 err = os.WriteFile(configPath, []byte(`{"features": {"cdi": true}}`), 0o644) 32 defer os.Remove(configPath) 33 assert.NilError(t, err) 34 d := daemon.New(t) 35 d.StartWithBusybox(ctx, t, "--config-file", configPath, "--cdi-spec-dir="+filepath.Join(cwd, "testdata", "cdi")) 36 defer d.Stop(t) 37 38 apiClient := d.NewClientT(t) 39 40 id := container.Run(ctx, t, apiClient, 41 container.WithCmd("/bin/sh", "-c", "env"), 42 container.WithCDIDevices("vendor1.com/device=foo"), 43 ) 44 defer apiClient.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true}) 45 46 inspect, err := apiClient.ContainerInspect(ctx, id) 47 assert.NilError(t, err) 48 49 expectedRequests := []containertypes.DeviceRequest{ 50 { 51 Driver: "cdi", 52 DeviceIDs: []string{"vendor1.com/device=foo"}, 53 }, 54 } 55 assert.Check(t, is.DeepEqual(inspect.HostConfig.DeviceRequests, expectedRequests)) 56 57 reader, err := apiClient.ContainerLogs(ctx, id, containertypes.LogsOptions{ 58 ShowStdout: true, 59 }) 60 assert.NilError(t, err) 61 62 actualStdout := new(bytes.Buffer) 63 actualStderr := io.Discard 64 _, err = stdcopy.StdCopy(actualStdout, actualStderr, reader) 65 assert.NilError(t, err) 66 67 outlines := strings.Split(actualStdout.String(), "\n") 68 assert.Assert(t, is.Contains(outlines, "FOO=injected")) 69 } 70 71 func TestCDISpecDirsAreInSystemInfo(t *testing.T) { 72 skip.If(t, testEnv.DaemonInfo.OSType == "windows") // d.Start fails on Windows with `protocol not available` 73 // TODO: This restriction can be relaxed with https://github.com/moby/moby/pull/46158 74 skip.If(t, testEnv.IsRootless, "the t.TempDir test creates a folder with incorrect permissions for rootless") 75 76 testCases := []struct { 77 description string 78 config map[string]interface{} 79 specDirs []string 80 expectedInfoCDISpecDirs []string 81 }{ 82 { 83 description: "CDI enabled with no spec dirs specified returns default", 84 config: map[string]interface{}{"features": map[string]bool{"cdi": true}}, 85 specDirs: nil, 86 expectedInfoCDISpecDirs: []string{"/etc/cdi", "/var/run/cdi"}, 87 }, 88 { 89 description: "CDI enabled with specified spec dirs are returned", 90 config: map[string]interface{}{"features": map[string]bool{"cdi": true}}, 91 specDirs: []string{"/foo/bar", "/baz/qux"}, 92 expectedInfoCDISpecDirs: []string{"/foo/bar", "/baz/qux"}, 93 }, 94 { 95 description: "CDI enabled with empty string as spec dir returns empty slice", 96 config: map[string]interface{}{"features": map[string]bool{"cdi": true}}, 97 specDirs: []string{""}, 98 expectedInfoCDISpecDirs: []string{}, 99 }, 100 { 101 description: "CDI enabled with empty config option returns empty slice", 102 config: map[string]interface{}{"features": map[string]bool{"cdi": true}, "cdi-spec-dirs": []string{}}, 103 expectedInfoCDISpecDirs: []string{}, 104 }, 105 { 106 description: "CDI disabled with no spec dirs specified returns empty slice", 107 specDirs: nil, 108 expectedInfoCDISpecDirs: []string{}, 109 }, 110 { 111 description: "CDI disabled with specified spec dirs returns empty slice", 112 specDirs: []string{"/foo/bar", "/baz/qux"}, 113 expectedInfoCDISpecDirs: []string{}, 114 }, 115 { 116 description: "CDI disabled with empty string as spec dir returns empty slice", 117 specDirs: []string{""}, 118 expectedInfoCDISpecDirs: []string{}, 119 }, 120 { 121 description: "CDI disabled with empty config option returns empty slice", 122 config: map[string]interface{}{"cdi-spec-dirs": []string{}}, 123 expectedInfoCDISpecDirs: []string{}, 124 }, 125 } 126 127 for _, tc := range testCases { 128 t.Run(tc.description, func(t *testing.T) { 129 var opts []daemon.Option 130 d := daemon.New(t, opts...) 131 132 var args []string 133 for _, specDir := range tc.specDirs { 134 args = append(args, "--cdi-spec-dir="+specDir) 135 } 136 if tc.config != nil { 137 configPath := filepath.Join(t.TempDir(), "daemon.json") 138 139 configFile, err := os.Create(configPath) 140 assert.NilError(t, err) 141 defer configFile.Close() 142 143 err = json.NewEncoder(configFile).Encode(tc.config) 144 assert.NilError(t, err) 145 146 args = append(args, "--config-file="+configPath) 147 } 148 d.Start(t, args...) 149 defer d.Stop(t) 150 151 info := d.Info(t) 152 153 assert.Check(t, is.DeepEqual(tc.expectedInfoCDISpecDirs, info.CDISpecDirs)) 154 }) 155 } 156 }