plugins: support for devices
Signed-off-by: Tibor Vass <tibor@docker.com>
This commit is contained in:
parent
e2e7ab655f
commit
53b9b99e5c
7 changed files with 152 additions and 109 deletions
|
@ -1313,8 +1313,6 @@ definitions:
|
|||
- Network
|
||||
- Linux
|
||||
- Mounts
|
||||
- Devices
|
||||
- DeviceCreation
|
||||
- Env
|
||||
- Args
|
||||
properties:
|
||||
|
@ -1365,23 +1363,23 @@ definitions:
|
|||
Linux:
|
||||
type: "object"
|
||||
x-nullable: false
|
||||
required: [Capabilities]
|
||||
required: [Capabilities, DeviceCreation, Devices]
|
||||
properties:
|
||||
Capabilities:
|
||||
type: "array"
|
||||
items:
|
||||
type: "string"
|
||||
DeviceCreation:
|
||||
type: "boolean"
|
||||
x-nullable: false
|
||||
Devices:
|
||||
type: "array"
|
||||
items:
|
||||
$ref: "#/definitions/PluginDevice"
|
||||
Mounts:
|
||||
type: "array"
|
||||
items:
|
||||
$ref: "#/definitions/PluginMount"
|
||||
Devices:
|
||||
type: "array"
|
||||
items:
|
||||
$ref: "#/definitions/PluginDevice"
|
||||
DeviceCreation:
|
||||
type: "boolean"
|
||||
x-nullable: false
|
||||
Env:
|
||||
type: "array"
|
||||
items:
|
||||
|
|
|
@ -43,14 +43,6 @@ type PluginConfig struct {
|
|||
// Required: true
|
||||
Description string `json:"Description"`
|
||||
|
||||
// device creation
|
||||
// Required: true
|
||||
DeviceCreation bool `json:"DeviceCreation"`
|
||||
|
||||
// devices
|
||||
// Required: true
|
||||
Devices []PluginDevice `json:"Devices"`
|
||||
|
||||
// documentation
|
||||
// Required: true
|
||||
Documentation string `json:"Documentation"`
|
||||
|
@ -128,6 +120,14 @@ type PluginConfigLinux struct {
|
|||
// capabilities
|
||||
// Required: true
|
||||
Capabilities []string `json:"Capabilities"`
|
||||
|
||||
// device creation
|
||||
// Required: true
|
||||
DeviceCreation bool `json:"DeviceCreation"`
|
||||
|
||||
// devices
|
||||
// Required: true
|
||||
Devices []PluginDevice `json:"Devices"`
|
||||
}
|
||||
|
||||
// PluginConfigNetwork plugin config network
|
||||
|
|
|
@ -8,13 +8,11 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/cloudflare/cfssl/log"
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/container"
|
||||
"github.com/docker/docker/daemon/links"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
|
@ -22,16 +20,10 @@ import (
|
|||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/libnetwork"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
"github.com/opencontainers/runc/libcontainer/devices"
|
||||
"github.com/opencontainers/runc/libcontainer/label"
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func u32Ptr(i int64) *uint32 { u := uint32(i); return &u }
|
||||
func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm }
|
||||
|
||||
func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]string, error) {
|
||||
var env []string
|
||||
children := daemon.children(container)
|
||||
|
@ -247,78 +239,6 @@ func killProcessDirectly(container *container.Container) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func specDevice(d *configs.Device) specs.Device {
|
||||
return specs.Device{
|
||||
Type: string(d.Type),
|
||||
Path: d.Path,
|
||||
Major: d.Major,
|
||||
Minor: d.Minor,
|
||||
FileMode: fmPtr(int64(d.FileMode)),
|
||||
UID: u32Ptr(int64(d.Uid)),
|
||||
GID: u32Ptr(int64(d.Gid)),
|
||||
}
|
||||
}
|
||||
|
||||
func specDeviceCgroup(d *configs.Device) specs.DeviceCgroup {
|
||||
t := string(d.Type)
|
||||
return specs.DeviceCgroup{
|
||||
Allow: true,
|
||||
Type: &t,
|
||||
Major: &d.Major,
|
||||
Minor: &d.Minor,
|
||||
Access: &d.Permissions,
|
||||
}
|
||||
}
|
||||
|
||||
func getDevicesFromPath(deviceMapping containertypes.DeviceMapping) (devs []specs.Device, devPermissions []specs.DeviceCgroup, err error) {
|
||||
resolvedPathOnHost := deviceMapping.PathOnHost
|
||||
|
||||
// check if it is a symbolic link
|
||||
if src, e := os.Lstat(deviceMapping.PathOnHost); e == nil && src.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
if linkedPathOnHost, e := filepath.EvalSymlinks(deviceMapping.PathOnHost); e == nil {
|
||||
resolvedPathOnHost = linkedPathOnHost
|
||||
}
|
||||
}
|
||||
|
||||
device, err := devices.DeviceFromPath(resolvedPathOnHost, deviceMapping.CgroupPermissions)
|
||||
// if there was no error, return the device
|
||||
if err == nil {
|
||||
device.Path = deviceMapping.PathInContainer
|
||||
return append(devs, specDevice(device)), append(devPermissions, specDeviceCgroup(device)), nil
|
||||
}
|
||||
|
||||
// if the device is not a device node
|
||||
// try to see if it's a directory holding many devices
|
||||
if err == devices.ErrNotADevice {
|
||||
|
||||
// check if it is a directory
|
||||
if src, e := os.Stat(resolvedPathOnHost); e == nil && src.IsDir() {
|
||||
|
||||
// mount the internal devices recursively
|
||||
filepath.Walk(resolvedPathOnHost, func(dpath string, f os.FileInfo, e error) error {
|
||||
childDevice, e := devices.DeviceFromPath(dpath, deviceMapping.CgroupPermissions)
|
||||
if e != nil {
|
||||
// ignore the device
|
||||
return nil
|
||||
}
|
||||
|
||||
// add the device to userSpecified devices
|
||||
childDevice.Path = strings.Replace(dpath, resolvedPathOnHost, deviceMapping.PathInContainer, 1)
|
||||
devs = append(devs, specDevice(childDevice))
|
||||
devPermissions = append(devPermissions, specDeviceCgroup(childDevice))
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(devs) > 0 {
|
||||
return devs, devPermissions, nil
|
||||
}
|
||||
|
||||
return devs, devPermissions, fmt.Errorf("error gathering device information while adding custom device %q: %s", deviceMapping.PathOnHost, err)
|
||||
}
|
||||
|
||||
func detachMounted(path string) error {
|
||||
return syscall.Unmount(path, syscall.MNT_DETACH)
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ func setDevices(s *specs.Spec, c *container.Container) error {
|
|||
return err
|
||||
}
|
||||
for _, d := range hostDevices {
|
||||
devs = append(devs, specDevice(d))
|
||||
devs = append(devs, oci.Device(d))
|
||||
}
|
||||
rwm := "rwm"
|
||||
devPermissions = []specs.DeviceCgroup{
|
||||
|
@ -99,7 +99,7 @@ func setDevices(s *specs.Spec, c *container.Container) error {
|
|||
}
|
||||
} else {
|
||||
for _, deviceMapping := range c.HostConfig.Devices {
|
||||
d, dPermissions, err := getDevicesFromPath(deviceMapping)
|
||||
d, dPermissions, err := oci.DevicesFromPath(deviceMapping.PathOnHost, deviceMapping.PathInContainer, deviceMapping.CgroupPermissions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
86
oci/devices_linux.go
Normal file
86
oci/devices_linux.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
package oci
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
"github.com/opencontainers/runc/libcontainer/devices"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
// Device transforms a libcontainer configs.Device to a specs.Device object.
|
||||
func Device(d *configs.Device) specs.Device {
|
||||
return specs.Device{
|
||||
Type: string(d.Type),
|
||||
Path: d.Path,
|
||||
Major: d.Major,
|
||||
Minor: d.Minor,
|
||||
FileMode: fmPtr(int64(d.FileMode)),
|
||||
UID: u32Ptr(int64(d.Uid)),
|
||||
GID: u32Ptr(int64(d.Gid)),
|
||||
}
|
||||
}
|
||||
|
||||
func deviceCgroup(d *configs.Device) specs.DeviceCgroup {
|
||||
t := string(d.Type)
|
||||
return specs.DeviceCgroup{
|
||||
Allow: true,
|
||||
Type: &t,
|
||||
Major: &d.Major,
|
||||
Minor: &d.Minor,
|
||||
Access: &d.Permissions,
|
||||
}
|
||||
}
|
||||
|
||||
// DevicesFromPath computes a list of devices and device permissions from paths (pathOnHost and pathInContainer) and cgroup permissions.
|
||||
func DevicesFromPath(pathOnHost, pathInContainer, cgroupPermissions string) (devs []specs.Device, devPermissions []specs.DeviceCgroup, err error) {
|
||||
resolvedPathOnHost := pathOnHost
|
||||
|
||||
// check if it is a symbolic link
|
||||
if src, e := os.Lstat(pathOnHost); e == nil && src.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
if linkedPathOnHost, e := filepath.EvalSymlinks(pathOnHost); e == nil {
|
||||
resolvedPathOnHost = linkedPathOnHost
|
||||
}
|
||||
}
|
||||
|
||||
device, err := devices.DeviceFromPath(resolvedPathOnHost, cgroupPermissions)
|
||||
// if there was no error, return the device
|
||||
if err == nil {
|
||||
device.Path = pathInContainer
|
||||
return append(devs, Device(device)), append(devPermissions, deviceCgroup(device)), nil
|
||||
}
|
||||
|
||||
// if the device is not a device node
|
||||
// try to see if it's a directory holding many devices
|
||||
if err == devices.ErrNotADevice {
|
||||
|
||||
// check if it is a directory
|
||||
if src, e := os.Stat(resolvedPathOnHost); e == nil && src.IsDir() {
|
||||
|
||||
// mount the internal devices recursively
|
||||
filepath.Walk(resolvedPathOnHost, func(dpath string, f os.FileInfo, e error) error {
|
||||
childDevice, e := devices.DeviceFromPath(dpath, cgroupPermissions)
|
||||
if e != nil {
|
||||
// ignore the device
|
||||
return nil
|
||||
}
|
||||
|
||||
// add the device to userSpecified devices
|
||||
childDevice.Path = strings.Replace(dpath, resolvedPathOnHost, pathInContainer, 1)
|
||||
devs = append(devs, Device(childDevice))
|
||||
devPermissions = append(devPermissions, deviceCgroup(childDevice))
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(devs) > 0 {
|
||||
return devs, devPermissions, nil
|
||||
}
|
||||
|
||||
return devs, devPermissions, fmt.Errorf("error gathering device information while adding custom device %q: %s", pathOnHost, err)
|
||||
}
|
20
oci/devices_unsupported.go
Normal file
20
oci/devices_unsupported.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
// +build !linux
|
||||
|
||||
package oci
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
// Device transforms a libcontainer configs.Device to a specs.Device object.
|
||||
// Not implemented
|
||||
func Device(d *configs.Device) specs.Device { return specs.Device{} }
|
||||
|
||||
// DevicesFromPath computes a list of devices and device permissions from paths (pathOnHost and pathInContainer) and cgroup permissions.
|
||||
// Not implemented
|
||||
func DevicesFromPath(pathOnHost, pathInContainer, cgroupPermissions string) (devs []specs.Device, devPermissions []specs.DeviceCgroup, err error) {
|
||||
return nil, nil, errors.New("oci/devices: unsupported platform")
|
||||
}
|
|
@ -9,6 +9,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/oci"
|
||||
"github.com/docker/docker/pkg/plugins"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
|
@ -103,6 +104,8 @@ func (p *Plugin) InitPlugin() error {
|
|||
p.PluginObj.Settings.Mounts[i] = mount
|
||||
}
|
||||
p.PluginObj.Settings.Env = make([]string, 0, len(p.PluginObj.Config.Env))
|
||||
p.PluginObj.Settings.Devices = make([]types.PluginDevice, 0, len(p.PluginObj.Config.Linux.Devices))
|
||||
copy(p.PluginObj.Settings.Devices, p.PluginObj.Config.Linux.Devices)
|
||||
for _, env := range p.PluginObj.Config.Env {
|
||||
if env.Value != nil {
|
||||
p.PluginObj.Settings.Env = append(p.PluginObj.Settings.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value))
|
||||
|
@ -175,7 +178,7 @@ next:
|
|||
}
|
||||
|
||||
// range over all the devices in the config
|
||||
for _, device := range p.PluginObj.Config.Devices {
|
||||
for _, device := range p.PluginObj.Config.Linux.Devices {
|
||||
// found the device in the config
|
||||
if device.Name == s.name {
|
||||
// is it settable ?
|
||||
|
@ -233,7 +236,7 @@ func (p *Plugin) ComputePrivileges() types.PluginPrivileges {
|
|||
})
|
||||
}
|
||||
}
|
||||
for _, device := range m.Devices {
|
||||
for _, device := range m.Linux.Devices {
|
||||
if device.Path != nil {
|
||||
privileges = append(privileges, types.PluginPrivilege{
|
||||
Name: "device",
|
||||
|
@ -242,7 +245,7 @@ func (p *Plugin) ComputePrivileges() types.PluginPrivileges {
|
|||
})
|
||||
}
|
||||
}
|
||||
if m.DeviceCreation {
|
||||
if m.Linux.DeviceCreation {
|
||||
privileges = append(privileges, types.PluginPrivilege{
|
||||
Name: "device-creation",
|
||||
Description: "",
|
||||
|
@ -299,12 +302,12 @@ func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
|
|||
Readonly: false, // TODO: all plugins should be readonly? settable in config?
|
||||
}
|
||||
|
||||
if p.PluginObj.Config.DeviceCreation {
|
||||
rwm := "rwm"
|
||||
s.Linux.Resources.Devices = []specs.DeviceCgroup{{Allow: true, Access: &rwm}}
|
||||
userMounts := make(map[string]struct{}, len(p.PluginObj.Config.Mounts))
|
||||
for _, m := range p.PluginObj.Config.Mounts {
|
||||
userMounts[m.Destination] = struct{}{}
|
||||
}
|
||||
|
||||
mounts := append(p.PluginObj.Settings.Mounts, types.PluginMount{
|
||||
mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{
|
||||
Source: &p.RuntimeSourcePath,
|
||||
Destination: defaultPluginRuntimeDestination,
|
||||
Type: "bind",
|
||||
|
@ -363,11 +366,27 @@ func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
|
|||
}
|
||||
|
||||
for i, m := range s.Mounts {
|
||||
if strings.HasPrefix(m.Destination, "/dev/") && true { // TODO: && user specified /dev
|
||||
s.Mounts = append(s.Mounts[:i], s.Mounts[i+1:]...)
|
||||
if strings.HasPrefix(m.Destination, "/dev/") {
|
||||
if _, ok := userMounts[m.Destination]; ok {
|
||||
s.Mounts = append(s.Mounts[:i], s.Mounts[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if p.PluginObj.Config.Linux.DeviceCreation {
|
||||
rwm := "rwm"
|
||||
s.Linux.Resources.Devices = []specs.DeviceCgroup{{Allow: true, Access: &rwm}}
|
||||
}
|
||||
for _, dev := range p.PluginObj.Config.Linux.Devices {
|
||||
path := *dev.Path
|
||||
d, dPermissions, err := oci.DevicesFromPath(path, path, "rwm")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Linux.Devices = append(s.Linux.Devices, d...)
|
||||
s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, dPermissions...)
|
||||
}
|
||||
|
||||
envs := make([]string, 1, len(p.PluginObj.Settings.Env)+1)
|
||||
envs[0] = "PATH=" + system.DefaultPathEnv
|
||||
envs = append(envs, p.PluginObj.Settings.Env...)
|
||||
|
|
Loading…
Reference in a new issue