cdi_test.go 5.2 KB

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