mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #6224 from tiborvass/storage-options
Add --storage-opt daemon option and some devicemapper option (with fixes)
This commit is contained in:
commit
9329c0d2e0
20 changed files with 532 additions and 97 deletions
|
@ -780,7 +780,7 @@ func NewDaemonFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*D
|
|||
graphdriver.DefaultDriver = config.GraphDriver
|
||||
|
||||
// Load storage driver
|
||||
driver, err := graphdriver.New(config.Root)
|
||||
driver, err := graphdriver.New(config.Root, config.GraphOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -809,7 +809,7 @@ func NewDaemonFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*D
|
|||
|
||||
// We don't want to use a complex driver like aufs or devmapper
|
||||
// for volumes, just a plain filesystem
|
||||
volumesDriver, err := graphdriver.GetDriver("vfs", config.Root)
|
||||
volumesDriver, err := graphdriver.GetDriver("vfs", config.Root, config.GraphOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ type Driver struct {
|
|||
|
||||
// New returns a new AUFS driver.
|
||||
// An error is returned if AUFS is not supported.
|
||||
func Init(root string) (graphdriver.Driver, error) {
|
||||
func Init(root string, options []string) (graphdriver.Driver, error) {
|
||||
// Try to load the aufs kernel module
|
||||
if err := supportsAufs(); err != nil {
|
||||
return nil, graphdriver.ErrNotSupported
|
||||
|
|
|
@ -17,7 +17,7 @@ var (
|
|||
)
|
||||
|
||||
func testInit(dir string, t *testing.T) graphdriver.Driver {
|
||||
d, err := Init(dir)
|
||||
d, err := Init(dir, nil)
|
||||
if err != nil {
|
||||
if err == graphdriver.ErrNotSupported {
|
||||
t.Skip(err)
|
||||
|
|
|
@ -22,7 +22,7 @@ func init() {
|
|||
graphdriver.Register("btrfs", Init)
|
||||
}
|
||||
|
||||
func Init(home string) (graphdriver.Driver, error) {
|
||||
func Init(home string, options []string) (graphdriver.Driver, error) {
|
||||
rootdir := path.Dir(home)
|
||||
|
||||
var buf syscall.Statfs_t
|
||||
|
|
143
daemon/graphdriver/devmapper/README.md
Normal file
143
daemon/graphdriver/devmapper/README.md
Normal file
|
@ -0,0 +1,143 @@
|
|||
## devicemapper - a storage backend based on Device Mapper
|
||||
|
||||
### Theory of operation
|
||||
|
||||
The device mapper graphdriver uses the device mapper thin provisioning
|
||||
module (dm-thinp) to implement CoW snapshots. For each devicemapper
|
||||
graph location (typically `/var/lib/docker/devicemapper`, $graph below)
|
||||
a thin pool is created based on two block devices, one for data and
|
||||
one for metadata. By default these block devices are created
|
||||
automatically by using loopback mounts of automatically creates sparse
|
||||
files.
|
||||
|
||||
The default loopback files used are `$graph/devicemapper/data` and
|
||||
`$graph/devicemapper/metadata`. Additional metadata required to map
|
||||
from docker entities to the corresponding devicemapper volumes is
|
||||
stored in the `$graph/devicemapper/json` file (encoded as Json).
|
||||
|
||||
In order to support multiple devicemapper graphs on a system the thin
|
||||
pool will be named something like: `docker-0:33-19478248-pool`, where
|
||||
the `0:30` part is the minor/major device nr and `19478248` is the
|
||||
inode number of the $graph directory.
|
||||
|
||||
On the thin pool docker automatically creates a base thin device,
|
||||
called something like `docker-0:33-19478248-base` of a fixed
|
||||
size. This is automatically formated on creation and contains just an
|
||||
empty filesystem. This device is the base of all docker images and
|
||||
containers. All base images are snapshots of this device and those
|
||||
images are then in turn used as snapshots for other images and
|
||||
eventually containers.
|
||||
|
||||
### options
|
||||
|
||||
The devicemapper backend supports some options that you can specify
|
||||
when starting the docker daemon using the --storage-opt flags.
|
||||
This uses the `dm` prefix and would be used somthing like `docker -d --storage-opt dm.foo=bar`.
|
||||
|
||||
Here is the list of supported options:
|
||||
|
||||
* `dm.basesize`
|
||||
|
||||
Specifies the size to use when creating the base device, which
|
||||
limits the size of images and containers. The default value is
|
||||
10G. Note, thin devices are inherently "sparse", so a 10G device
|
||||
which is mostly empty doesn't use 10 GB of space on the
|
||||
pool. However, the filesystem will use more space for the empty
|
||||
case the larger the device is.
|
||||
|
||||
Example use:
|
||||
|
||||
``docker -d --storage-opt dm.basesize=20G``
|
||||
|
||||
* `dm.loopdatasize`
|
||||
|
||||
Specifies the size to use when creating the loopback file for the
|
||||
"data" device which is used for the thin pool. The default size is
|
||||
100G. Note that the file is sparse, so it will not initially take
|
||||
up this much space.
|
||||
|
||||
Example use:
|
||||
|
||||
``docker -d --storage-opt dm.loopdatasize=200G``
|
||||
|
||||
* `dm.loopmetadatasize`
|
||||
|
||||
Specifies the size to use when creating the loopback file for the
|
||||
"metadadata" device which is used for the thin pool. The default size is
|
||||
2G. Note that the file is sparse, so it will not initially take
|
||||
up this much space.
|
||||
|
||||
Example use:
|
||||
|
||||
``docker -d --storage-opt dm.loopmetadatasize=4G``
|
||||
|
||||
* `dm.fs`
|
||||
|
||||
Specifies the filesystem type to use for the base device. The supported
|
||||
options are "ext4" and "xfs". The default is "ext4"
|
||||
|
||||
Example use:
|
||||
|
||||
``docker -d --storage-opt dm.fs=xfs``
|
||||
|
||||
* `dm.mkfsarg`
|
||||
|
||||
Specifies extra mkfs arguments to be used when creating the base device.
|
||||
|
||||
Example use:
|
||||
|
||||
``docker -d --storage-opt "dm.mkfsarg=-O ^has_journal"``
|
||||
|
||||
* `dm.mountopt`
|
||||
|
||||
Specifies extra mount options used when mounting the thin devices.
|
||||
|
||||
Example use:
|
||||
|
||||
``docker -d --storage-opt dm.mountopt=nodiscard``
|
||||
|
||||
* `dm.datadev`
|
||||
|
||||
Specifies a custom blockdevice to use for data for the thin pool.
|
||||
|
||||
If using a block device for device mapper storage, ideally both
|
||||
datadev and metadatadev should be specified to completely avoid
|
||||
using the loopback device.
|
||||
|
||||
Example use:
|
||||
|
||||
``docker -d --storage-opt dm.datadev=/dev/sdb1 --storage-opt dm.metadatadev=/dev/sdc1``
|
||||
|
||||
* `dm.metadatadev`
|
||||
|
||||
Specifies a custom blockdevice to use for metadata for the thin
|
||||
pool.
|
||||
|
||||
For best performance the metadata should be on a different spindle
|
||||
than the data, or even better on an SSD.
|
||||
|
||||
If setting up a new metadata pool it is required to be valid. This
|
||||
can be achieved by zeroing the first 4k to indicate empty
|
||||
metadata, like this:
|
||||
|
||||
``dd if=/dev/zero of=$metadata_dev bs=4096 count=1```
|
||||
|
||||
Example use:
|
||||
|
||||
``docker -d --storage-opt dm.datadev=/dev/sdb1 --storage-opt dm.metadatadev=/dev/sdc1``
|
||||
|
||||
* `dm.blkdiscard`
|
||||
|
||||
Enables or disables the use of blkdiscard when removing
|
||||
devicemapper devices. This is enabled by default (only) if using
|
||||
loopback devices and is required to res-parsify the loopback file
|
||||
on image/container removal.
|
||||
|
||||
Disabling this on loopback can lead to *much* faster container
|
||||
removal times, but will make the space used in /var/lib/docker
|
||||
directory not be returned to the system for other use when
|
||||
containers are removed.
|
||||
|
||||
Example use:
|
||||
|
||||
``docker -d --storage-opt dm.blkdiscard=false``
|
|
@ -13,11 +13,14 @@ import (
|
|||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/dotcloud/docker/daemon/graphdriver"
|
||||
"github.com/dotcloud/docker/pkg/label"
|
||||
"github.com/dotcloud/docker/pkg/units"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
)
|
||||
|
||||
|
@ -64,6 +67,17 @@ type DeviceSet struct {
|
|||
TransactionId uint64
|
||||
NewTransactionId uint64
|
||||
nextDeviceId int
|
||||
|
||||
// Options
|
||||
dataLoopbackSize int64
|
||||
metaDataLoopbackSize int64
|
||||
baseFsSize uint64
|
||||
filesystem string
|
||||
mountOptions string
|
||||
mkfsArgs []string
|
||||
dataDevice string
|
||||
metadataDevice string
|
||||
doBlkDiscard bool
|
||||
}
|
||||
|
||||
type DiskUsage struct {
|
||||
|
@ -273,26 +287,39 @@ func (devices *DeviceSet) activateDeviceIfNeeded(info *DevInfo) error {
|
|||
func (devices *DeviceSet) createFilesystem(info *DevInfo) error {
|
||||
devname := info.DevName()
|
||||
|
||||
err := exec.Command("mkfs.ext4", "-E", "nodiscard,lazy_itable_init=0,lazy_journal_init=0", devname).Run()
|
||||
if err != nil {
|
||||
err = exec.Command("mkfs.ext4", "-E", "nodiscard,lazy_itable_init=0", devname).Run()
|
||||
args := []string{}
|
||||
for _, arg := range devices.mkfsArgs {
|
||||
args = append(args, arg)
|
||||
}
|
||||
|
||||
args = append(args, devname)
|
||||
|
||||
var err error
|
||||
switch devices.filesystem {
|
||||
case "xfs":
|
||||
err = exec.Command("mkfs.xfs", args...).Run()
|
||||
case "ext4":
|
||||
err = exec.Command("mkfs.ext4", append([]string{"-E", "nodiscard,lazy_itable_init=0,lazy_journal_init=0"}, args...)...).Run()
|
||||
if err != nil {
|
||||
err = exec.Command("mkfs.ext4", append([]string{"-E", "nodiscard,lazy_itable_init=0"}, args...)...).Run()
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("Unsupported filesystem type %s", devices.filesystem)
|
||||
}
|
||||
if err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (devices *DeviceSet) initMetaData() error {
|
||||
_, _, _, params, err := getStatus(devices.getPoolName())
|
||||
if err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := fmt.Sscanf(params, "%d", &devices.TransactionId); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
devices.NewTransactionId = devices.TransactionId
|
||||
|
@ -301,7 +328,6 @@ func (devices *DeviceSet) initMetaData() error {
|
|||
|
||||
jsonData, err := ioutil.ReadFile(devices.oldMetadataFile())
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -309,7 +335,6 @@ func (devices *DeviceSet) initMetaData() error {
|
|||
m := MetaData{Devices: make(map[string]*DevInfo)}
|
||||
|
||||
if err := json.Unmarshal(jsonData, &m); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -359,7 +384,6 @@ func (devices *DeviceSet) setupBaseImage() error {
|
|||
if oldInfo != nil && !oldInfo.Initialized {
|
||||
utils.Debugf("Removing uninitialized base image")
|
||||
if err := devices.deleteDevice(oldInfo); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -370,37 +394,32 @@ func (devices *DeviceSet) setupBaseImage() error {
|
|||
|
||||
// Create initial device
|
||||
if err := createDevice(devices.getPoolDevName(), &id); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Ids are 24bit, so wrap around
|
||||
devices.nextDeviceId = (id + 1) & 0xffffff
|
||||
|
||||
utils.Debugf("Registering base device (id %v) with FS size %v", id, DefaultBaseFsSize)
|
||||
info, err := devices.registerDevice(id, "", DefaultBaseFsSize)
|
||||
utils.Debugf("Registering base device (id %v) with FS size %v", id, devices.baseFsSize)
|
||||
info, err := devices.registerDevice(id, "", devices.baseFsSize)
|
||||
if err != nil {
|
||||
_ = deleteDevice(devices.getPoolDevName(), id)
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
utils.Debugf("Creating filesystem on base device-manager snapshot")
|
||||
|
||||
if err = devices.activateDeviceIfNeeded(info); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := devices.createFilesystem(info); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
info.Initialized = true
|
||||
if err = devices.saveMetadata(info); err != nil {
|
||||
info.Initialized = false
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -506,6 +525,12 @@ func (devices *DeviceSet) ResizePool(size int64) error {
|
|||
func (devices *DeviceSet) initDevmapper(doInit bool) error {
|
||||
logInit(devices)
|
||||
|
||||
_, err := getDriverVersion()
|
||||
if err != nil {
|
||||
// Can't even get driver version, assume not supported
|
||||
return graphdriver.ErrNotSupported
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(devices.metadataDir(), 0700); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
@ -548,45 +573,74 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error {
|
|||
if info.Exists == 0 {
|
||||
utils.Debugf("Pool doesn't exist. Creating it.")
|
||||
|
||||
hasData := devices.hasImage("data")
|
||||
hasMetadata := devices.hasImage("metadata")
|
||||
var (
|
||||
dataFile *os.File
|
||||
metadataFile *os.File
|
||||
)
|
||||
|
||||
if !doInit && !hasData {
|
||||
return errors.New("Loopback data file not found")
|
||||
}
|
||||
if devices.dataDevice == "" {
|
||||
// Make sure the sparse images exist in <root>/devicemapper/data
|
||||
|
||||
if !doInit && !hasMetadata {
|
||||
return errors.New("Loopback metadata file not found")
|
||||
}
|
||||
hasData := devices.hasImage("data")
|
||||
|
||||
createdLoopback = !hasData || !hasMetadata
|
||||
data, err := devices.ensureImage("data", DefaultDataLoopbackSize)
|
||||
if err != nil {
|
||||
utils.Debugf("Error device ensureImage (data): %s\n", err)
|
||||
return err
|
||||
}
|
||||
metadata, err := devices.ensureImage("metadata", DefaultMetaDataLoopbackSize)
|
||||
if err != nil {
|
||||
utils.Debugf("Error device ensureImage (metadata): %s\n", err)
|
||||
return err
|
||||
}
|
||||
if !doInit && !hasData {
|
||||
return errors.New("Loopback data file not found")
|
||||
}
|
||||
|
||||
dataFile, err := attachLoopDevice(data)
|
||||
if err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
if !hasData {
|
||||
createdLoopback = true
|
||||
}
|
||||
|
||||
data, err := devices.ensureImage("data", devices.dataLoopbackSize)
|
||||
if err != nil {
|
||||
utils.Debugf("Error device ensureImage (data): %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
dataFile, err = attachLoopDevice(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
dataFile, err = os.OpenFile(devices.dataDevice, os.O_RDWR, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
defer dataFile.Close()
|
||||
|
||||
metadataFile, err := attachLoopDevice(metadata)
|
||||
if err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
if devices.metadataDevice == "" {
|
||||
// Make sure the sparse images exist in <root>/devicemapper/metadata
|
||||
|
||||
hasMetadata := devices.hasImage("metadata")
|
||||
|
||||
if !doInit && !hasMetadata {
|
||||
return errors.New("Loopback metadata file not found")
|
||||
}
|
||||
|
||||
if !hasMetadata {
|
||||
createdLoopback = true
|
||||
}
|
||||
|
||||
metadata, err := devices.ensureImage("metadata", devices.metaDataLoopbackSize)
|
||||
if err != nil {
|
||||
utils.Debugf("Error device ensureImage (metadata): %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
metadataFile, err = attachLoopDevice(metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
metadataFile, err = os.OpenFile(devices.metadataDevice, os.O_RDWR, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
defer metadataFile.Close()
|
||||
|
||||
if err := createPool(devices.getPoolName(), dataFile, metadataFile); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -595,7 +649,6 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error {
|
|||
// load the transaction id and migrate old metadata
|
||||
if !createdLoopback {
|
||||
if err = devices.initMetaData(); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -646,12 +699,14 @@ func (devices *DeviceSet) AddDevice(hash, baseHash string) error {
|
|||
}
|
||||
|
||||
func (devices *DeviceSet) deleteDevice(info *DevInfo) error {
|
||||
// This is a workaround for the kernel not discarding block so
|
||||
// on the thin pool when we remove a thinp device, so we do it
|
||||
// manually
|
||||
if err := devices.activateDeviceIfNeeded(info); err == nil {
|
||||
if err := BlockDeviceDiscard(info.DevName()); err != nil {
|
||||
utils.Debugf("Error discarding block on device: %s (ignoring)\n", err)
|
||||
if devices.doBlkDiscard {
|
||||
// This is a workaround for the kernel not discarding block so
|
||||
// on the thin pool when we remove a thinp device, so we do it
|
||||
// manually
|
||||
if err := devices.activateDeviceIfNeeded(info); err == nil {
|
||||
if err := BlockDeviceDiscard(info.DevName()); err != nil {
|
||||
utils.Debugf("Error discarding block on device: %s (ignoring)\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -705,7 +760,6 @@ func (devices *DeviceSet) deactivatePool() error {
|
|||
devname := devices.getPoolDevName()
|
||||
devinfo, err := getInfo(devname)
|
||||
if err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
if devinfo.Exists != 0 {
|
||||
|
@ -727,12 +781,10 @@ func (devices *DeviceSet) deactivateDevice(info *DevInfo) error {
|
|||
|
||||
devinfo, err := getInfo(info.Name())
|
||||
if err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
if devinfo.Exists != 0 {
|
||||
if err := devices.removeDeviceAndWait(info.Name()); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -907,11 +959,24 @@ func (devices *DeviceSet) MountDevice(hash, path, mountLabel string) error {
|
|||
|
||||
var flags uintptr = syscall.MS_MGC_VAL
|
||||
|
||||
mountOptions := label.FormatMountLabel("discard", mountLabel)
|
||||
err = syscall.Mount(info.DevName(), path, "ext4", flags, mountOptions)
|
||||
fstype, err := ProbeFsType(info.DevName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
options := ""
|
||||
|
||||
if fstype == "xfs" {
|
||||
// XFS needs nouuid or it can't mount filesystems with the same fs
|
||||
options = joinMountOptions(options, "nouuid")
|
||||
}
|
||||
|
||||
options = joinMountOptions(options, devices.mountOptions)
|
||||
options = joinMountOptions(options, label.FormatMountLabel("", mountLabel))
|
||||
|
||||
err = syscall.Mount(info.DevName(), path, fstype, flags, joinMountOptions("discard", options))
|
||||
if err != nil && err == syscall.EINVAL {
|
||||
mountOptions = label.FormatMountLabel("", mountLabel)
|
||||
err = syscall.Mount(info.DevName(), path, "ext4", flags, mountOptions)
|
||||
err = syscall.Mount(info.DevName(), path, fstype, flags, options)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err)
|
||||
|
@ -949,7 +1014,6 @@ func (devices *DeviceSet) UnmountDevice(hash string) error {
|
|||
|
||||
utils.Debugf("[devmapper] Unmount(%s)", info.mountPath)
|
||||
if err := syscall.Unmount(info.mountPath, 0); err != nil {
|
||||
utils.Debugf("\n--->Err: %s\n", err)
|
||||
return err
|
||||
}
|
||||
utils.Debugf("[devmapper] Unmount done")
|
||||
|
@ -1084,12 +1148,72 @@ func (devices *DeviceSet) Status() *Status {
|
|||
return status
|
||||
}
|
||||
|
||||
func NewDeviceSet(root string, doInit bool) (*DeviceSet, error) {
|
||||
func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error) {
|
||||
SetDevDir("/dev")
|
||||
|
||||
devices := &DeviceSet{
|
||||
root: root,
|
||||
MetaData: MetaData{Devices: make(map[string]*DevInfo)},
|
||||
root: root,
|
||||
MetaData: MetaData{Devices: make(map[string]*DevInfo)},
|
||||
dataLoopbackSize: DefaultDataLoopbackSize,
|
||||
metaDataLoopbackSize: DefaultMetaDataLoopbackSize,
|
||||
baseFsSize: DefaultBaseFsSize,
|
||||
filesystem: "ext4",
|
||||
doBlkDiscard: true,
|
||||
}
|
||||
|
||||
foundBlkDiscard := false
|
||||
for _, option := range options {
|
||||
key, val, err := utils.ParseKeyValueOpt(option)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key = strings.ToLower(key)
|
||||
switch key {
|
||||
case "dm.basesize":
|
||||
size, err := units.FromHumanSize(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
devices.baseFsSize = uint64(size)
|
||||
case "dm.loopdatasize":
|
||||
size, err := units.FromHumanSize(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
devices.dataLoopbackSize = size
|
||||
case "dm.loopmetadatasize":
|
||||
size, err := units.FromHumanSize(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
devices.metaDataLoopbackSize = size
|
||||
case "dm.fs":
|
||||
if val != "ext4" && val != "xfs" {
|
||||
return nil, fmt.Errorf("Unsupported filesystem %s\n", val)
|
||||
}
|
||||
devices.filesystem = val
|
||||
case "dm.mkfsarg":
|
||||
devices.mkfsArgs = append(devices.mkfsArgs, val)
|
||||
case "dm.mountopt":
|
||||
devices.mountOptions = joinMountOptions(devices.mountOptions, val)
|
||||
case "dm.metadatadev":
|
||||
devices.metadataDevice = val
|
||||
case "dm.datadev":
|
||||
devices.dataDevice = val
|
||||
case "dm.blkdiscard":
|
||||
foundBlkDiscard = true
|
||||
devices.doBlkDiscard, err = strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown option %s\n", key)
|
||||
}
|
||||
}
|
||||
|
||||
// By default, don't do blk discard hack on raw devices, its rarely useful and is expensive
|
||||
if !foundBlkDiscard && devices.dataDevice != "" {
|
||||
devices.doBlkDiscard = false
|
||||
}
|
||||
|
||||
if err := devices.initDevmapper(doInit); err != nil {
|
||||
|
|
|
@ -52,6 +52,7 @@ var (
|
|||
ErrTaskAddTarget = errors.New("dm_task_add_target failed")
|
||||
ErrTaskSetSector = errors.New("dm_task_set_sector failed")
|
||||
ErrTaskGetInfo = errors.New("dm_task_get_info failed")
|
||||
ErrTaskGetDriverVersion = errors.New("dm_task_get_driver_version failed")
|
||||
ErrTaskSetCookie = errors.New("dm_task_set_cookie failed")
|
||||
ErrNilCookie = errors.New("cookie ptr can't be nil")
|
||||
ErrAttachLoopbackDevice = errors.New("loopback mounting failed")
|
||||
|
@ -178,6 +179,14 @@ func (t *Task) GetInfo() (*Info, error) {
|
|||
return info, nil
|
||||
}
|
||||
|
||||
func (t *Task) GetDriverVersion() (string, error) {
|
||||
res := DmTaskGetDriverVersion(t.unmanaged)
|
||||
if res == "" {
|
||||
return "", ErrTaskGetDriverVersion
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64,
|
||||
length uint64, targetType string, params string) {
|
||||
|
||||
|
@ -394,6 +403,17 @@ func getInfo(name string) (*Info, error) {
|
|||
return task.GetInfo()
|
||||
}
|
||||
|
||||
func getDriverVersion() (string, error) {
|
||||
task := TaskCreate(DeviceVersion)
|
||||
if task == nil {
|
||||
return "", fmt.Errorf("Can't create DeviceVersion task")
|
||||
}
|
||||
if err := task.Run(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return task.GetDriverVersion()
|
||||
}
|
||||
|
||||
func getStatus(name string) (uint64, uint64, string, string, error) {
|
||||
task, err := createTask(DeviceStatus, name)
|
||||
if task == nil {
|
||||
|
|
|
@ -85,23 +85,24 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
DmGetLibraryVersion = dmGetLibraryVersionFct
|
||||
DmGetNextTarget = dmGetNextTargetFct
|
||||
DmLogInitVerbose = dmLogInitVerboseFct
|
||||
DmSetDevDir = dmSetDevDirFct
|
||||
DmTaskAddTarget = dmTaskAddTargetFct
|
||||
DmTaskCreate = dmTaskCreateFct
|
||||
DmTaskDestroy = dmTaskDestroyFct
|
||||
DmTaskGetInfo = dmTaskGetInfoFct
|
||||
DmTaskRun = dmTaskRunFct
|
||||
DmTaskSetAddNode = dmTaskSetAddNodeFct
|
||||
DmTaskSetCookie = dmTaskSetCookieFct
|
||||
DmTaskSetMessage = dmTaskSetMessageFct
|
||||
DmTaskSetName = dmTaskSetNameFct
|
||||
DmTaskSetRo = dmTaskSetRoFct
|
||||
DmTaskSetSector = dmTaskSetSectorFct
|
||||
DmUdevWait = dmUdevWaitFct
|
||||
LogWithErrnoInit = logWithErrnoInitFct
|
||||
DmGetLibraryVersion = dmGetLibraryVersionFct
|
||||
DmGetNextTarget = dmGetNextTargetFct
|
||||
DmLogInitVerbose = dmLogInitVerboseFct
|
||||
DmSetDevDir = dmSetDevDirFct
|
||||
DmTaskAddTarget = dmTaskAddTargetFct
|
||||
DmTaskCreate = dmTaskCreateFct
|
||||
DmTaskDestroy = dmTaskDestroyFct
|
||||
DmTaskGetInfo = dmTaskGetInfoFct
|
||||
DmTaskGetDriverVersion = dmTaskGetDriverVersionFct
|
||||
DmTaskRun = dmTaskRunFct
|
||||
DmTaskSetAddNode = dmTaskSetAddNodeFct
|
||||
DmTaskSetCookie = dmTaskSetCookieFct
|
||||
DmTaskSetMessage = dmTaskSetMessageFct
|
||||
DmTaskSetName = dmTaskSetNameFct
|
||||
DmTaskSetRo = dmTaskSetRoFct
|
||||
DmTaskSetSector = dmTaskSetSectorFct
|
||||
DmUdevWait = dmUdevWaitFct
|
||||
LogWithErrnoInit = logWithErrnoInitFct
|
||||
)
|
||||
|
||||
func free(p *C.char) {
|
||||
|
@ -184,6 +185,16 @@ func dmTaskGetInfoFct(task *CDmTask, info *Info) int {
|
|||
return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo))
|
||||
}
|
||||
|
||||
func dmTaskGetDriverVersionFct(task *CDmTask) string {
|
||||
buffer := C.malloc(128)
|
||||
defer C.free(buffer)
|
||||
res := C.dm_task_get_driver_version((*C.struct_dm_task)(task), (*C.char)(buffer), 128)
|
||||
if res == 0 {
|
||||
return ""
|
||||
}
|
||||
return C.GoString((*C.char)(buffer))
|
||||
}
|
||||
|
||||
func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
|
||||
var (
|
||||
Cstart, Clength C.uint64_t
|
||||
|
|
|
@ -26,8 +26,8 @@ type Driver struct {
|
|||
home string
|
||||
}
|
||||
|
||||
func Init(home string) (graphdriver.Driver, error) {
|
||||
deviceSet, err := NewDeviceSet(home, true)
|
||||
func Init(home string, options []string) (graphdriver.Driver, error) {
|
||||
deviceSet, err := NewDeviceSet(home, true, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
package devmapper
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
@ -27,3 +29,58 @@ func Mounted(mountpoint string) (bool, error) {
|
|||
parentSt := parent.Sys().(*syscall.Stat_t)
|
||||
return mntpointSt.Dev != parentSt.Dev, nil
|
||||
}
|
||||
|
||||
type probeData struct {
|
||||
fsName string
|
||||
magic string
|
||||
offset uint64
|
||||
}
|
||||
|
||||
func ProbeFsType(device string) (string, error) {
|
||||
probes := []probeData{
|
||||
{"btrfs", "_BHRfS_M", 0x10040},
|
||||
{"ext4", "\123\357", 0x438},
|
||||
{"xfs", "XFSB", 0},
|
||||
}
|
||||
|
||||
maxLen := uint64(0)
|
||||
for _, p := range probes {
|
||||
l := p.offset + uint64(len(p.magic))
|
||||
if l > maxLen {
|
||||
maxLen = l
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Open(device)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buffer := make([]byte, maxLen)
|
||||
l, err := file.Read(buffer)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
file.Close()
|
||||
if uint64(l) != maxLen {
|
||||
return "", fmt.Errorf("unable to detect filesystem type of %s, short read", device)
|
||||
}
|
||||
|
||||
for _, p := range probes {
|
||||
if bytes.Equal([]byte(p.magic), buffer[p.offset:p.offset+uint64(len(p.magic))]) {
|
||||
return p.fsName, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("Unknown filesystem type on %s", device)
|
||||
}
|
||||
|
||||
func joinMountOptions(a, b string) string {
|
||||
if a == "" {
|
||||
return b
|
||||
}
|
||||
if b == "" {
|
||||
return a
|
||||
}
|
||||
return a + "," + b
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ const (
|
|||
FsMagicAufs = FsMagic(0x61756673)
|
||||
)
|
||||
|
||||
type InitFunc func(root string) (Driver, error)
|
||||
type InitFunc func(root string, options []string) (Driver, error)
|
||||
|
||||
type Driver interface {
|
||||
String() string
|
||||
|
@ -69,23 +69,23 @@ func Register(name string, initFunc InitFunc) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func GetDriver(name, home string) (Driver, error) {
|
||||
func GetDriver(name, home string, options []string) (Driver, error) {
|
||||
if initFunc, exists := drivers[name]; exists {
|
||||
return initFunc(path.Join(home, name))
|
||||
return initFunc(path.Join(home, name), options)
|
||||
}
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
func New(root string) (driver Driver, err error) {
|
||||
func New(root string, options []string) (driver Driver, err error) {
|
||||
for _, name := range []string{os.Getenv("DOCKER_DRIVER"), DefaultDriver} {
|
||||
if name != "" {
|
||||
return GetDriver(name, root)
|
||||
return GetDriver(name, root, options)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for priority drivers first
|
||||
for _, name := range priority {
|
||||
driver, err = GetDriver(name, root)
|
||||
driver, err = GetDriver(name, root, options)
|
||||
if err != nil {
|
||||
if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS {
|
||||
continue
|
||||
|
@ -97,7 +97,7 @@ func New(root string) (driver Driver, err error) {
|
|||
|
||||
// Check all registered drivers if no priority driver is found
|
||||
for _, initFunc := range drivers {
|
||||
if driver, err = initFunc(root); err != nil {
|
||||
if driver, err = initFunc(root, options); err != nil {
|
||||
if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS {
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ func newDriver(t *testing.T, name string) *Driver {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
d, err := graphdriver.GetDriver(name, root)
|
||||
d, err := graphdriver.GetDriver(name, root, nil)
|
||||
if err != nil {
|
||||
if err == graphdriver.ErrNotSupported || err == graphdriver.ErrPrerequisites {
|
||||
t.Skip("Driver %s not supported", name)
|
||||
|
|
|
@ -12,7 +12,7 @@ func init() {
|
|||
graphdriver.Register("vfs", Init)
|
||||
}
|
||||
|
||||
func Init(home string) (graphdriver.Driver, error) {
|
||||
func Init(home string, options []string) (graphdriver.Driver, error) {
|
||||
d := &Driver{
|
||||
home: home,
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ type Config struct {
|
|||
BridgeIP string
|
||||
InterContainerCommunication bool
|
||||
GraphDriver string
|
||||
GraphOptions []string
|
||||
ExecDriver string
|
||||
Mtu int
|
||||
DisableNetwork bool
|
||||
|
@ -49,6 +50,10 @@ func ConfigFromJob(job *engine.Job) *Config {
|
|||
ExecDriver: job.Getenv("ExecDriver"),
|
||||
EnableSelinuxSupport: job.GetenvBool("EnableSelinuxSupport"),
|
||||
}
|
||||
if graphOpts := job.GetenvList("GraphOptions"); graphOpts != nil {
|
||||
config.GraphOptions = graphOpts
|
||||
}
|
||||
|
||||
if dns := job.GetenvList("Dns"); dns != nil {
|
||||
config.Dns = dns
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ func main() {
|
|||
var (
|
||||
flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit")
|
||||
flDaemon = flag.Bool([]string{"d", "-daemon"}, false, "Enable daemon mode")
|
||||
flGraphOpts opts.ListOpts
|
||||
flDebug = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode")
|
||||
flAutoRestart = flag.Bool([]string{"r", "-restart"}, true, "Restart previously running containers")
|
||||
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() {
|
|||
flag.Var(&flDns, []string{"#dns", "-dns"}, "Force docker to use specific DNS servers")
|
||||
flag.Var(&flDnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains")
|
||||
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.")
|
||||
flag.Var(&flGraphOpts, []string{"-storage-opt"}, "Set storage driver options")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
|
@ -156,6 +158,7 @@ func main() {
|
|||
job.Setenv("DefaultIp", *flDefaultIp)
|
||||
job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
|
||||
job.Setenv("GraphDriver", *flGraphDriver)
|
||||
job.SetenvList("GraphOptions", flGraphOpts.GetAll())
|
||||
job.Setenv("ExecDriver", *flExecDriver)
|
||||
job.SetenvInt("Mtu", *flMtu)
|
||||
job.SetenvBool("EnableSelinuxSupport", *flSelinuxEnabled)
|
||||
|
|
|
@ -73,6 +73,7 @@ expect an integer, and they can only be specified once.
|
|||
-p, --pidfile="/var/run/docker.pid" Path to use for daemon PID file
|
||||
-r, --restart=true Restart previously running containers
|
||||
-s, --storage-driver="" Force the docker runtime to use a specific storage driver
|
||||
--storage-opt=[] Set storage driver options
|
||||
--selinux-enabled=false Enable selinux support
|
||||
--tls=false Use TLS; implied by tls-verify flags
|
||||
--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) {
|
|||
}
|
||||
|
||||
func mkTestTagStore(root string, t *testing.T) *TagStore {
|
||||
driver, err := graphdriver.New(root)
|
||||
driver, err := graphdriver.New(root, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -293,7 +293,7 @@ func tempGraph(t *testing.T) (*graph.Graph, graphdriver.Driver) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
driver, err := graphdriver.New(tmp)
|
||||
driver, err := graphdriver.New(tmp, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -21,6 +21,42 @@ func HumanSize(size int64) string {
|
|||
return fmt.Sprintf("%.4g %s", sizef, units[i])
|
||||
}
|
||||
|
||||
// FromHumanSize returns an integer from a human-readable specification of a size
|
||||
// using SI standard (eg. "44kB", "17MB")
|
||||
func FromHumanSize(size string) (int64, error) {
|
||||
re, error := regexp.Compile("^(\\d+)([kKmMgGtTpP])?[bB]?$")
|
||||
if error != nil {
|
||||
return -1, fmt.Errorf("%s does not specify not a size", size)
|
||||
}
|
||||
|
||||
matches := re.FindStringSubmatch(size)
|
||||
|
||||
if len(matches) != 3 {
|
||||
return -1, fmt.Errorf("Invalid size: '%s'", size)
|
||||
}
|
||||
|
||||
theSize, error := strconv.ParseInt(matches[1], 10, 0)
|
||||
if error != nil {
|
||||
return -1, error
|
||||
}
|
||||
|
||||
unit := strings.ToLower(matches[2])
|
||||
|
||||
if unit == "k" {
|
||||
theSize *= 1000
|
||||
} else if unit == "m" {
|
||||
theSize *= 1000 * 1000
|
||||
} else if unit == "g" {
|
||||
theSize *= 1000 * 1000 * 1000
|
||||
} else if unit == "t" {
|
||||
theSize *= 1000 * 1000 * 1000 * 1000
|
||||
} else if unit == "p" {
|
||||
theSize *= 1000 * 1000 * 1000 * 1000 * 1000
|
||||
}
|
||||
|
||||
return theSize, nil
|
||||
}
|
||||
|
||||
// Parses a human-readable string representing an amount of RAM
|
||||
// in bytes, kibibytes, mebibytes or gibibytes, and returns the
|
||||
// number of bytes, or -1 if the string is unparseable.
|
||||
|
|
|
@ -20,6 +20,41 @@ func TestHumanSize(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestFromHumanSize(t *testing.T) {
|
||||
assertFromHumanSize(t, "32", false, 32)
|
||||
assertFromHumanSize(t, "32b", false, 32)
|
||||
assertFromHumanSize(t, "32B", false, 32)
|
||||
assertFromHumanSize(t, "32k", false, 32*1000)
|
||||
assertFromHumanSize(t, "32K", false, 32*1000)
|
||||
assertFromHumanSize(t, "32kb", false, 32*1000)
|
||||
assertFromHumanSize(t, "32Kb", false, 32*1000)
|
||||
assertFromHumanSize(t, "32Mb", false, 32*1000*1000)
|
||||
assertFromHumanSize(t, "32Gb", false, 32*1000*1000*1000)
|
||||
assertFromHumanSize(t, "32Tb", false, 32*1000*1000*1000*1000)
|
||||
assertFromHumanSize(t, "8Pb", false, 8*1000*1000*1000*1000*1000)
|
||||
|
||||
assertFromHumanSize(t, "", true, -1)
|
||||
assertFromHumanSize(t, "hello", true, -1)
|
||||
assertFromHumanSize(t, "-32", true, -1)
|
||||
assertFromHumanSize(t, " 32 ", true, -1)
|
||||
assertFromHumanSize(t, "32 mb", true, -1)
|
||||
assertFromHumanSize(t, "32m b", true, -1)
|
||||
assertFromHumanSize(t, "32bm", true, -1)
|
||||
}
|
||||
|
||||
func assertFromHumanSize(t *testing.T, size string, expectError bool, expectedBytes int64) {
|
||||
actualBytes, err := FromHumanSize(size)
|
||||
if (err != nil) && !expectError {
|
||||
t.Errorf("Unexpected error parsing '%s': %s", size, err)
|
||||
}
|
||||
if (err == nil) && expectError {
|
||||
t.Errorf("Expected to get an error parsing '%s', but got none (bytes=%d)", size, actualBytes)
|
||||
}
|
||||
if actualBytes != expectedBytes {
|
||||
t.Errorf("Expected '%s' to parse as %d bytes, got %d", size, expectedBytes, actualBytes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRAMInBytes(t *testing.T) {
|
||||
assertRAMInBytes(t, "32", false, 32)
|
||||
assertRAMInBytes(t, "32b", false, 32)
|
||||
|
|
Loading…
Reference in a new issue