Browse Source

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 years ago
parent
commit
7a59913b1a
5 changed files with 135 additions and 5 deletions
  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: No memory limit support"
           - "WARNING: bridge-nf-call-iptables is disabled"
           - "WARNING: bridge-nf-call-iptables is disabled"
           - "WARNING: bridge-nf-call-ip6tables 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
   # PluginsInfo is a temp struct holding Plugins name
   # registered with docker daemon. It is used by Info struct
   # 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
 	SecurityOptions     []string
 	ProductLicense      string               `json:",omitempty"`
 	ProductLicense      string               `json:",omitempty"`
 	DefaultAddressPools []NetworkAddressPool `json:",omitempty"`
 	DefaultAddressPools []NetworkAddressPool `json:",omitempty"`
+	CDISpecDirs         []string
 
 
 	// Legacy API fields for older API versions.
 	// Legacy API fields for older API versions.
 	legacyFields
 	legacyFields

+ 10 - 0
daemon/info.go

@@ -62,6 +62,7 @@ func (daemon *Daemon) SystemInfo() *system.Info {
 		NoProxy:            getConfigOrEnv(cfg.NoProxy, "NO_PROXY", "no_proxy"),
 		NoProxy:            getConfigOrEnv(cfg.NoProxy, "NO_PROXY", "no_proxy"),
 		LiveRestoreEnabled: cfg.LiveRestoreEnabled,
 		LiveRestoreEnabled: cfg.LiveRestoreEnabled,
 		Isolation:          daemon.defaultIsolation,
 		Isolation:          daemon.defaultIsolation,
+		CDISpecDirs:        promoteNil(cfg.CDISpecDirs),
 	}
 	}
 
 
 	daemon.fillContainerStates(v)
 	daemon.fillContainerStates(v)
@@ -309,3 +310,12 @@ func getConfigOrEnv(config string, env ...string) string {
 	}
 	}
 	return getEnvAny(env...)
 	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.
   `BindOptions.ReadOnlyNonRecursive` and `BindOptions.ReadOnlyForceRecursive` to customize the behavior.
 * `POST /containers/create` now accepts a `HealthConfig.StartInterval` to set the
 * `POST /containers/create` now accepts a `HealthConfig.StartInterval` to set the
   interval for health checks during the start period.
   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
 ## v1.43 API changes
 
 
@@ -103,7 +107,7 @@ keywords: "API, Docker, rcli, REST, documentation"
   a default.
   a default.
 
 
   This change is not versioned, and affects all API versions if the daemon has
   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
 * `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
   client to detect if Swarm is enabled on the daemon, without having to call
   additional endpoints.
   additional endpoints.
@@ -126,7 +130,7 @@ keywords: "API, Docker, rcli, REST, documentation"
   versioned, and affects all API versions if the daemon has this patch.
   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 /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
   `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.
   stream is sent to client, `application/vnd.docker.raw-stream` otherwise.
 * `POST /volumes/create` now accepts a new `ClusterVolumeSpec` to create a cluster
 * `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.
   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
 * Volume information returned by `GET /volumes/{name}`, `GET /volumes` and
   `GET /system/df` can now contain a `ClusterVolume` if the volume is a cluster
   `GET /system/df` can now contain a `ClusterVolume` if the volume is a cluster
   volume (requires the daemon to be a Swarm manager).
   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).
 * Added a new `PUT /volumes{name}` endpoint to update cluster volumes (CNI).
   Cluster volumes are only supported if the daemon is a Swarm manager.
   Cluster volumes are only supported if the daemon is a Swarm manager.
 * `GET /containers/{name}/attach/ws` endpoint now accepts `stdin`, `stdout` and
 * `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
 [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
 ## 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 `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 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
 * `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.
   `TaskSpec.Placement.Constraints` field.
 * `GET /networks/(id or name)` now includes IP and name of all peers nodes for swarm mode overlay networks.
 * `GET /networks/(id or name)` now includes IP and name of all peers nodes for swarm mode overlay networks.
 * `GET /plugins` list plugins.
 * `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 (
 import (
 	"bytes"
 	"bytes"
 	"context"
 	"context"
+	"encoding/json"
 	"io"
 	"io"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
@@ -62,3 +63,99 @@ func TestCreateWithCDIDevices(t *testing.T) {
 	outlines := strings.Split(actualStdout.String(), "\n")
 	outlines := strings.Split(actualStdout.String(), "\n")
 	assert.Assert(t, is.Contains(outlines, "FOO=injected"))
 	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))
+		})
+	}
+}