Explorar el Código

plugins: support for devices

Signed-off-by: Tibor Vass <tibor@docker.com>
Tibor Vass hace 8 años
padre
commit
53b9b99e5c

+ 8 - 10
api/swagger.yaml

@@ -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:

+ 8 - 8
api/types/plugin.go

@@ -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

+ 0 - 80
daemon/container_operations_unix.go

@@ -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)
 }

+ 2 - 2
daemon/oci_linux.go

@@ -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 - 0
oci/devices_linux.go

@@ -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 - 0
oci/devices_unsupported.go

@@ -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")
+}

+ 28 - 9
plugin/v2/plugin.go

@@ -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,9 +366,25 @@ 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)