Browse code

Merge pull request #6224 from tiborvass/storage-options

Add --storage-opt daemon option and some devicemapper option (with fixes)

Tibor Vass authored on 2014/06/06 08:00:03
Showing 20 changed files
... ...
@@ -780,7 +780,7 @@ func NewDaemonFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*D
780 780
 	graphdriver.DefaultDriver = config.GraphDriver
781 781
 
782 782
 	// Load storage driver
783
-	driver, err := graphdriver.New(config.Root)
783
+	driver, err := graphdriver.New(config.Root, config.GraphOptions)
784 784
 	if err != nil {
785 785
 		return nil, err
786 786
 	}
... ...
@@ -809,7 +809,7 @@ func NewDaemonFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*D
809 809
 
810 810
 	// We don't want to use a complex driver like aufs or devmapper
811 811
 	// for volumes, just a plain filesystem
812
-	volumesDriver, err := graphdriver.GetDriver("vfs", config.Root)
812
+	volumesDriver, err := graphdriver.GetDriver("vfs", config.Root, config.GraphOptions)
813 813
 	if err != nil {
814 814
 		return nil, err
815 815
 	}
... ...
@@ -57,7 +57,7 @@ type Driver struct {
57 57
 
58 58
 // New returns a new AUFS driver.
59 59
 // An error is returned if AUFS is not supported.
60
-func Init(root string) (graphdriver.Driver, error) {
60
+func Init(root string, options []string) (graphdriver.Driver, error) {
61 61
 	// Try to load the aufs kernel module
62 62
 	if err := supportsAufs(); err != nil {
63 63
 		return nil, graphdriver.ErrNotSupported
... ...
@@ -17,7 +17,7 @@ var (
17 17
 )
18 18
 
19 19
 func testInit(dir string, t *testing.T) graphdriver.Driver {
20
-	d, err := Init(dir)
20
+	d, err := Init(dir, nil)
21 21
 	if err != nil {
22 22
 		if err == graphdriver.ErrNotSupported {
23 23
 			t.Skip(err)
... ...
@@ -22,7 +22,7 @@ func init() {
22 22
 	graphdriver.Register("btrfs", Init)
23 23
 }
24 24
 
25
-func Init(home string) (graphdriver.Driver, error) {
25
+func Init(home string, options []string) (graphdriver.Driver, error) {
26 26
 	rootdir := path.Dir(home)
27 27
 
28 28
 	var buf syscall.Statfs_t
29 29
new file mode 100644
... ...
@@ -0,0 +1,143 @@
0
+## devicemapper - a storage backend based on Device Mapper
1
+
2
+### Theory of operation
3
+
4
+The device mapper graphdriver uses the device mapper thin provisioning
5
+module (dm-thinp) to implement CoW snapshots. For each devicemapper
6
+graph location (typically `/var/lib/docker/devicemapper`, $graph below)
7
+a thin pool is created based on two block devices, one for data and
8
+one for metadata.  By default these block devices are created
9
+automatically by using loopback mounts of automatically creates sparse
10
+files.
11
+
12
+The default loopback files used are `$graph/devicemapper/data` and
13
+`$graph/devicemapper/metadata`. Additional metadata required to map
14
+from docker entities to the corresponding devicemapper volumes is
15
+stored in the `$graph/devicemapper/json` file (encoded as Json).
16
+
17
+In order to support multiple devicemapper graphs on a system the thin
18
+pool will be named something like: `docker-0:33-19478248-pool`, where
19
+the `0:30` part is the minor/major device nr and `19478248` is the
20
+inode number of the $graph directory.
21
+
22
+On the thin pool docker automatically creates a base thin device,
23
+called something like `docker-0:33-19478248-base` of a fixed
24
+size. This is automatically formated on creation and contains just an
25
+empty filesystem. This device is the base of all docker images and
26
+containers. All base images are snapshots of this device and those
27
+images are then in turn used as snapshots for other images and
28
+eventually containers.
29
+
30
+### options
31
+
32
+The devicemapper backend supports some options that you can specify
33
+when starting the docker daemon using the --storage-opt flags.
34
+This uses the `dm` prefix and would be used somthing like `docker -d --storage-opt dm.foo=bar`.
35
+
36
+Here is the list of supported options:
37
+
38
+ *  `dm.basesize`
39
+
40
+    Specifies the size to use when creating the base device, which
41
+    limits the size of images and containers. The default value is
42
+    10G. Note, thin devices are inherently "sparse", so a 10G device
43
+    which is mostly empty doesn't use 10 GB of space on the
44
+    pool. However, the filesystem will use more space for the empty
45
+    case the larger the device is.
46
+
47
+    Example use:
48
+
49
+    ``docker -d --storage-opt dm.basesize=20G``
50
+
51
+ *  `dm.loopdatasize`
52
+
53
+    Specifies the size to use when creating the loopback file for the
54
+    "data" device which is used for the thin pool. The default size is
55
+    100G. Note that the file is sparse, so it will not initially take
56
+    up this much space.
57
+
58
+    Example use:
59
+
60
+    ``docker -d --storage-opt dm.loopdatasize=200G``
61
+
62
+ *  `dm.loopmetadatasize`
63
+
64
+    Specifies the size to use when creating the loopback file for the
65
+    "metadadata" device which is used for the thin pool. The default size is
66
+    2G. Note that the file is sparse, so it will not initially take
67
+    up this much space.
68
+
69
+    Example use:
70
+
71
+    ``docker -d --storage-opt dm.loopmetadatasize=4G``
72
+
73
+ *  `dm.fs`
74
+
75
+    Specifies the filesystem type to use for the base device. The supported
76
+    options are "ext4" and "xfs". The default is "ext4"
77
+
78
+    Example use:
79
+
80
+    ``docker -d --storage-opt dm.fs=xfs``
81
+
82
+ *  `dm.mkfsarg`
83
+
84
+    Specifies extra mkfs arguments to be used when creating the base device.
85
+
86
+    Example use:
87
+
88
+    ``docker -d --storage-opt "dm.mkfsarg=-O ^has_journal"``
89
+
90
+ *  `dm.mountopt`
91
+
92
+    Specifies extra mount options used when mounting the thin devices.
93
+
94
+    Example use:
95
+
96
+    ``docker -d --storage-opt dm.mountopt=nodiscard``
97
+
98
+ *  `dm.datadev`
99
+
100
+    Specifies a custom blockdevice to use for data for the thin pool.
101
+
102
+    If using a block device for device mapper storage, ideally both
103
+    datadev and metadatadev should be specified to completely avoid
104
+    using the loopback device.
105
+
106
+    Example use:
107
+
108
+    ``docker -d --storage-opt dm.datadev=/dev/sdb1 --storage-opt dm.metadatadev=/dev/sdc1``
109
+
110
+ *  `dm.metadatadev`
111
+
112
+    Specifies a custom blockdevice to use for metadata for the thin
113
+    pool.
114
+
115
+    For best performance the metadata should be on a different spindle
116
+    than the data, or even better on an SSD.
117
+
118
+    If setting up a new metadata pool it is required to be valid. This
119
+    can be achieved by zeroing the first 4k to indicate empty
120
+    metadata, like this:
121
+
122
+    ``dd if=/dev/zero of=$metadata_dev bs=4096 count=1```
123
+
124
+    Example use:
125
+
126
+    ``docker -d --storage-opt dm.datadev=/dev/sdb1 --storage-opt dm.metadatadev=/dev/sdc1``
127
+
128
+ *  `dm.blkdiscard`
129
+
130
+    Enables or disables the use of blkdiscard when removing
131
+    devicemapper devices. This is enabled by default (only) if using
132
+    loopback devices and is required to res-parsify the loopback file
133
+    on image/container removal.
134
+
135
+    Disabling this on loopback can lead to *much* faster container
136
+    removal times, but will make the space used in /var/lib/docker
137
+    directory not be returned to the system for other use when
138
+    containers are removed.
139
+
140
+    Example use:
141
+
142
+    ``docker -d --storage-opt dm.blkdiscard=false``
... ...
@@ -13,11 +13,14 @@ import (
13 13
 	"path"
14 14
 	"path/filepath"
15 15
 	"strconv"
16
+	"strings"
16 17
 	"sync"
17 18
 	"syscall"
18 19
 	"time"
19 20
 
21
+	"github.com/dotcloud/docker/daemon/graphdriver"
20 22
 	"github.com/dotcloud/docker/pkg/label"
23
+	"github.com/dotcloud/docker/pkg/units"
21 24
 	"github.com/dotcloud/docker/utils"
22 25
 )
23 26
 
... ...
@@ -64,6 +67,17 @@ type DeviceSet struct {
64 64
 	TransactionId    uint64
65 65
 	NewTransactionId uint64
66 66
 	nextDeviceId     int
67
+
68
+	// Options
69
+	dataLoopbackSize     int64
70
+	metaDataLoopbackSize int64
71
+	baseFsSize           uint64
72
+	filesystem           string
73
+	mountOptions         string
74
+	mkfsArgs             []string
75
+	dataDevice           string
76
+	metadataDevice       string
77
+	doBlkDiscard         bool
67 78
 }
68 79
 
69 80
 type DiskUsage struct {
... ...
@@ -273,26 +287,39 @@ func (devices *DeviceSet) activateDeviceIfNeeded(info *DevInfo) error {
273 273
 func (devices *DeviceSet) createFilesystem(info *DevInfo) error {
274 274
 	devname := info.DevName()
275 275
 
276
-	err := exec.Command("mkfs.ext4", "-E", "nodiscard,lazy_itable_init=0,lazy_journal_init=0", devname).Run()
277
-	if err != nil {
278
-		err = exec.Command("mkfs.ext4", "-E", "nodiscard,lazy_itable_init=0", devname).Run()
276
+	args := []string{}
277
+	for _, arg := range devices.mkfsArgs {
278
+		args = append(args, arg)
279
+	}
280
+
281
+	args = append(args, devname)
282
+
283
+	var err error
284
+	switch devices.filesystem {
285
+	case "xfs":
286
+		err = exec.Command("mkfs.xfs", args...).Run()
287
+	case "ext4":
288
+		err = exec.Command("mkfs.ext4", append([]string{"-E", "nodiscard,lazy_itable_init=0,lazy_journal_init=0"}, args...)...).Run()
289
+		if err != nil {
290
+			err = exec.Command("mkfs.ext4", append([]string{"-E", "nodiscard,lazy_itable_init=0"}, args...)...).Run()
291
+		}
292
+	default:
293
+		err = fmt.Errorf("Unsupported filesystem type %s", devices.filesystem)
279 294
 	}
280 295
 	if err != nil {
281
-		utils.Debugf("\n--->Err: %s\n", err)
282 296
 		return err
283 297
 	}
298
+
284 299
 	return nil
285 300
 }
286 301
 
287 302
 func (devices *DeviceSet) initMetaData() error {
288 303
 	_, _, _, params, err := getStatus(devices.getPoolName())
289 304
 	if err != nil {
290
-		utils.Debugf("\n--->Err: %s\n", err)
291 305
 		return err
292 306
 	}
293 307
 
294 308
 	if _, err := fmt.Sscanf(params, "%d", &devices.TransactionId); err != nil {
295
-		utils.Debugf("\n--->Err: %s\n", err)
296 309
 		return err
297 310
 	}
298 311
 	devices.NewTransactionId = devices.TransactionId
... ...
@@ -301,7 +328,6 @@ func (devices *DeviceSet) initMetaData() error {
301 301
 
302 302
 	jsonData, err := ioutil.ReadFile(devices.oldMetadataFile())
303 303
 	if err != nil && !os.IsNotExist(err) {
304
-		utils.Debugf("\n--->Err: %s\n", err)
305 304
 		return err
306 305
 	}
307 306
 
... ...
@@ -309,7 +335,6 @@ func (devices *DeviceSet) initMetaData() error {
309 309
 		m := MetaData{Devices: make(map[string]*DevInfo)}
310 310
 
311 311
 		if err := json.Unmarshal(jsonData, &m); err != nil {
312
-			utils.Debugf("\n--->Err: %s\n", err)
313 312
 			return err
314 313
 		}
315 314
 
... ...
@@ -359,7 +384,6 @@ func (devices *DeviceSet) setupBaseImage() error {
359 359
 	if oldInfo != nil && !oldInfo.Initialized {
360 360
 		utils.Debugf("Removing uninitialized base image")
361 361
 		if err := devices.deleteDevice(oldInfo); err != nil {
362
-			utils.Debugf("\n--->Err: %s\n", err)
363 362
 			return err
364 363
 		}
365 364
 	}
... ...
@@ -370,37 +394,32 @@ func (devices *DeviceSet) setupBaseImage() error {
370 370
 
371 371
 	// Create initial device
372 372
 	if err := createDevice(devices.getPoolDevName(), &id); err != nil {
373
-		utils.Debugf("\n--->Err: %s\n", err)
374 373
 		return err
375 374
 	}
376 375
 
377 376
 	// Ids are 24bit, so wrap around
378 377
 	devices.nextDeviceId = (id + 1) & 0xffffff
379 378
 
380
-	utils.Debugf("Registering base device (id %v) with FS size %v", id, DefaultBaseFsSize)
381
-	info, err := devices.registerDevice(id, "", DefaultBaseFsSize)
379
+	utils.Debugf("Registering base device (id %v) with FS size %v", id, devices.baseFsSize)
380
+	info, err := devices.registerDevice(id, "", devices.baseFsSize)
382 381
 	if err != nil {
383 382
 		_ = deleteDevice(devices.getPoolDevName(), id)
384
-		utils.Debugf("\n--->Err: %s\n", err)
385 383
 		return err
386 384
 	}
387 385
 
388 386
 	utils.Debugf("Creating filesystem on base device-manager snapshot")
389 387
 
390 388
 	if err = devices.activateDeviceIfNeeded(info); err != nil {
391
-		utils.Debugf("\n--->Err: %s\n", err)
392 389
 		return err
393 390
 	}
394 391
 
395 392
 	if err := devices.createFilesystem(info); err != nil {
396
-		utils.Debugf("\n--->Err: %s\n", err)
397 393
 		return err
398 394
 	}
399 395
 
400 396
 	info.Initialized = true
401 397
 	if err = devices.saveMetadata(info); err != nil {
402 398
 		info.Initialized = false
403
-		utils.Debugf("\n--->Err: %s\n", err)
404 399
 		return err
405 400
 	}
406 401
 
... ...
@@ -506,6 +525,12 @@ func (devices *DeviceSet) ResizePool(size int64) error {
506 506
 func (devices *DeviceSet) initDevmapper(doInit bool) error {
507 507
 	logInit(devices)
508 508
 
509
+	_, err := getDriverVersion()
510
+	if err != nil {
511
+		// Can't even get driver version, assume not supported
512
+		return graphdriver.ErrNotSupported
513
+	}
514
+
509 515
 	if err := os.MkdirAll(devices.metadataDir(), 0700); err != nil && !os.IsExist(err) {
510 516
 		return err
511 517
 	}
... ...
@@ -548,45 +573,74 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error {
548 548
 	if info.Exists == 0 {
549 549
 		utils.Debugf("Pool doesn't exist. Creating it.")
550 550
 
551
-		hasData := devices.hasImage("data")
552
-		hasMetadata := devices.hasImage("metadata")
551
+		var (
552
+			dataFile     *os.File
553
+			metadataFile *os.File
554
+		)
553 555
 
554
-		if !doInit && !hasData {
555
-			return errors.New("Loopback data file not found")
556
-		}
556
+		if devices.dataDevice == "" {
557
+			// Make sure the sparse images exist in <root>/devicemapper/data
557 558
 
558
-		if !doInit && !hasMetadata {
559
-			return errors.New("Loopback metadata file not found")
560
-		}
559
+			hasData := devices.hasImage("data")
561 560
 
562
-		createdLoopback = !hasData || !hasMetadata
563
-		data, err := devices.ensureImage("data", DefaultDataLoopbackSize)
564
-		if err != nil {
565
-			utils.Debugf("Error device ensureImage (data): %s\n", err)
566
-			return err
567
-		}
568
-		metadata, err := devices.ensureImage("metadata", DefaultMetaDataLoopbackSize)
569
-		if err != nil {
570
-			utils.Debugf("Error device ensureImage (metadata): %s\n", err)
571
-			return err
572
-		}
561
+			if !doInit && !hasData {
562
+				return errors.New("Loopback data file not found")
563
+			}
573 564
 
574
-		dataFile, err := attachLoopDevice(data)
575
-		if err != nil {
576
-			utils.Debugf("\n--->Err: %s\n", err)
577
-			return err
565
+			if !hasData {
566
+				createdLoopback = true
567
+			}
568
+
569
+			data, err := devices.ensureImage("data", devices.dataLoopbackSize)
570
+			if err != nil {
571
+				utils.Debugf("Error device ensureImage (data): %s\n", err)
572
+				return err
573
+			}
574
+
575
+			dataFile, err = attachLoopDevice(data)
576
+			if err != nil {
577
+				return err
578
+			}
579
+		} else {
580
+			dataFile, err = os.OpenFile(devices.dataDevice, os.O_RDWR, 0600)
581
+			if err != nil {
582
+				return err
583
+			}
578 584
 		}
579 585
 		defer dataFile.Close()
580 586
 
581
-		metadataFile, err := attachLoopDevice(metadata)
582
-		if err != nil {
583
-			utils.Debugf("\n--->Err: %s\n", err)
584
-			return err
587
+		if devices.metadataDevice == "" {
588
+			// Make sure the sparse images exist in <root>/devicemapper/metadata
589
+
590
+			hasMetadata := devices.hasImage("metadata")
591
+
592
+			if !doInit && !hasMetadata {
593
+				return errors.New("Loopback metadata file not found")
594
+			}
595
+
596
+			if !hasMetadata {
597
+				createdLoopback = true
598
+			}
599
+
600
+			metadata, err := devices.ensureImage("metadata", devices.metaDataLoopbackSize)
601
+			if err != nil {
602
+				utils.Debugf("Error device ensureImage (metadata): %s\n", err)
603
+				return err
604
+			}
605
+
606
+			metadataFile, err = attachLoopDevice(metadata)
607
+			if err != nil {
608
+				return err
609
+			}
610
+		} else {
611
+			metadataFile, err = os.OpenFile(devices.metadataDevice, os.O_RDWR, 0600)
612
+			if err != nil {
613
+				return err
614
+			}
585 615
 		}
586 616
 		defer metadataFile.Close()
587 617
 
588 618
 		if err := createPool(devices.getPoolName(), dataFile, metadataFile); err != nil {
589
-			utils.Debugf("\n--->Err: %s\n", err)
590 619
 			return err
591 620
 		}
592 621
 	}
... ...
@@ -595,7 +649,6 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error {
595 595
 	// load the transaction id and migrate old metadata
596 596
 	if !createdLoopback {
597 597
 		if err = devices.initMetaData(); err != nil {
598
-			utils.Debugf("\n--->Err: %s\n", err)
599 598
 			return err
600 599
 		}
601 600
 	}
... ...
@@ -646,12 +699,14 @@ func (devices *DeviceSet) AddDevice(hash, baseHash string) error {
646 646
 }
647 647
 
648 648
 func (devices *DeviceSet) deleteDevice(info *DevInfo) error {
649
-	// This is a workaround for the kernel not discarding block so
650
-	// on the thin pool when we remove a thinp device, so we do it
651
-	// manually
652
-	if err := devices.activateDeviceIfNeeded(info); err == nil {
653
-		if err := BlockDeviceDiscard(info.DevName()); err != nil {
654
-			utils.Debugf("Error discarding block on device: %s (ignoring)\n", err)
649
+	if devices.doBlkDiscard {
650
+		// This is a workaround for the kernel not discarding block so
651
+		// on the thin pool when we remove a thinp device, so we do it
652
+		// manually
653
+		if err := devices.activateDeviceIfNeeded(info); err == nil {
654
+			if err := BlockDeviceDiscard(info.DevName()); err != nil {
655
+				utils.Debugf("Error discarding block on device: %s (ignoring)\n", err)
656
+			}
655 657
 		}
656 658
 	}
657 659
 
... ...
@@ -705,7 +760,6 @@ func (devices *DeviceSet) deactivatePool() error {
705 705
 	devname := devices.getPoolDevName()
706 706
 	devinfo, err := getInfo(devname)
707 707
 	if err != nil {
708
-		utils.Debugf("\n--->Err: %s\n", err)
709 708
 		return err
710 709
 	}
711 710
 	if devinfo.Exists != 0 {
... ...
@@ -727,12 +781,10 @@ func (devices *DeviceSet) deactivateDevice(info *DevInfo) error {
727 727
 
728 728
 	devinfo, err := getInfo(info.Name())
729 729
 	if err != nil {
730
-		utils.Debugf("\n--->Err: %s\n", err)
731 730
 		return err
732 731
 	}
733 732
 	if devinfo.Exists != 0 {
734 733
 		if err := devices.removeDeviceAndWait(info.Name()); err != nil {
735
-			utils.Debugf("\n--->Err: %s\n", err)
736 734
 			return err
737 735
 		}
738 736
 	}
... ...
@@ -907,11 +959,24 @@ func (devices *DeviceSet) MountDevice(hash, path, mountLabel string) error {
907 907
 
908 908
 	var flags uintptr = syscall.MS_MGC_VAL
909 909
 
910
-	mountOptions := label.FormatMountLabel("discard", mountLabel)
911
-	err = syscall.Mount(info.DevName(), path, "ext4", flags, mountOptions)
910
+	fstype, err := ProbeFsType(info.DevName())
911
+	if err != nil {
912
+		return err
913
+	}
914
+
915
+	options := ""
916
+
917
+	if fstype == "xfs" {
918
+		// XFS needs nouuid or it can't mount filesystems with the same fs
919
+		options = joinMountOptions(options, "nouuid")
920
+	}
921
+
922
+	options = joinMountOptions(options, devices.mountOptions)
923
+	options = joinMountOptions(options, label.FormatMountLabel("", mountLabel))
924
+
925
+	err = syscall.Mount(info.DevName(), path, fstype, flags, joinMountOptions("discard", options))
912 926
 	if err != nil && err == syscall.EINVAL {
913
-		mountOptions = label.FormatMountLabel("", mountLabel)
914
-		err = syscall.Mount(info.DevName(), path, "ext4", flags, mountOptions)
927
+		err = syscall.Mount(info.DevName(), path, fstype, flags, options)
915 928
 	}
916 929
 	if err != nil {
917 930
 		return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err)
... ...
@@ -949,7 +1014,6 @@ func (devices *DeviceSet) UnmountDevice(hash string) error {
949 949
 
950 950
 	utils.Debugf("[devmapper] Unmount(%s)", info.mountPath)
951 951
 	if err := syscall.Unmount(info.mountPath, 0); err != nil {
952
-		utils.Debugf("\n--->Err: %s\n", err)
953 952
 		return err
954 953
 	}
955 954
 	utils.Debugf("[devmapper] Unmount done")
... ...
@@ -1084,12 +1148,72 @@ func (devices *DeviceSet) Status() *Status {
1084 1084
 	return status
1085 1085
 }
1086 1086
 
1087
-func NewDeviceSet(root string, doInit bool) (*DeviceSet, error) {
1087
+func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error) {
1088 1088
 	SetDevDir("/dev")
1089 1089
 
1090 1090
 	devices := &DeviceSet{
1091
-		root:     root,
1092
-		MetaData: MetaData{Devices: make(map[string]*DevInfo)},
1091
+		root:                 root,
1092
+		MetaData:             MetaData{Devices: make(map[string]*DevInfo)},
1093
+		dataLoopbackSize:     DefaultDataLoopbackSize,
1094
+		metaDataLoopbackSize: DefaultMetaDataLoopbackSize,
1095
+		baseFsSize:           DefaultBaseFsSize,
1096
+		filesystem:           "ext4",
1097
+		doBlkDiscard:         true,
1098
+	}
1099
+
1100
+	foundBlkDiscard := false
1101
+	for _, option := range options {
1102
+		key, val, err := utils.ParseKeyValueOpt(option)
1103
+		if err != nil {
1104
+			return nil, err
1105
+		}
1106
+		key = strings.ToLower(key)
1107
+		switch key {
1108
+		case "dm.basesize":
1109
+			size, err := units.FromHumanSize(val)
1110
+			if err != nil {
1111
+				return nil, err
1112
+			}
1113
+			devices.baseFsSize = uint64(size)
1114
+		case "dm.loopdatasize":
1115
+			size, err := units.FromHumanSize(val)
1116
+			if err != nil {
1117
+				return nil, err
1118
+			}
1119
+			devices.dataLoopbackSize = size
1120
+		case "dm.loopmetadatasize":
1121
+			size, err := units.FromHumanSize(val)
1122
+			if err != nil {
1123
+				return nil, err
1124
+			}
1125
+			devices.metaDataLoopbackSize = size
1126
+		case "dm.fs":
1127
+			if val != "ext4" && val != "xfs" {
1128
+				return nil, fmt.Errorf("Unsupported filesystem %s\n", val)
1129
+			}
1130
+			devices.filesystem = val
1131
+		case "dm.mkfsarg":
1132
+			devices.mkfsArgs = append(devices.mkfsArgs, val)
1133
+		case "dm.mountopt":
1134
+			devices.mountOptions = joinMountOptions(devices.mountOptions, val)
1135
+		case "dm.metadatadev":
1136
+			devices.metadataDevice = val
1137
+		case "dm.datadev":
1138
+			devices.dataDevice = val
1139
+		case "dm.blkdiscard":
1140
+			foundBlkDiscard = true
1141
+			devices.doBlkDiscard, err = strconv.ParseBool(val)
1142
+			if err != nil {
1143
+				return nil, err
1144
+			}
1145
+		default:
1146
+			return nil, fmt.Errorf("Unknown option %s\n", key)
1147
+		}
1148
+	}
1149
+
1150
+	// By default, don't do blk discard hack on raw devices, its rarely useful and is expensive
1151
+	if !foundBlkDiscard && devices.dataDevice != "" {
1152
+		devices.doBlkDiscard = false
1093 1153
 	}
1094 1154
 
1095 1155
 	if err := devices.initDevmapper(doInit); err != nil {
... ...
@@ -52,6 +52,7 @@ var (
52 52
 	ErrTaskAddTarget          = errors.New("dm_task_add_target failed")
53 53
 	ErrTaskSetSector          = errors.New("dm_task_set_sector failed")
54 54
 	ErrTaskGetInfo            = errors.New("dm_task_get_info failed")
55
+	ErrTaskGetDriverVersion   = errors.New("dm_task_get_driver_version failed")
55 56
 	ErrTaskSetCookie          = errors.New("dm_task_set_cookie failed")
56 57
 	ErrNilCookie              = errors.New("cookie ptr can't be nil")
57 58
 	ErrAttachLoopbackDevice   = errors.New("loopback mounting failed")
... ...
@@ -178,6 +179,14 @@ func (t *Task) GetInfo() (*Info, error) {
178 178
 	return info, nil
179 179
 }
180 180
 
181
+func (t *Task) GetDriverVersion() (string, error) {
182
+	res := DmTaskGetDriverVersion(t.unmanaged)
183
+	if res == "" {
184
+		return "", ErrTaskGetDriverVersion
185
+	}
186
+	return res, nil
187
+}
188
+
181 189
 func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64,
182 190
 	length uint64, targetType string, params string) {
183 191
 
... ...
@@ -394,6 +403,17 @@ func getInfo(name string) (*Info, error) {
394 394
 	return task.GetInfo()
395 395
 }
396 396
 
397
+func getDriverVersion() (string, error) {
398
+	task := TaskCreate(DeviceVersion)
399
+	if task == nil {
400
+		return "", fmt.Errorf("Can't create DeviceVersion task")
401
+	}
402
+	if err := task.Run(); err != nil {
403
+		return "", err
404
+	}
405
+	return task.GetDriverVersion()
406
+}
407
+
397 408
 func getStatus(name string) (uint64, uint64, string, string, error) {
398 409
 	task, err := createTask(DeviceStatus, name)
399 410
 	if task == nil {
... ...
@@ -85,23 +85,24 @@ const (
85 85
 )
86 86
 
87 87
 var (
88
-	DmGetLibraryVersion = dmGetLibraryVersionFct
89
-	DmGetNextTarget     = dmGetNextTargetFct
90
-	DmLogInitVerbose    = dmLogInitVerboseFct
91
-	DmSetDevDir         = dmSetDevDirFct
92
-	DmTaskAddTarget     = dmTaskAddTargetFct
93
-	DmTaskCreate        = dmTaskCreateFct
94
-	DmTaskDestroy       = dmTaskDestroyFct
95
-	DmTaskGetInfo       = dmTaskGetInfoFct
96
-	DmTaskRun           = dmTaskRunFct
97
-	DmTaskSetAddNode    = dmTaskSetAddNodeFct
98
-	DmTaskSetCookie     = dmTaskSetCookieFct
99
-	DmTaskSetMessage    = dmTaskSetMessageFct
100
-	DmTaskSetName       = dmTaskSetNameFct
101
-	DmTaskSetRo         = dmTaskSetRoFct
102
-	DmTaskSetSector     = dmTaskSetSectorFct
103
-	DmUdevWait          = dmUdevWaitFct
104
-	LogWithErrnoInit    = logWithErrnoInitFct
88
+	DmGetLibraryVersion    = dmGetLibraryVersionFct
89
+	DmGetNextTarget        = dmGetNextTargetFct
90
+	DmLogInitVerbose       = dmLogInitVerboseFct
91
+	DmSetDevDir            = dmSetDevDirFct
92
+	DmTaskAddTarget        = dmTaskAddTargetFct
93
+	DmTaskCreate           = dmTaskCreateFct
94
+	DmTaskDestroy          = dmTaskDestroyFct
95
+	DmTaskGetInfo          = dmTaskGetInfoFct
96
+	DmTaskGetDriverVersion = dmTaskGetDriverVersionFct
97
+	DmTaskRun              = dmTaskRunFct
98
+	DmTaskSetAddNode       = dmTaskSetAddNodeFct
99
+	DmTaskSetCookie        = dmTaskSetCookieFct
100
+	DmTaskSetMessage       = dmTaskSetMessageFct
101
+	DmTaskSetName          = dmTaskSetNameFct
102
+	DmTaskSetRo            = dmTaskSetRoFct
103
+	DmTaskSetSector        = dmTaskSetSectorFct
104
+	DmUdevWait             = dmUdevWaitFct
105
+	LogWithErrnoInit       = logWithErrnoInitFct
105 106
 )
106 107
 
107 108
 func free(p *C.char) {
... ...
@@ -184,6 +185,16 @@ func dmTaskGetInfoFct(task *CDmTask, info *Info) int {
184 184
 	return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo))
185 185
 }
186 186
 
187
+func dmTaskGetDriverVersionFct(task *CDmTask) string {
188
+	buffer := C.malloc(128)
189
+	defer C.free(buffer)
190
+	res := C.dm_task_get_driver_version((*C.struct_dm_task)(task), (*C.char)(buffer), 128)
191
+	if res == 0 {
192
+		return ""
193
+	}
194
+	return C.GoString((*C.char)(buffer))
195
+}
196
+
187 197
 func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
188 198
 	var (
189 199
 		Cstart, Clength      C.uint64_t
... ...
@@ -26,8 +26,8 @@ type Driver struct {
26 26
 	home string
27 27
 }
28 28
 
29
-func Init(home string) (graphdriver.Driver, error) {
30
-	deviceSet, err := NewDeviceSet(home, true)
29
+func Init(home string, options []string) (graphdriver.Driver, error) {
30
+	deviceSet, err := NewDeviceSet(home, true, options)
31 31
 	if err != nil {
32 32
 		return nil, err
33 33
 	}
... ...
@@ -3,6 +3,8 @@
3 3
 package devmapper
4 4
 
5 5
 import (
6
+	"bytes"
7
+	"fmt"
6 8
 	"os"
7 9
 	"path/filepath"
8 10
 	"syscall"
... ...
@@ -27,3 +29,58 @@ func Mounted(mountpoint string) (bool, error) {
27 27
 	parentSt := parent.Sys().(*syscall.Stat_t)
28 28
 	return mntpointSt.Dev != parentSt.Dev, nil
29 29
 }
30
+
31
+type probeData struct {
32
+	fsName string
33
+	magic  string
34
+	offset uint64
35
+}
36
+
37
+func ProbeFsType(device string) (string, error) {
38
+	probes := []probeData{
39
+		{"btrfs", "_BHRfS_M", 0x10040},
40
+		{"ext4", "\123\357", 0x438},
41
+		{"xfs", "XFSB", 0},
42
+	}
43
+
44
+	maxLen := uint64(0)
45
+	for _, p := range probes {
46
+		l := p.offset + uint64(len(p.magic))
47
+		if l > maxLen {
48
+			maxLen = l
49
+		}
50
+	}
51
+
52
+	file, err := os.Open(device)
53
+	if err != nil {
54
+		return "", err
55
+	}
56
+
57
+	buffer := make([]byte, maxLen)
58
+	l, err := file.Read(buffer)
59
+	if err != nil {
60
+		return "", err
61
+	}
62
+	file.Close()
63
+	if uint64(l) != maxLen {
64
+		return "", fmt.Errorf("unable to detect filesystem type of %s, short read", device)
65
+	}
66
+
67
+	for _, p := range probes {
68
+		if bytes.Equal([]byte(p.magic), buffer[p.offset:p.offset+uint64(len(p.magic))]) {
69
+			return p.fsName, nil
70
+		}
71
+	}
72
+
73
+	return "", fmt.Errorf("Unknown filesystem type on %s", device)
74
+}
75
+
76
+func joinMountOptions(a, b string) string {
77
+	if a == "" {
78
+		return b
79
+	}
80
+	if b == "" {
81
+		return a
82
+	}
83
+	return a + "," + b
84
+}
... ...
@@ -15,7 +15,7 @@ const (
15 15
 	FsMagicAufs  = FsMagic(0x61756673)
16 16
 )
17 17
 
18
-type InitFunc func(root string) (Driver, error)
18
+type InitFunc func(root string, options []string) (Driver, error)
19 19
 
20 20
 type Driver interface {
21 21
 	String() string
... ...
@@ -69,23 +69,23 @@ func Register(name string, initFunc InitFunc) error {
69 69
 	return nil
70 70
 }
71 71
 
72
-func GetDriver(name, home string) (Driver, error) {
72
+func GetDriver(name, home string, options []string) (Driver, error) {
73 73
 	if initFunc, exists := drivers[name]; exists {
74
-		return initFunc(path.Join(home, name))
74
+		return initFunc(path.Join(home, name), options)
75 75
 	}
76 76
 	return nil, ErrNotSupported
77 77
 }
78 78
 
79
-func New(root string) (driver Driver, err error) {
79
+func New(root string, options []string) (driver Driver, err error) {
80 80
 	for _, name := range []string{os.Getenv("DOCKER_DRIVER"), DefaultDriver} {
81 81
 		if name != "" {
82
-			return GetDriver(name, root)
82
+			return GetDriver(name, root, options)
83 83
 		}
84 84
 	}
85 85
 
86 86
 	// Check for priority drivers first
87 87
 	for _, name := range priority {
88
-		driver, err = GetDriver(name, root)
88
+		driver, err = GetDriver(name, root, options)
89 89
 		if err != nil {
90 90
 			if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS {
91 91
 				continue
... ...
@@ -97,7 +97,7 @@ func New(root string) (driver Driver, err error) {
97 97
 
98 98
 	// Check all registered drivers if no priority driver is found
99 99
 	for _, initFunc := range drivers {
100
-		if driver, err = initFunc(root); err != nil {
100
+		if driver, err = initFunc(root, options); err != nil {
101 101
 			if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS {
102 102
 				continue
103 103
 			}
... ...
@@ -29,7 +29,7 @@ func newDriver(t *testing.T, name string) *Driver {
29 29
 		t.Fatal(err)
30 30
 	}
31 31
 
32
-	d, err := graphdriver.GetDriver(name, root)
32
+	d, err := graphdriver.GetDriver(name, root, nil)
33 33
 	if err != nil {
34 34
 		if err == graphdriver.ErrNotSupported || err == graphdriver.ErrPrerequisites {
35 35
 			t.Skip("Driver %s not supported", name)
... ...
@@ -12,7 +12,7 @@ func init() {
12 12
 	graphdriver.Register("vfs", Init)
13 13
 }
14 14
 
15
-func Init(home string) (graphdriver.Driver, error) {
15
+func Init(home string, options []string) (graphdriver.Driver, error) {
16 16
 	d := &Driver{
17 17
 		home: home,
18 18
 	}
... ...
@@ -25,6 +25,7 @@ type Config struct {
25 25
 	BridgeIP                    string
26 26
 	InterContainerCommunication bool
27 27
 	GraphDriver                 string
28
+	GraphOptions                []string
28 29
 	ExecDriver                  string
29 30
 	Mtu                         int
30 31
 	DisableNetwork              bool
... ...
@@ -49,6 +50,10 @@ func ConfigFromJob(job *engine.Job) *Config {
49 49
 		ExecDriver:                  job.Getenv("ExecDriver"),
50 50
 		EnableSelinuxSupport:        job.GetenvBool("EnableSelinuxSupport"),
51 51
 	}
52
+	if graphOpts := job.GetenvList("GraphOptions"); graphOpts != nil {
53
+		config.GraphOptions = graphOpts
54
+	}
55
+
52 56
 	if dns := job.GetenvList("Dns"); dns != nil {
53 57
 		config.Dns = dns
54 58
 	}
... ...
@@ -41,6 +41,7 @@ func main() {
41 41
 	var (
42 42
 		flVersion            = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit")
43 43
 		flDaemon             = flag.Bool([]string{"d", "-daemon"}, false, "Enable daemon mode")
44
+		flGraphOpts          opts.ListOpts
44 45
 		flDebug              = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode")
45 46
 		flAutoRestart        = flag.Bool([]string{"r", "-restart"}, true, "Restart previously running containers")
46 47
 		bridgeName           = flag.String([]string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking")
... ...
@@ -69,6 +70,7 @@ func main() {
69 69
 	flag.Var(&flDns, []string{"#dns", "-dns"}, "Force docker to use specific DNS servers")
70 70
 	flag.Var(&flDnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains")
71 71
 	flag.Var(&flHosts, []string{"H", "-host"}, "The socket(s) to bind to in daemon mode\nspecified using one or more tcp://host:port, unix:///path/to/socket, fd://* or fd://socketfd.")
72
+	flag.Var(&flGraphOpts, []string{"-storage-opt"}, "Set storage driver options")
72 73
 
73 74
 	flag.Parse()
74 75
 
... ...
@@ -156,6 +158,7 @@ func main() {
156 156
 			job.Setenv("DefaultIp", *flDefaultIp)
157 157
 			job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
158 158
 			job.Setenv("GraphDriver", *flGraphDriver)
159
+			job.SetenvList("GraphOptions", flGraphOpts.GetAll())
159 160
 			job.Setenv("ExecDriver", *flExecDriver)
160 161
 			job.SetenvInt("Mtu", *flMtu)
161 162
 			job.SetenvBool("EnableSelinuxSupport", *flSelinuxEnabled)
... ...
@@ -73,6 +73,7 @@ expect an integer, and they can only be specified once.
73 73
       -p, --pidfile="/var/run/docker.pid"        Path to use for daemon PID file
74 74
       -r, --restart=true                         Restart previously running containers
75 75
       -s, --storage-driver=""                    Force the docker runtime to use a specific storage driver
76
+      --storage-opt=[]                           Set storage driver options
76 77
       --selinux-enabled=false                    Enable selinux support
77 78
       --tls=false                                Use TLS; implied by tls-verify flags
78 79
       --tlscacert="/home/sven/.docker/ca.pem"    Trust only remotes providing a certificate signed by the CA given here
... ...
@@ -36,7 +36,7 @@ func fakeTar() (io.Reader, error) {
36 36
 }
37 37
 
38 38
 func mkTestTagStore(root string, t *testing.T) *TagStore {
39
-	driver, err := graphdriver.New(root)
39
+	driver, err := graphdriver.New(root, nil)
40 40
 	if err != nil {
41 41
 		t.Fatal(err)
42 42
 	}
... ...
@@ -293,7 +293,7 @@ func tempGraph(t *testing.T) (*graph.Graph, graphdriver.Driver) {
293 293
 	if err != nil {
294 294
 		t.Fatal(err)
295 295
 	}
296
-	driver, err := graphdriver.New(tmp)
296
+	driver, err := graphdriver.New(tmp, nil)
297 297
 	if err != nil {
298 298
 		t.Fatal(err)
299 299
 	}
... ...
@@ -21,6 +21,42 @@ func HumanSize(size int64) string {
21 21
 	return fmt.Sprintf("%.4g %s", sizef, units[i])
22 22
 }
23 23
 
24
+// FromHumanSize returns an integer from a human-readable specification of a size
25
+// using SI standard (eg. "44kB", "17MB")
26
+func FromHumanSize(size string) (int64, error) {
27
+	re, error := regexp.Compile("^(\\d+)([kKmMgGtTpP])?[bB]?$")
28
+	if error != nil {
29
+		return -1, fmt.Errorf("%s does not specify not a size", size)
30
+	}
31
+
32
+	matches := re.FindStringSubmatch(size)
33
+
34
+	if len(matches) != 3 {
35
+		return -1, fmt.Errorf("Invalid size: '%s'", size)
36
+	}
37
+
38
+	theSize, error := strconv.ParseInt(matches[1], 10, 0)
39
+	if error != nil {
40
+		return -1, error
41
+	}
42
+
43
+	unit := strings.ToLower(matches[2])
44
+
45
+	if unit == "k" {
46
+		theSize *= 1000
47
+	} else if unit == "m" {
48
+		theSize *= 1000 * 1000
49
+	} else if unit == "g" {
50
+		theSize *= 1000 * 1000 * 1000
51
+	} else if unit == "t" {
52
+		theSize *= 1000 * 1000 * 1000 * 1000
53
+	} else if unit == "p" {
54
+		theSize *= 1000 * 1000 * 1000 * 1000 * 1000
55
+	}
56
+
57
+	return theSize, nil
58
+}
59
+
24 60
 // Parses a human-readable string representing an amount of RAM
25 61
 // in bytes, kibibytes, mebibytes or gibibytes, and returns the
26 62
 // number of bytes, or -1 if the string is unparseable.
... ...
@@ -20,6 +20,41 @@ func TestHumanSize(t *testing.T) {
20 20
 	}
21 21
 }
22 22
 
23
+func TestFromHumanSize(t *testing.T) {
24
+	assertFromHumanSize(t, "32", false, 32)
25
+	assertFromHumanSize(t, "32b", false, 32)
26
+	assertFromHumanSize(t, "32B", false, 32)
27
+	assertFromHumanSize(t, "32k", false, 32*1000)
28
+	assertFromHumanSize(t, "32K", false, 32*1000)
29
+	assertFromHumanSize(t, "32kb", false, 32*1000)
30
+	assertFromHumanSize(t, "32Kb", false, 32*1000)
31
+	assertFromHumanSize(t, "32Mb", false, 32*1000*1000)
32
+	assertFromHumanSize(t, "32Gb", false, 32*1000*1000*1000)
33
+	assertFromHumanSize(t, "32Tb", false, 32*1000*1000*1000*1000)
34
+	assertFromHumanSize(t, "8Pb", false, 8*1000*1000*1000*1000*1000)
35
+
36
+	assertFromHumanSize(t, "", true, -1)
37
+	assertFromHumanSize(t, "hello", true, -1)
38
+	assertFromHumanSize(t, "-32", true, -1)
39
+	assertFromHumanSize(t, " 32 ", true, -1)
40
+	assertFromHumanSize(t, "32 mb", true, -1)
41
+	assertFromHumanSize(t, "32m b", true, -1)
42
+	assertFromHumanSize(t, "32bm", true, -1)
43
+}
44
+
45
+func assertFromHumanSize(t *testing.T, size string, expectError bool, expectedBytes int64) {
46
+	actualBytes, err := FromHumanSize(size)
47
+	if (err != nil) && !expectError {
48
+		t.Errorf("Unexpected error parsing '%s': %s", size, err)
49
+	}
50
+	if (err == nil) && expectError {
51
+		t.Errorf("Expected to get an error parsing '%s', but got none (bytes=%d)", size, actualBytes)
52
+	}
53
+	if actualBytes != expectedBytes {
54
+		t.Errorf("Expected '%s' to parse as %d bytes, got %d", size, expectedBytes, actualBytes)
55
+	}
56
+}
57
+
23 58
 func TestRAMInBytes(t *testing.T) {
24 59
 	assertRAMInBytes(t, "32", false, 32)
25 60
 	assertRAMInBytes(t, "32b", false, 32)