Add --storage-opt daemon option and some devicemapper option (with fixes)
| ... | ... |
@@ -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 |
| 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)
|
| ... | ... |
@@ -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 |
| ... | ... |
@@ -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) |