瀏覽代碼

Add CDISpecDirs to Info output

This change adds the configured CDI spec directories to the
system info output.

Signed-off-by: Evan Lezar <elezar@nvidia.com>
Evan Lezar 2 年之前
父節點
當前提交
7a59913b1a
共有 5 個文件被更改,包括 135 次插入5 次删除
  1. 18 0
      api/swagger.yaml
  2. 1 0
      api/types/system/info.go
  3. 10 0
      daemon/info.go
  4. 9 5
      docs/api/version-history.md
  5. 97 0
      integration/container/cdi_test.go

+ 18 - 0
api/swagger.yaml

@@ -5301,7 +5301,25 @@ definitions:
           - "WARNING: No memory limit support"
           - "WARNING: bridge-nf-call-iptables is disabled"
           - "WARNING: bridge-nf-call-ip6tables is disabled"
+      CDISpecDirs:
+        description: |
+          List of directories where (Container Device Interface) CDI
+          specifications are located.
+
+          These specifications define vendor-specific modifications to an OCI
+          runtime specification for a container being created.
 
+          An empty list indicates that CDI device injection is disabled.
+
+          Note that since using CDI device injection requires the daemon to have
+          experimental enabled. For non-experimental daemons an empty list will
+          always be returned.
+        type: "array"
+        items:
+          type: "string"
+        example:
+          - "/etc/cdi"
+          - "/var/run/cdi"
 
   # PluginsInfo is a temp struct holding Plugins name
   # registered with docker daemon. It is used by Info struct

+ 1 - 0
api/types/system/info.go

@@ -73,6 +73,7 @@ type Info struct {
 	SecurityOptions     []string
 	ProductLicense      string               `json:",omitempty"`
 	DefaultAddressPools []NetworkAddressPool `json:",omitempty"`
+	CDISpecDirs         []string
 
 	// Legacy API fields for older API versions.
 	legacyFields

+ 10 - 0
daemon/info.go

@@ -62,6 +62,7 @@ func (daemon *Daemon) SystemInfo() *system.Info {
 		NoProxy:            getConfigOrEnv(cfg.NoProxy, "NO_PROXY", "no_proxy"),
 		LiveRestoreEnabled: cfg.LiveRestoreEnabled,
 		Isolation:          daemon.defaultIsolation,
+		CDISpecDirs:        promoteNil(cfg.CDISpecDirs),
 	}
 
 	daemon.fillContainerStates(v)
@@ -309,3 +310,12 @@ func getConfigOrEnv(config string, env ...string) string {
 	}
 	return getEnvAny(env...)
 }
+
+// promoteNil converts a nil slice to an empty slice of that type.
+// A non-nil slice is returned as is.
+func promoteNil[S ~[]E, E any](s S) S {
+	if s == nil {
+		return S{}
+	}
+	return s
+}

+ 9 - 5
docs/api/version-history.md

@@ -34,6 +34,10 @@ keywords: "API, Docker, rcli, REST, documentation"
   `BindOptions.ReadOnlyNonRecursive` and `BindOptions.ReadOnlyForceRecursive` to customize the behavior.
 * `POST /containers/create` now accepts a `HealthConfig.StartInterval` to set the
   interval for health checks during the start period.
+* `GET /info` now includes a `CDISpecDirs` field indicating the configured CDI
+  specifications directories. The use of the applied setting requires the daemon
+  to have expermental enabled, and for non-experimental daemons an empty list is
+  always returned.
 
 ## v1.43 API changes
 
@@ -103,7 +107,7 @@ keywords: "API, Docker, rcli, REST, documentation"
   a default.
 
   This change is not versioned, and affects all API versions if the daemon has
-  this patch. 
+  this patch.
 * `GET /_ping` and `HEAD /_ping` now return a `Swarm` header, which allows a
   client to detect if Swarm is enabled on the daemon, without having to call
   additional endpoints.
@@ -126,7 +130,7 @@ keywords: "API, Docker, rcli, REST, documentation"
   versioned, and affects all API versions if the daemon has this patch.
 * `GET /containers/{id}/attach`, `GET /exec/{id}/start`, `GET /containers/{id}/logs`
   `GET /services/{id}/logs` and `GET /tasks/{id}/logs` now set Content-Type header
-  to `application/vnd.docker.multiplexed-stream` when a multiplexed stdout/stderr 
+  to `application/vnd.docker.multiplexed-stream` when a multiplexed stdout/stderr
   stream is sent to client, `application/vnd.docker.raw-stream` otherwise.
 * `POST /volumes/create` now accepts a new `ClusterVolumeSpec` to create a cluster
   volume (CNI). This option can only be used if the daemon is a Swarm manager.
@@ -139,7 +143,7 @@ keywords: "API, Docker, rcli, REST, documentation"
 * Volume information returned by `GET /volumes/{name}`, `GET /volumes` and
   `GET /system/df` can now contain a `ClusterVolume` if the volume is a cluster
   volume (requires the daemon to be a Swarm manager).
-* The `Volume` type, as returned by `Added new `ClusterVolume` fields 
+* The `Volume` type, as returned by `Added new `ClusterVolume` fields
 * Added a new `PUT /volumes{name}` endpoint to update cluster volumes (CNI).
   Cluster volumes are only supported if the daemon is a Swarm manager.
 * `GET /containers/{name}/attach/ws` endpoint now accepts `stdin`, `stdout` and
@@ -355,7 +359,7 @@ keywords: "API, Docker, rcli, REST, documentation"
 
 [Docker Engine API v1.36](https://docs.docker.com/engine/api/v1.36/) documentation
 
-* `Get /events` now return `exec_die` event when an exec process terminates.  
+* `Get /events` now return `exec_die` event when an exec process terminates.
 
 
 ## v1.35 API changes
@@ -563,7 +567,7 @@ keywords: "API, Docker, rcli, REST, documentation"
 * `POST /services/create` and `POST /services/(id or name)/update` now accept the `TTY` parameter, which allocate a pseudo-TTY in container.
 * `POST /services/create` and `POST /services/(id or name)/update` now accept the `DNSConfig` parameter, which specifies DNS related configurations in resolver configuration file (resolv.conf) through `Nameservers`, `Search`, and `Options`.
 * `POST /services/create` and `POST /services/(id or name)/update` now support
-  `node.platform.arch` and `node.platform.os` constraints in the services 
+  `node.platform.arch` and `node.platform.os` constraints in the services
   `TaskSpec.Placement.Constraints` field.
 * `GET /networks/(id or name)` now includes IP and name of all peers nodes for swarm mode overlay networks.
 * `GET /plugins` list plugins.

+ 97 - 0
integration/container/cdi_test.go

@@ -3,6 +3,7 @@ package container // import "github.com/docker/docker/integration/container"
 import (
 	"bytes"
 	"context"
+	"encoding/json"
 	"io"
 	"os"
 	"path/filepath"
@@ -62,3 +63,99 @@ func TestCreateWithCDIDevices(t *testing.T) {
 	outlines := strings.Split(actualStdout.String(), "\n")
 	assert.Assert(t, is.Contains(outlines, "FOO=injected"))
 }
+
+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{}
+		experimental            bool
+		specDirs                []string
+		expectedInfoCDISpecDirs []string
+	}{
+		{
+			description:             "experimental no spec dirs specified returns default",
+			experimental:            true,
+			specDirs:                nil,
+			expectedInfoCDISpecDirs: []string{"/etc/cdi", "/var/run/cdi"},
+		},
+		{
+			description:             "experimental specified spec dirs are returned",
+			experimental:            true,
+			specDirs:                []string{"/foo/bar", "/baz/qux"},
+			expectedInfoCDISpecDirs: []string{"/foo/bar", "/baz/qux"},
+		},
+		{
+			description:             "experimental empty string as spec dir returns empty slice",
+			experimental:            true,
+			specDirs:                []string{""},
+			expectedInfoCDISpecDirs: []string{},
+		},
+		{
+			description:             "experimental empty config option returns empty slice",
+			experimental:            true,
+			config:                  map[string]interface{}{"cdi-spec-dirs": []string{}},
+			expectedInfoCDISpecDirs: []string{},
+		},
+		{
+			description:             "non-experimental no spec dirs specified returns empty slice",
+			experimental:            false,
+			specDirs:                nil,
+			expectedInfoCDISpecDirs: []string{},
+		},
+		{
+			description:             "non-experimental specified spec dirs returns empty slice",
+			experimental:            false,
+			specDirs:                []string{"/foo/bar", "/baz/qux"},
+			expectedInfoCDISpecDirs: []string{},
+		},
+		{
+			description:             "non-experimental empty string as spec dir returns empty slice",
+			experimental:            false,
+			specDirs:                []string{""},
+			expectedInfoCDISpecDirs: []string{},
+		},
+		{
+			description:             "non-experimental empty config option returns empty slice",
+			experimental:            false,
+			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
+			if tc.experimental {
+				opts = append(opts, daemon.WithExperimental())
+			}
+			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))
+		})
+	}
+}