From 48038347d75d720593bb50d90cf03158149ffa41 Mon Sep 17 00:00:00 2001 From: Evan Lezar Date: Wed, 18 Jun 2025 17:55:52 +0200 Subject: [PATCH] Match device driver on name and ignore capabilities This change ignores requested capabilities when a driver is explicitly requested. This simplifies the logic for selecting a driver and means that users need not spefify redundant capabilities. With the exception of the catch-all "gpu" capability the remaining capabilities are only relevant for the "nvidia" driver. Signed-off-by: Evan Lezar --- api/swagger.yaml | 11 +++++++++++ daemon/devices.go | 38 +++++++++++++++++++++++--------------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/api/swagger.yaml b/api/swagger.yaml index cce4252e28..07fda39064 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -293,6 +293,11 @@ definitions: description: "A request for devices to be sent to device drivers" properties: Driver: + description: | + The name of the device driver to use for this request. + + Note that if this is specified the capabilities are ignored when + selecting a device driver. type: "string" example: "nvidia" Count: @@ -309,6 +314,12 @@ definitions: Capabilities: description: | A list of capabilities; an OR list of AND lists of capabilities. + + Note that if a driver is specified the capabilities have no effect on + selecting a driver as the driver name is used directly. + + Note that if no driver is specified the capabilities are used to + select a driver with the required capabilities. type: "array" items: type: "array" diff --git a/daemon/devices.go b/daemon/devices.go index 89c19b4fc2..5a09a189d8 100644 --- a/daemon/devices.go +++ b/daemon/devices.go @@ -3,6 +3,7 @@ package daemon import ( "context" + "github.com/containerd/log" "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/system" "github.com/moby/moby/v2/daemon/config" @@ -39,26 +40,33 @@ func registerDeviceDriver(name string, d *deviceDriver) { func (daemon *Daemon) handleDevice(req container.DeviceRequest, spec *specs.Spec) error { if req.Driver == "" { - for _, dd := range deviceDrivers { + // If no driver is explicitly requested, we iterate over the registered + // drivers and attempt to match on capabilities. + for driver, dd := range deviceDrivers { if selected := dd.capset.Match(req.Capabilities); selected != nil { + log.G(context.TODO()).WithFields(log.Fields{ + "driver": driver, + "capabilities": map[string]any{ + "requested": req.Capabilities, + "selected": selected, + }, + }).Debugf("Selecting device driver by capabilities") return dd.updateSpec(spec, &deviceInstance{req: req, selectedCaps: selected}) } } } else if dd := deviceDrivers[req.Driver]; dd != nil { - // We add a special case for the CDI driver here as the cdi driver does - // not distinguish between capabilities. - // Furthermore, the "OR" and "AND" matching logic for the capability - // sets requires that a dummy capability be specified when constructing a - // DeviceRequest. - // This workaround can be removed once these device driver are - // refactored to be plugins, with each driver implementing its own - // matching logic, for example. - if req.Driver == "cdi" { - return dd.updateSpec(spec, &deviceInstance{req: req}) - } - if selected := dd.capset.Match(req.Capabilities); selected != nil { - return dd.updateSpec(spec, &deviceInstance{req: req, selectedCaps: selected}) - } + selected := dd.capset.Match(req.Capabilities) + // If a driver is explicitly requested and registered, then we use the + // specified driver, ignoring the capabilities. + log.G(context.TODO()).WithFields(log.Fields{ + "driver": req.Driver, + "capabilities": map[string]any{ + "requested": req.Capabilities, + "selected": selected, + }, + }).Debugf("Selecting device driver by driver name; possibly ignoring capabilities") + return dd.updateSpec(spec, &deviceInstance{req: req, selectedCaps: selected}) } + return incompatibleDeviceRequest{req.Driver, req.Capabilities} }