1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

plugins: support for devices

Signed-off-by: Tibor Vass <tibor@docker.com>
This commit is contained in:
Tibor Vass 2016-11-16 16:18:43 -08:00
parent e2e7ab655f
commit 53b9b99e5c
7 changed files with 152 additions and 109 deletions

View file

@ -1313,8 +1313,6 @@ definitions:
- Network - Network
- Linux - Linux
- Mounts - Mounts
- Devices
- DeviceCreation
- Env - Env
- Args - Args
properties: properties:
@ -1365,23 +1363,23 @@ definitions:
Linux: Linux:
type: "object" type: "object"
x-nullable: false x-nullable: false
required: [Capabilities] required: [Capabilities, DeviceCreation, Devices]
properties: properties:
Capabilities: Capabilities:
type: "array" type: "array"
items: items:
type: "string" type: "string"
Mounts: DeviceCreation:
type: "array" type: "boolean"
items: x-nullable: false
$ref: "#/definitions/PluginMount"
Devices: Devices:
type: "array" type: "array"
items: items:
$ref: "#/definitions/PluginDevice" $ref: "#/definitions/PluginDevice"
DeviceCreation: Mounts:
type: "boolean" type: "array"
x-nullable: false items:
$ref: "#/definitions/PluginMount"
Env: Env:
type: "array" type: "array"
items: items:

View file

@ -43,14 +43,6 @@ type PluginConfig struct {
// Required: true // Required: true
Description string `json:"Description"` Description string `json:"Description"`
// device creation
// Required: true
DeviceCreation bool `json:"DeviceCreation"`
// devices
// Required: true
Devices []PluginDevice `json:"Devices"`
// documentation // documentation
// Required: true // Required: true
Documentation string `json:"Documentation"` Documentation string `json:"Documentation"`
@ -128,6 +120,14 @@ type PluginConfigLinux struct {
// capabilities // capabilities
// Required: true // Required: true
Capabilities []string `json:"Capabilities"` Capabilities []string `json:"Capabilities"`
// device creation
// Required: true
DeviceCreation bool `json:"DeviceCreation"`
// devices
// Required: true
Devices []PluginDevice `json:"Devices"`
} }
// PluginConfigNetwork plugin config network // PluginConfigNetwork plugin config network

View file

@ -8,13 +8,11 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings"
"syscall" "syscall"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/cloudflare/cfssl/log" "github.com/cloudflare/cfssl/log"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/container" "github.com/docker/docker/container"
"github.com/docker/docker/daemon/links" "github.com/docker/docker/daemon/links"
"github.com/docker/docker/pkg/idtools" "github.com/docker/docker/pkg/idtools"
@ -22,16 +20,10 @@ import (
"github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/runconfig" "github.com/docker/docker/runconfig"
"github.com/docker/libnetwork" "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/runc/libcontainer/label"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors" "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) { func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]string, error) {
var env []string var env []string
children := daemon.children(container) children := daemon.children(container)
@ -247,78 +239,6 @@ func killProcessDirectly(container *container.Container) error {
return nil 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 { func detachMounted(path string) error {
return syscall.Unmount(path, syscall.MNT_DETACH) return syscall.Unmount(path, syscall.MNT_DETACH)
} }

View file

@ -88,7 +88,7 @@ func setDevices(s *specs.Spec, c *container.Container) error {
return err return err
} }
for _, d := range hostDevices { for _, d := range hostDevices {
devs = append(devs, specDevice(d)) devs = append(devs, oci.Device(d))
} }
rwm := "rwm" rwm := "rwm"
devPermissions = []specs.DeviceCgroup{ devPermissions = []specs.DeviceCgroup{
@ -99,7 +99,7 @@ func setDevices(s *specs.Spec, c *container.Container) error {
} }
} else { } else {
for _, deviceMapping := range c.HostConfig.Devices { 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 { if err != nil {
return err return err
} }

86
oci/devices_linux.go Normal file
View 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)
}

View 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")
}

View file

@ -9,6 +9,7 @@ import (
"sync" "sync"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/oci"
"github.com/docker/docker/pkg/plugins" "github.com/docker/docker/pkg/plugins"
"github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/system"
specs "github.com/opencontainers/runtime-spec/specs-go" 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.Mounts[i] = mount
} }
p.PluginObj.Settings.Env = make([]string, 0, len(p.PluginObj.Config.Env)) 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 { for _, env := range p.PluginObj.Config.Env {
if env.Value != nil { if env.Value != nil {
p.PluginObj.Settings.Env = append(p.PluginObj.Settings.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value)) 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 // 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 // found the device in the config
if device.Name == s.name { if device.Name == s.name {
// is it settable ? // 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 { if device.Path != nil {
privileges = append(privileges, types.PluginPrivilege{ privileges = append(privileges, types.PluginPrivilege{
Name: "device", Name: "device",
@ -242,7 +245,7 @@ func (p *Plugin) ComputePrivileges() types.PluginPrivileges {
}) })
} }
} }
if m.DeviceCreation { if m.Linux.DeviceCreation {
privileges = append(privileges, types.PluginPrivilege{ privileges = append(privileges, types.PluginPrivilege{
Name: "device-creation", Name: "device-creation",
Description: "", 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? Readonly: false, // TODO: all plugins should be readonly? settable in config?
} }
if p.PluginObj.Config.DeviceCreation { userMounts := make(map[string]struct{}, len(p.PluginObj.Config.Mounts))
rwm := "rwm" for _, m := range p.PluginObj.Config.Mounts {
s.Linux.Resources.Devices = []specs.DeviceCgroup{{Allow: true, Access: &rwm}} userMounts[m.Destination] = struct{}{}
} }
mounts := append(p.PluginObj.Settings.Mounts, types.PluginMount{ mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{
Source: &p.RuntimeSourcePath, Source: &p.RuntimeSourcePath,
Destination: defaultPluginRuntimeDestination, Destination: defaultPluginRuntimeDestination,
Type: "bind", Type: "bind",
@ -363,10 +366,26 @@ func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
} }
for i, m := range s.Mounts { for i, m := range s.Mounts {
if strings.HasPrefix(m.Destination, "/dev/") && true { // TODO: && user specified /dev if strings.HasPrefix(m.Destination, "/dev/") {
if _, ok := userMounts[m.Destination]; ok {
s.Mounts = append(s.Mounts[:i], s.Mounts[i+1:]...) 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 := make([]string, 1, len(p.PluginObj.Settings.Env)+1)
envs[0] = "PATH=" + system.DefaultPathEnv envs[0] = "PATH=" + system.DefaultPathEnv