Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ Usage of generic-device-plugin:
A "count" can be specified to allow a discovered device group to be scheduled multiple times.
For example, to permit allocation of the FUSE device 10 times: {"name": "fuse", "groups": [{"count": 10, "paths": [{"path": "/dev/fuse"}]}]}
Note: if omitted, "count" is assumed to be 1
An "optional" field can be specified for individual paths to allow containers to start even when some devices are missing.
For example, to expose serial devices that may or may not be present: {"name": "serial", "groups": [{"paths": [{"path": "/dev/ttyS0", "optional": true}, {"path": "/dev/ttyUSB0", "optional": true}]}]}
If mountPath is a directory, the device will be mounted to the directory with the name of the device.
For example, to expose the serial devices to the /dev/serial directory: {"name": "serial", "groups": [{"paths": [{"path": "/dev/ttyUSB*", "mountPath": "/dev/serial/"}]}]}
--domain string The domain to use when when declaring devices. (default "squat.ai")
Expand Down
2 changes: 2 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ For example, to expose a CH340 serial converter: {"name": "ch340", "groups": [{"
A "count" can be specified to allow a discovered device group to be scheduled multiple times.
For example, to permit allocation of the FUSE device 10 times: {"name": "fuse", "groups": [{"count": 10, "paths": [{"path": "/dev/fuse"}]}]}
Note: if omitted, "count" is assumed to be 1
An "optional" field can be specified for individual paths to allow containers to start even when some devices are missing.
For example, to expose serial devices that may or may not be present: {"name": "serial", "groups": [{"paths": [{"path": "/dev/ttyS0", "optional": true}, {"path": "/dev/ttyUSB0", "optional": true}]}]}
If mountPath is a directory, the device will be mounted to the directory with the name of the device.
For example, to expose the serial devices to the /dev/serial directory: {"name": "serial", "groups": [{"paths": [{"path": "/dev/ttyUSB*", "mountPath": "/dev/serial/"}]}]}`)
flag.String("plugin-directory", v1beta1.DevicePluginPath, "The directory in which to create plugin sockets.")
Expand Down
21 changes: 19 additions & 2 deletions deviceplugin/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"crypto/sha1"
"fmt"
"io/fs"
"math"
"path/filepath"
"sort"
"strconv"
Expand Down Expand Up @@ -53,6 +54,11 @@ type Path struct {
// then the group will provide 5 pairs of devices.
// When unspecified, Limit defaults to 1.
Limit uint `json:"limit,omitempty"`
// Optional specifies whether this device path is optional.
// When true, if the device path does not exist, it will be ignored instead of causing an error.
// This allows containers to start even when some devices are not present on the system.
// When unspecified, Optional defaults to false.
Optional bool `json:"optional,omitempty"`
}

// PathType represents the kinds of file-system nodes that can be scheduled.
Expand All @@ -71,19 +77,26 @@ func (gp *GenericPlugin) discoverPath() ([]device, error) {
for _, group := range gp.ds.Groups {
paths := make([][]string, len(group.Paths))
var length int
var limitLength int
limitLength := math.MaxInt
// Track which paths have matches (used for optional paths).
pathHasMatches := make([]bool, len(group.Paths))
// Discover all the devices matching each pattern in the Paths group.
for i, path := range group.Paths {
matches, err := fs.Glob(gp.fs, path.Path)
if err != nil {
return nil, err
}
// If no matches found and path is optional, skip it.
if len(matches) == 0 && path.Optional {
continue
}
pathHasMatches[i] = true
sort.Strings(matches)
for j := uint(0); j < path.Limit; j++ {
paths[i] = append(paths[i], matches...)
}
// Keep track of the shortest reusable length in the group.
if i == 0 || len(paths[i]) < limitLength {
if len(paths[i]) < limitLength {
limitLength = len(paths[i])
}
// Keep track of the greatest natural length in the group.
Expand All @@ -105,6 +118,10 @@ func (gp *GenericPlugin) discoverPath() ([]device, error) {
},
}
for k, path := range group.Paths {
// Skip paths that had no matches (optional and missing).
if !pathHasMatches[k] {
continue
}
mountPath = path.MountPath
if mountPath == "" {
mountPath = paths[k][i]
Expand Down
109 changes: 109 additions & 0 deletions deviceplugin/path_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,115 @@ func TestDiscoverPaths(t *testing.T) {
},
err: nil,
},
{
name: "optional paths - some missing",
ds: &DeviceSpec{
Name: "serial",
Groups: []*Group{
{
Paths: []*Path{
{
Path: "/dev/ttyS0",
Optional: true,
},
{
Path: "/dev/ttyUSB0",
Optional: true,
},
{
Path: "/dev/ttyUSB1",
Optional: true,
},
},
},
},
},
fs: fstest.MapFS{
"dev/ttyUSB0": {},
},
out: []device{
{
deviceSpecs: []*v1beta1.DeviceSpec{
{
ContainerPath: "/dev/ttyUSB0",
HostPath: "/dev/ttyUSB0",
},
},
},
},
err: nil,
},
{
name: "optional paths - all present",
ds: &DeviceSpec{
Name: "serial",
Groups: []*Group{
{
Paths: []*Path{
{
Path: "/dev/ttyS0",
Optional: true,
},
{
Path: "/dev/ttyUSB0",
Optional: true,
},
{
Path: "/dev/ttyUSB1",
Optional: true,
},
},
},
},
},
fs: fstest.MapFS{
"dev/ttyS0": {},
"dev/ttyUSB0": {},
"dev/ttyUSB1": {},
},
out: []device{
{
deviceSpecs: []*v1beta1.DeviceSpec{
{
ContainerPath: "/dev/ttyS0",
HostPath: "/dev/ttyS0",
},
{
ContainerPath: "/dev/ttyUSB0",
HostPath: "/dev/ttyUSB0",
},
{
ContainerPath: "/dev/ttyUSB1",
HostPath: "/dev/ttyUSB1",
},
},
},
},
err: nil,
},
{
name: "optional paths - all missing",
ds: &DeviceSpec{
Name: "serial",
Groups: []*Group{
{
Paths: []*Path{
{
Path: "/dev/ttyS0",
Optional: true,
},
{
Path: "/dev/ttyUSB0",
Optional: true,
},
},
},
},
},
fs: fstest.MapFS{},
out: []device{},
err: nil,
},
} {
t.Run(tc.name, func(t *testing.T) {
tc.ds.Default()
Expand Down
Loading