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  }