Browse code

plugins: support for devices

Signed-off-by: Tibor Vass <tibor@docker.com>

Tibor Vass authored on 2016/11/17 09:18:43
Showing 7 changed files
... ...
@@ -1313,8 +1313,6 @@ definitions:
1313 1313
           - Network
1314 1314
           - Linux
1315 1315
           - Mounts
1316
-          - Devices
1317
-          - DeviceCreation
1318 1316
           - Env
1319 1317
           - Args
1320 1318
         properties:
... ...
@@ -1365,23 +1363,23 @@ definitions:
1365 1365
           Linux:
1366 1366
             type: "object"
1367 1367
             x-nullable: false
1368
-            required: [Capabilities]
1368
+            required: [Capabilities, DeviceCreation, Devices]
1369 1369
             properties:
1370 1370
               Capabilities:
1371 1371
                 type: "array"
1372 1372
                 items:
1373 1373
                   type: "string"
1374
+              DeviceCreation:
1375
+                type: "boolean"
1376
+                x-nullable: false
1377
+              Devices:
1378
+                type: "array"
1379
+                items:
1380
+                  $ref: "#/definitions/PluginDevice"
1374 1381
           Mounts:
1375 1382
             type: "array"
1376 1383
             items:
1377 1384
               $ref: "#/definitions/PluginMount"
1378
-          Devices:
1379
-            type: "array"
1380
-            items:
1381
-              $ref: "#/definitions/PluginDevice"
1382
-          DeviceCreation:
1383
-            type: "boolean"
1384
-            x-nullable: false
1385 1385
           Env:
1386 1386
             type: "array"
1387 1387
             items:
... ...
@@ -43,14 +43,6 @@ type PluginConfig struct {
43 43
 	// Required: true
44 44
 	Description string `json:"Description"`
45 45
 
46
-	// device creation
47
-	// Required: true
48
-	DeviceCreation bool `json:"DeviceCreation"`
49
-
50
-	// devices
51
-	// Required: true
52
-	Devices []PluginDevice `json:"Devices"`
53
-
54 46
 	// documentation
55 47
 	// Required: true
56 48
 	Documentation string `json:"Documentation"`
... ...
@@ -128,6 +120,14 @@ type PluginConfigLinux struct {
128 128
 	// capabilities
129 129
 	// Required: true
130 130
 	Capabilities []string `json:"Capabilities"`
131
+
132
+	// device creation
133
+	// Required: true
134
+	DeviceCreation bool `json:"DeviceCreation"`
135
+
136
+	// devices
137
+	// Required: true
138
+	Devices []PluginDevice `json:"Devices"`
131 139
 }
132 140
 
133 141
 // PluginConfigNetwork plugin config network
... ...
@@ -8,13 +8,11 @@ import (
8 8
 	"os"
9 9
 	"path/filepath"
10 10
 	"strconv"
11
-	"strings"
12 11
 	"syscall"
13 12
 	"time"
14 13
 
15 14
 	"github.com/Sirupsen/logrus"
16 15
 	"github.com/cloudflare/cfssl/log"
17
-	containertypes "github.com/docker/docker/api/types/container"
18 16
 	"github.com/docker/docker/container"
19 17
 	"github.com/docker/docker/daemon/links"
20 18
 	"github.com/docker/docker/pkg/idtools"
... ...
@@ -22,16 +20,10 @@ import (
22 22
 	"github.com/docker/docker/pkg/stringid"
23 23
 	"github.com/docker/docker/runconfig"
24 24
 	"github.com/docker/libnetwork"
25
-	"github.com/opencontainers/runc/libcontainer/configs"
26
-	"github.com/opencontainers/runc/libcontainer/devices"
27 25
 	"github.com/opencontainers/runc/libcontainer/label"
28
-	"github.com/opencontainers/runtime-spec/specs-go"
29 26
 	"github.com/pkg/errors"
30 27
 )
31 28
 
32
-func u32Ptr(i int64) *uint32     { u := uint32(i); return &u }
33
-func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm }
34
-
35 29
 func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]string, error) {
36 30
 	var env []string
37 31
 	children := daemon.children(container)
... ...
@@ -247,78 +239,6 @@ func killProcessDirectly(container *container.Container) error {
247 247
 	return nil
248 248
 }
249 249
 
250
-func specDevice(d *configs.Device) specs.Device {
251
-	return specs.Device{
252
-		Type:     string(d.Type),
253
-		Path:     d.Path,
254
-		Major:    d.Major,
255
-		Minor:    d.Minor,
256
-		FileMode: fmPtr(int64(d.FileMode)),
257
-		UID:      u32Ptr(int64(d.Uid)),
258
-		GID:      u32Ptr(int64(d.Gid)),
259
-	}
260
-}
261
-
262
-func specDeviceCgroup(d *configs.Device) specs.DeviceCgroup {
263
-	t := string(d.Type)
264
-	return specs.DeviceCgroup{
265
-		Allow:  true,
266
-		Type:   &t,
267
-		Major:  &d.Major,
268
-		Minor:  &d.Minor,
269
-		Access: &d.Permissions,
270
-	}
271
-}
272
-
273
-func getDevicesFromPath(deviceMapping containertypes.DeviceMapping) (devs []specs.Device, devPermissions []specs.DeviceCgroup, err error) {
274
-	resolvedPathOnHost := deviceMapping.PathOnHost
275
-
276
-	// check if it is a symbolic link
277
-	if src, e := os.Lstat(deviceMapping.PathOnHost); e == nil && src.Mode()&os.ModeSymlink == os.ModeSymlink {
278
-		if linkedPathOnHost, e := filepath.EvalSymlinks(deviceMapping.PathOnHost); e == nil {
279
-			resolvedPathOnHost = linkedPathOnHost
280
-		}
281
-	}
282
-
283
-	device, err := devices.DeviceFromPath(resolvedPathOnHost, deviceMapping.CgroupPermissions)
284
-	// if there was no error, return the device
285
-	if err == nil {
286
-		device.Path = deviceMapping.PathInContainer
287
-		return append(devs, specDevice(device)), append(devPermissions, specDeviceCgroup(device)), nil
288
-	}
289
-
290
-	// if the device is not a device node
291
-	// try to see if it's a directory holding many devices
292
-	if err == devices.ErrNotADevice {
293
-
294
-		// check if it is a directory
295
-		if src, e := os.Stat(resolvedPathOnHost); e == nil && src.IsDir() {
296
-
297
-			// mount the internal devices recursively
298
-			filepath.Walk(resolvedPathOnHost, func(dpath string, f os.FileInfo, e error) error {
299
-				childDevice, e := devices.DeviceFromPath(dpath, deviceMapping.CgroupPermissions)
300
-				if e != nil {
301
-					// ignore the device
302
-					return nil
303
-				}
304
-
305
-				// add the device to userSpecified devices
306
-				childDevice.Path = strings.Replace(dpath, resolvedPathOnHost, deviceMapping.PathInContainer, 1)
307
-				devs = append(devs, specDevice(childDevice))
308
-				devPermissions = append(devPermissions, specDeviceCgroup(childDevice))
309
-
310
-				return nil
311
-			})
312
-		}
313
-	}
314
-
315
-	if len(devs) > 0 {
316
-		return devs, devPermissions, nil
317
-	}
318
-
319
-	return devs, devPermissions, fmt.Errorf("error gathering device information while adding custom device %q: %s", deviceMapping.PathOnHost, err)
320
-}
321
-
322 250
 func detachMounted(path string) error {
323 251
 	return syscall.Unmount(path, syscall.MNT_DETACH)
324 252
 }
... ...
@@ -88,7 +88,7 @@ func setDevices(s *specs.Spec, c *container.Container) error {
88 88
 			return err
89 89
 		}
90 90
 		for _, d := range hostDevices {
91
-			devs = append(devs, specDevice(d))
91
+			devs = append(devs, oci.Device(d))
92 92
 		}
93 93
 		rwm := "rwm"
94 94
 		devPermissions = []specs.DeviceCgroup{
... ...
@@ -99,7 +99,7 @@ func setDevices(s *specs.Spec, c *container.Container) error {
99 99
 		}
100 100
 	} else {
101 101
 		for _, deviceMapping := range c.HostConfig.Devices {
102
-			d, dPermissions, err := getDevicesFromPath(deviceMapping)
102
+			d, dPermissions, err := oci.DevicesFromPath(deviceMapping.PathOnHost, deviceMapping.PathInContainer, deviceMapping.CgroupPermissions)
103 103
 			if err != nil {
104 104
 				return err
105 105
 			}
106 106
new file mode 100644
... ...
@@ -0,0 +1,86 @@
0
+package oci
1
+
2
+import (
3
+	"fmt"
4
+	"os"
5
+	"path/filepath"
6
+	"strings"
7
+
8
+	"github.com/opencontainers/runc/libcontainer/configs"
9
+	"github.com/opencontainers/runc/libcontainer/devices"
10
+	specs "github.com/opencontainers/runtime-spec/specs-go"
11
+)
12
+
13
+// Device transforms a libcontainer configs.Device to a specs.Device object.
14
+func Device(d *configs.Device) specs.Device {
15
+	return specs.Device{
16
+		Type:     string(d.Type),
17
+		Path:     d.Path,
18
+		Major:    d.Major,
19
+		Minor:    d.Minor,
20
+		FileMode: fmPtr(int64(d.FileMode)),
21
+		UID:      u32Ptr(int64(d.Uid)),
22
+		GID:      u32Ptr(int64(d.Gid)),
23
+	}
24
+}
25
+
26
+func deviceCgroup(d *configs.Device) specs.DeviceCgroup {
27
+	t := string(d.Type)
28
+	return specs.DeviceCgroup{
29
+		Allow:  true,
30
+		Type:   &t,
31
+		Major:  &d.Major,
32
+		Minor:  &d.Minor,
33
+		Access: &d.Permissions,
34
+	}
35
+}
36
+
37
+// DevicesFromPath computes a list of devices and device permissions from paths (pathOnHost and pathInContainer) and cgroup permissions.
38
+func DevicesFromPath(pathOnHost, pathInContainer, cgroupPermissions string) (devs []specs.Device, devPermissions []specs.DeviceCgroup, err error) {
39
+	resolvedPathOnHost := pathOnHost
40
+
41
+	// check if it is a symbolic link
42
+	if src, e := os.Lstat(pathOnHost); e == nil && src.Mode()&os.ModeSymlink == os.ModeSymlink {
43
+		if linkedPathOnHost, e := filepath.EvalSymlinks(pathOnHost); e == nil {
44
+			resolvedPathOnHost = linkedPathOnHost
45
+		}
46
+	}
47
+
48
+	device, err := devices.DeviceFromPath(resolvedPathOnHost, cgroupPermissions)
49
+	// if there was no error, return the device
50
+	if err == nil {
51
+		device.Path = pathInContainer
52
+		return append(devs, Device(device)), append(devPermissions, deviceCgroup(device)), nil
53
+	}
54
+
55
+	// if the device is not a device node
56
+	// try to see if it's a directory holding many devices
57
+	if err == devices.ErrNotADevice {
58
+
59
+		// check if it is a directory
60
+		if src, e := os.Stat(resolvedPathOnHost); e == nil && src.IsDir() {
61
+
62
+			// mount the internal devices recursively
63
+			filepath.Walk(resolvedPathOnHost, func(dpath string, f os.FileInfo, e error) error {
64
+				childDevice, e := devices.DeviceFromPath(dpath, cgroupPermissions)
65
+				if e != nil {
66
+					// ignore the device
67
+					return nil
68
+				}
69
+
70
+				// add the device to userSpecified devices
71
+				childDevice.Path = strings.Replace(dpath, resolvedPathOnHost, pathInContainer, 1)
72
+				devs = append(devs, Device(childDevice))
73
+				devPermissions = append(devPermissions, deviceCgroup(childDevice))
74
+
75
+				return nil
76
+			})
77
+		}
78
+	}
79
+
80
+	if len(devs) > 0 {
81
+		return devs, devPermissions, nil
82
+	}
83
+
84
+	return devs, devPermissions, fmt.Errorf("error gathering device information while adding custom device %q: %s", pathOnHost, err)
85
+}
0 86
new file mode 100644
... ...
@@ -0,0 +1,20 @@
0
+// +build !linux
1
+
2
+package oci
3
+
4
+import (
5
+	"errors"
6
+
7
+	"github.com/opencontainers/runc/libcontainer/configs"
8
+	specs "github.com/opencontainers/runtime-spec/specs-go"
9
+)
10
+
11
+// Device transforms a libcontainer configs.Device to a specs.Device object.
12
+// Not implemented
13
+func Device(d *configs.Device) specs.Device { return specs.Device{} }
14
+
15
+// DevicesFromPath computes a list of devices and device permissions from paths (pathOnHost and pathInContainer) and cgroup permissions.
16
+// Not implemented
17
+func DevicesFromPath(pathOnHost, pathInContainer, cgroupPermissions string) (devs []specs.Device, devPermissions []specs.DeviceCgroup, err error) {
18
+	return nil, nil, errors.New("oci/devices: unsupported platform")
19
+}
... ...
@@ -9,6 +9,7 @@ import (
9 9
 	"sync"
10 10
 
11 11
 	"github.com/docker/docker/api/types"
12
+	"github.com/docker/docker/oci"
12 13
 	"github.com/docker/docker/pkg/plugins"
13 14
 	"github.com/docker/docker/pkg/system"
14 15
 	specs "github.com/opencontainers/runtime-spec/specs-go"
... ...
@@ -103,6 +104,8 @@ func (p *Plugin) InitPlugin() error {
103 103
 		p.PluginObj.Settings.Mounts[i] = mount
104 104
 	}
105 105
 	p.PluginObj.Settings.Env = make([]string, 0, len(p.PluginObj.Config.Env))
106
+	p.PluginObj.Settings.Devices = make([]types.PluginDevice, 0, len(p.PluginObj.Config.Linux.Devices))
107
+	copy(p.PluginObj.Settings.Devices, p.PluginObj.Config.Linux.Devices)
106 108
 	for _, env := range p.PluginObj.Config.Env {
107 109
 		if env.Value != nil {
108 110
 			p.PluginObj.Settings.Env = append(p.PluginObj.Settings.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value))
... ...
@@ -175,7 +178,7 @@ next:
175 175
 		}
176 176
 
177 177
 		// range over all the devices in the config
178
-		for _, device := range p.PluginObj.Config.Devices {
178
+		for _, device := range p.PluginObj.Config.Linux.Devices {
179 179
 			// found the device in the config
180 180
 			if device.Name == s.name {
181 181
 				// is it settable ?
... ...
@@ -233,7 +236,7 @@ func (p *Plugin) ComputePrivileges() types.PluginPrivileges {
233 233
 			})
234 234
 		}
235 235
 	}
236
-	for _, device := range m.Devices {
236
+	for _, device := range m.Linux.Devices {
237 237
 		if device.Path != nil {
238 238
 			privileges = append(privileges, types.PluginPrivilege{
239 239
 				Name:        "device",
... ...
@@ -242,7 +245,7 @@ func (p *Plugin) ComputePrivileges() types.PluginPrivileges {
242 242
 			})
243 243
 		}
244 244
 	}
245
-	if m.DeviceCreation {
245
+	if m.Linux.DeviceCreation {
246 246
 		privileges = append(privileges, types.PluginPrivilege{
247 247
 			Name:        "device-creation",
248 248
 			Description: "",
... ...
@@ -299,12 +302,12 @@ func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
299 299
 		Readonly: false, // TODO: all plugins should be readonly? settable in config?
300 300
 	}
301 301
 
302
-	if p.PluginObj.Config.DeviceCreation {
303
-		rwm := "rwm"
304
-		s.Linux.Resources.Devices = []specs.DeviceCgroup{{Allow: true, Access: &rwm}}
302
+	userMounts := make(map[string]struct{}, len(p.PluginObj.Config.Mounts))
303
+	for _, m := range p.PluginObj.Config.Mounts {
304
+		userMounts[m.Destination] = struct{}{}
305 305
 	}
306 306
 
307
-	mounts := append(p.PluginObj.Settings.Mounts, types.PluginMount{
307
+	mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{
308 308
 		Source:      &p.RuntimeSourcePath,
309 309
 		Destination: defaultPluginRuntimeDestination,
310 310
 		Type:        "bind",
... ...
@@ -363,9 +366,25 @@ func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
363 363
 	}
364 364
 
365 365
 	for i, m := range s.Mounts {
366
-		if strings.HasPrefix(m.Destination, "/dev/") && true { // TODO: && user specified /dev
367
-			s.Mounts = append(s.Mounts[:i], s.Mounts[i+1:]...)
366
+		if strings.HasPrefix(m.Destination, "/dev/") {
367
+			if _, ok := userMounts[m.Destination]; ok {
368
+				s.Mounts = append(s.Mounts[:i], s.Mounts[i+1:]...)
369
+			}
370
+		}
371
+	}
372
+
373
+	if p.PluginObj.Config.Linux.DeviceCreation {
374
+		rwm := "rwm"
375
+		s.Linux.Resources.Devices = []specs.DeviceCgroup{{Allow: true, Access: &rwm}}
376
+	}
377
+	for _, dev := range p.PluginObj.Config.Linux.Devices {
378
+		path := *dev.Path
379
+		d, dPermissions, err := oci.DevicesFromPath(path, path, "rwm")
380
+		if err != nil {
381
+			return nil, err
368 382
 		}
383
+		s.Linux.Devices = append(s.Linux.Devices, d...)
384
+		s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, dPermissions...)
369 385
 	}
370 386
 
371 387
 	envs := make([]string, 1, len(p.PluginObj.Settings.Env)+1)