1
0
Fork 0
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:
Tibor Vass 2014-06-05 16:00:03 -07:00
commit 9329c0d2e0
20 changed files with 532 additions and 97 deletions

View file

@ -780,7 +780,7 @@ func NewDaemonFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*D
graphdriver.DefaultDriver = config.GraphDriver graphdriver.DefaultDriver = config.GraphDriver
// Load storage driver // Load storage driver
driver, err := graphdriver.New(config.Root) driver, err := graphdriver.New(config.Root, config.GraphOptions)
if err != nil { if err != nil {
return nil, err 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 // We don't want to use a complex driver like aufs or devmapper
// for volumes, just a plain filesystem // 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -57,7 +57,7 @@ type Driver struct {
// New returns a new AUFS driver. // New returns a new AUFS driver.
// An error is returned if AUFS is not supported. // 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 // Try to load the aufs kernel module
if err := supportsAufs(); err != nil { if err := supportsAufs(); err != nil {
return nil, graphdriver.ErrNotSupported return nil, graphdriver.ErrNotSupported

View file

@ -17,7 +17,7 @@ var (
) )
func testInit(dir string, t *testing.T) graphdriver.Driver { func testInit(dir string, t *testing.T) graphdriver.Driver {
d, err := Init(dir) d, err := Init(dir, nil)
if err != nil { if err != nil {
if err == graphdriver.ErrNotSupported { if err == graphdriver.ErrNotSupported {
t.Skip(err) t.Skip(err)

View file

@ -22,7 +22,7 @@ func init() {
graphdriver.Register("btrfs", 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) rootdir := path.Dir(home)
var buf syscall.Statfs_t var buf syscall.Statfs_t

View 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``

View file

@ -13,11 +13,14 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings"
"sync" "sync"
"syscall" "syscall"
"time" "time"
"github.com/dotcloud/docker/daemon/graphdriver"
"github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/label"
"github.com/dotcloud/docker/pkg/units"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
) )
@ -64,6 +67,17 @@ type DeviceSet struct {
TransactionId uint64 TransactionId uint64
NewTransactionId uint64 NewTransactionId uint64
nextDeviceId int nextDeviceId int
// Options
dataLoopbackSize int64
metaDataLoopbackSize int64
baseFsSize uint64
filesystem string
mountOptions string
mkfsArgs []string
dataDevice string
metadataDevice string
doBlkDiscard bool
} }
type DiskUsage struct { type DiskUsage struct {
@ -273,26 +287,39 @@ func (devices *DeviceSet) activateDeviceIfNeeded(info *DevInfo) error {
func (devices *DeviceSet) createFilesystem(info *DevInfo) error { func (devices *DeviceSet) createFilesystem(info *DevInfo) error {
devname := info.DevName() devname := info.DevName()
err := exec.Command("mkfs.ext4", "-E", "nodiscard,lazy_itable_init=0,lazy_journal_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 { if err != nil {
err = exec.Command("mkfs.ext4", "-E", "nodiscard,lazy_itable_init=0", devname).Run() 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 { if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
return nil return nil
} }
func (devices *DeviceSet) initMetaData() error { func (devices *DeviceSet) initMetaData() error {
_, _, _, params, err := getStatus(devices.getPoolName()) _, _, _, params, err := getStatus(devices.getPoolName())
if err != nil { if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
if _, err := fmt.Sscanf(params, "%d", &devices.TransactionId); err != nil { if _, err := fmt.Sscanf(params, "%d", &devices.TransactionId); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
devices.NewTransactionId = devices.TransactionId devices.NewTransactionId = devices.TransactionId
@ -301,7 +328,6 @@ func (devices *DeviceSet) initMetaData() error {
jsonData, err := ioutil.ReadFile(devices.oldMetadataFile()) jsonData, err := ioutil.ReadFile(devices.oldMetadataFile())
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
@ -309,7 +335,6 @@ func (devices *DeviceSet) initMetaData() error {
m := MetaData{Devices: make(map[string]*DevInfo)} m := MetaData{Devices: make(map[string]*DevInfo)}
if err := json.Unmarshal(jsonData, &m); err != nil { if err := json.Unmarshal(jsonData, &m); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
@ -359,7 +384,6 @@ func (devices *DeviceSet) setupBaseImage() error {
if oldInfo != nil && !oldInfo.Initialized { if oldInfo != nil && !oldInfo.Initialized {
utils.Debugf("Removing uninitialized base image") utils.Debugf("Removing uninitialized base image")
if err := devices.deleteDevice(oldInfo); err != nil { if err := devices.deleteDevice(oldInfo); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
} }
@ -370,37 +394,32 @@ func (devices *DeviceSet) setupBaseImage() error {
// Create initial device // Create initial device
if err := createDevice(devices.getPoolDevName(), &id); err != nil { if err := createDevice(devices.getPoolDevName(), &id); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
// Ids are 24bit, so wrap around // Ids are 24bit, so wrap around
devices.nextDeviceId = (id + 1) & 0xffffff devices.nextDeviceId = (id + 1) & 0xffffff
utils.Debugf("Registering base device (id %v) with FS size %v", id, DefaultBaseFsSize) utils.Debugf("Registering base device (id %v) with FS size %v", id, devices.baseFsSize)
info, err := devices.registerDevice(id, "", DefaultBaseFsSize) info, err := devices.registerDevice(id, "", devices.baseFsSize)
if err != nil { if err != nil {
_ = deleteDevice(devices.getPoolDevName(), id) _ = deleteDevice(devices.getPoolDevName(), id)
utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
utils.Debugf("Creating filesystem on base device-manager snapshot") utils.Debugf("Creating filesystem on base device-manager snapshot")
if err = devices.activateDeviceIfNeeded(info); err != nil { if err = devices.activateDeviceIfNeeded(info); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
if err := devices.createFilesystem(info); err != nil { if err := devices.createFilesystem(info); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
info.Initialized = true info.Initialized = true
if err = devices.saveMetadata(info); err != nil { if err = devices.saveMetadata(info); err != nil {
info.Initialized = false info.Initialized = false
utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
@ -506,6 +525,12 @@ func (devices *DeviceSet) ResizePool(size int64) error {
func (devices *DeviceSet) initDevmapper(doInit bool) error { func (devices *DeviceSet) initDevmapper(doInit bool) error {
logInit(devices) 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) { if err := os.MkdirAll(devices.metadataDir(), 0700); err != nil && !os.IsExist(err) {
return err return err
} }
@ -548,45 +573,74 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error {
if info.Exists == 0 { if info.Exists == 0 {
utils.Debugf("Pool doesn't exist. Creating it.") utils.Debugf("Pool doesn't exist. Creating it.")
var (
dataFile *os.File
metadataFile *os.File
)
if devices.dataDevice == "" {
// Make sure the sparse images exist in <root>/devicemapper/data
hasData := devices.hasImage("data") hasData := devices.hasImage("data")
hasMetadata := devices.hasImage("metadata")
if !doInit && !hasData { if !doInit && !hasData {
return errors.New("Loopback data file not found") return errors.New("Loopback data file not found")
} }
if !doInit && !hasMetadata { if !hasData {
return errors.New("Loopback metadata file not found") createdLoopback = true
} }
createdLoopback = !hasData || !hasMetadata data, err := devices.ensureImage("data", devices.dataLoopbackSize)
data, err := devices.ensureImage("data", DefaultDataLoopbackSize)
if err != nil { if err != nil {
utils.Debugf("Error device ensureImage (data): %s\n", err) utils.Debugf("Error device ensureImage (data): %s\n", err)
return err return err
} }
metadata, err := devices.ensureImage("metadata", DefaultMetaDataLoopbackSize)
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()
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 { if err != nil {
utils.Debugf("Error device ensureImage (metadata): %s\n", err) utils.Debugf("Error device ensureImage (metadata): %s\n", err)
return err return err
} }
dataFile, err := attachLoopDevice(data) metadataFile, err = attachLoopDevice(metadata)
if err != nil { if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
defer dataFile.Close() } else {
metadataFile, err = os.OpenFile(devices.metadataDevice, os.O_RDWR, 0600)
metadataFile, err := attachLoopDevice(metadata)
if err != nil { if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
}
defer metadataFile.Close() defer metadataFile.Close()
if err := createPool(devices.getPoolName(), dataFile, metadataFile); err != nil { if err := createPool(devices.getPoolName(), dataFile, metadataFile); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
} }
@ -595,7 +649,6 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error {
// load the transaction id and migrate old metadata // load the transaction id and migrate old metadata
if !createdLoopback { if !createdLoopback {
if err = devices.initMetaData(); err != nil { if err = devices.initMetaData(); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
} }
@ -646,6 +699,7 @@ func (devices *DeviceSet) AddDevice(hash, baseHash string) error {
} }
func (devices *DeviceSet) deleteDevice(info *DevInfo) error { func (devices *DeviceSet) deleteDevice(info *DevInfo) error {
if devices.doBlkDiscard {
// This is a workaround for the kernel not discarding block so // 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 // on the thin pool when we remove a thinp device, so we do it
// manually // manually
@ -654,6 +708,7 @@ func (devices *DeviceSet) deleteDevice(info *DevInfo) error {
utils.Debugf("Error discarding block on device: %s (ignoring)\n", err) utils.Debugf("Error discarding block on device: %s (ignoring)\n", err)
} }
} }
}
devinfo, _ := getInfo(info.Name()) devinfo, _ := getInfo(info.Name())
if devinfo != nil && devinfo.Exists != 0 { if devinfo != nil && devinfo.Exists != 0 {
@ -705,7 +760,6 @@ func (devices *DeviceSet) deactivatePool() error {
devname := devices.getPoolDevName() devname := devices.getPoolDevName()
devinfo, err := getInfo(devname) devinfo, err := getInfo(devname)
if err != nil { if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
if devinfo.Exists != 0 { if devinfo.Exists != 0 {
@ -727,12 +781,10 @@ func (devices *DeviceSet) deactivateDevice(info *DevInfo) error {
devinfo, err := getInfo(info.Name()) devinfo, err := getInfo(info.Name())
if err != nil { if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
if devinfo.Exists != 0 { if devinfo.Exists != 0 {
if err := devices.removeDeviceAndWait(info.Name()); err != nil { if err := devices.removeDeviceAndWait(info.Name()); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
} }
@ -907,11 +959,24 @@ func (devices *DeviceSet) MountDevice(hash, path, mountLabel string) error {
var flags uintptr = syscall.MS_MGC_VAL var flags uintptr = syscall.MS_MGC_VAL
mountOptions := label.FormatMountLabel("discard", mountLabel) fstype, err := ProbeFsType(info.DevName())
err = syscall.Mount(info.DevName(), path, "ext4", flags, mountOptions) 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 { if err != nil && err == syscall.EINVAL {
mountOptions = label.FormatMountLabel("", mountLabel) err = syscall.Mount(info.DevName(), path, fstype, flags, options)
err = syscall.Mount(info.DevName(), path, "ext4", flags, mountOptions)
} }
if err != nil { if err != nil {
return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err) 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) utils.Debugf("[devmapper] Unmount(%s)", info.mountPath)
if err := syscall.Unmount(info.mountPath, 0); err != nil { if err := syscall.Unmount(info.mountPath, 0); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err return err
} }
utils.Debugf("[devmapper] Unmount done") utils.Debugf("[devmapper] Unmount done")
@ -1084,12 +1148,72 @@ func (devices *DeviceSet) Status() *Status {
return status return status
} }
func NewDeviceSet(root string, doInit bool) (*DeviceSet, error) { func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error) {
SetDevDir("/dev") SetDevDir("/dev")
devices := &DeviceSet{ devices := &DeviceSet{
root: root, root: root,
MetaData: MetaData{Devices: make(map[string]*DevInfo)}, 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 { if err := devices.initDevmapper(doInit); err != nil {

View file

@ -52,6 +52,7 @@ var (
ErrTaskAddTarget = errors.New("dm_task_add_target failed") ErrTaskAddTarget = errors.New("dm_task_add_target failed")
ErrTaskSetSector = errors.New("dm_task_set_sector failed") ErrTaskSetSector = errors.New("dm_task_set_sector failed")
ErrTaskGetInfo = errors.New("dm_task_get_info 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") ErrTaskSetCookie = errors.New("dm_task_set_cookie failed")
ErrNilCookie = errors.New("cookie ptr can't be nil") ErrNilCookie = errors.New("cookie ptr can't be nil")
ErrAttachLoopbackDevice = errors.New("loopback mounting failed") ErrAttachLoopbackDevice = errors.New("loopback mounting failed")
@ -178,6 +179,14 @@ func (t *Task) GetInfo() (*Info, error) {
return info, nil 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, func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64,
length uint64, targetType string, params string) { length uint64, targetType string, params string) {
@ -394,6 +403,17 @@ func getInfo(name string) (*Info, error) {
return task.GetInfo() 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) { func getStatus(name string) (uint64, uint64, string, string, error) {
task, err := createTask(DeviceStatus, name) task, err := createTask(DeviceStatus, name)
if task == nil { if task == nil {

View file

@ -93,6 +93,7 @@ var (
DmTaskCreate = dmTaskCreateFct DmTaskCreate = dmTaskCreateFct
DmTaskDestroy = dmTaskDestroyFct DmTaskDestroy = dmTaskDestroyFct
DmTaskGetInfo = dmTaskGetInfoFct DmTaskGetInfo = dmTaskGetInfoFct
DmTaskGetDriverVersion = dmTaskGetDriverVersionFct
DmTaskRun = dmTaskRunFct DmTaskRun = dmTaskRunFct
DmTaskSetAddNode = dmTaskSetAddNodeFct DmTaskSetAddNode = dmTaskSetAddNodeFct
DmTaskSetCookie = dmTaskSetCookieFct DmTaskSetCookie = dmTaskSetCookieFct
@ -184,6 +185,16 @@ func dmTaskGetInfoFct(task *CDmTask, info *Info) int {
return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo)) 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 { func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
var ( var (
Cstart, Clength C.uint64_t Cstart, Clength C.uint64_t

View file

@ -26,8 +26,8 @@ type Driver struct {
home string home string
} }
func Init(home string) (graphdriver.Driver, error) { func Init(home string, options []string) (graphdriver.Driver, error) {
deviceSet, err := NewDeviceSet(home, true) deviceSet, err := NewDeviceSet(home, true, options)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -3,6 +3,8 @@
package devmapper package devmapper
import ( import (
"bytes"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"syscall" "syscall"
@ -27,3 +29,58 @@ func Mounted(mountpoint string) (bool, error) {
parentSt := parent.Sys().(*syscall.Stat_t) parentSt := parent.Sys().(*syscall.Stat_t)
return mntpointSt.Dev != parentSt.Dev, nil 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
}

View file

@ -15,7 +15,7 @@ const (
FsMagicAufs = FsMagic(0x61756673) FsMagicAufs = FsMagic(0x61756673)
) )
type InitFunc func(root string) (Driver, error) type InitFunc func(root string, options []string) (Driver, error)
type Driver interface { type Driver interface {
String() string String() string
@ -69,23 +69,23 @@ func Register(name string, initFunc InitFunc) error {
return nil return nil
} }
func GetDriver(name, home string) (Driver, error) { func GetDriver(name, home string, options []string) (Driver, error) {
if initFunc, exists := drivers[name]; exists { if initFunc, exists := drivers[name]; exists {
return initFunc(path.Join(home, name)) return initFunc(path.Join(home, name), options)
} }
return nil, ErrNotSupported 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} { for _, name := range []string{os.Getenv("DOCKER_DRIVER"), DefaultDriver} {
if name != "" { if name != "" {
return GetDriver(name, root) return GetDriver(name, root, options)
} }
} }
// Check for priority drivers first // Check for priority drivers first
for _, name := range priority { for _, name := range priority {
driver, err = GetDriver(name, root) driver, err = GetDriver(name, root, options)
if err != nil { if err != nil {
if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS { if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS {
continue continue
@ -97,7 +97,7 @@ func New(root string) (driver Driver, err error) {
// Check all registered drivers if no priority driver is found // Check all registered drivers if no priority driver is found
for _, initFunc := range drivers { 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 { if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS {
continue continue
} }

View file

@ -29,7 +29,7 @@ func newDriver(t *testing.T, name string) *Driver {
t.Fatal(err) t.Fatal(err)
} }
d, err := graphdriver.GetDriver(name, root) d, err := graphdriver.GetDriver(name, root, nil)
if err != nil { if err != nil {
if err == graphdriver.ErrNotSupported || err == graphdriver.ErrPrerequisites { if err == graphdriver.ErrNotSupported || err == graphdriver.ErrPrerequisites {
t.Skip("Driver %s not supported", name) t.Skip("Driver %s not supported", name)

View file

@ -12,7 +12,7 @@ func init() {
graphdriver.Register("vfs", Init) graphdriver.Register("vfs", Init)
} }
func Init(home string) (graphdriver.Driver, error) { func Init(home string, options []string) (graphdriver.Driver, error) {
d := &Driver{ d := &Driver{
home: home, home: home,
} }

View file

@ -25,6 +25,7 @@ type Config struct {
BridgeIP string BridgeIP string
InterContainerCommunication bool InterContainerCommunication bool
GraphDriver string GraphDriver string
GraphOptions []string
ExecDriver string ExecDriver string
Mtu int Mtu int
DisableNetwork bool DisableNetwork bool
@ -49,6 +50,10 @@ func ConfigFromJob(job *engine.Job) *Config {
ExecDriver: job.Getenv("ExecDriver"), ExecDriver: job.Getenv("ExecDriver"),
EnableSelinuxSupport: job.GetenvBool("EnableSelinuxSupport"), EnableSelinuxSupport: job.GetenvBool("EnableSelinuxSupport"),
} }
if graphOpts := job.GetenvList("GraphOptions"); graphOpts != nil {
config.GraphOptions = graphOpts
}
if dns := job.GetenvList("Dns"); dns != nil { if dns := job.GetenvList("Dns"); dns != nil {
config.Dns = dns config.Dns = dns
} }

View file

@ -41,6 +41,7 @@ func main() {
var ( var (
flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit") flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit")
flDaemon = flag.Bool([]string{"d", "-daemon"}, false, "Enable daemon mode") flDaemon = flag.Bool([]string{"d", "-daemon"}, false, "Enable daemon mode")
flGraphOpts opts.ListOpts
flDebug = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode") flDebug = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode")
flAutoRestart = flag.Bool([]string{"r", "-restart"}, true, "Restart previously running containers") 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") 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(&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(&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(&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() flag.Parse()
@ -156,6 +158,7 @@ func main() {
job.Setenv("DefaultIp", *flDefaultIp) job.Setenv("DefaultIp", *flDefaultIp)
job.SetenvBool("InterContainerCommunication", *flInterContainerComm) job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
job.Setenv("GraphDriver", *flGraphDriver) job.Setenv("GraphDriver", *flGraphDriver)
job.SetenvList("GraphOptions", flGraphOpts.GetAll())
job.Setenv("ExecDriver", *flExecDriver) job.Setenv("ExecDriver", *flExecDriver)
job.SetenvInt("Mtu", *flMtu) job.SetenvInt("Mtu", *flMtu)
job.SetenvBool("EnableSelinuxSupport", *flSelinuxEnabled) job.SetenvBool("EnableSelinuxSupport", *flSelinuxEnabled)

View file

@ -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 -p, --pidfile="/var/run/docker.pid" Path to use for daemon PID file
-r, --restart=true Restart previously running containers -r, --restart=true Restart previously running containers
-s, --storage-driver="" Force the docker runtime to use a specific storage driver -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 --selinux-enabled=false Enable selinux support
--tls=false Use TLS; implied by tls-verify flags --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 --tlscacert="/home/sven/.docker/ca.pem" Trust only remotes providing a certificate signed by the CA given here

View file

@ -36,7 +36,7 @@ func fakeTar() (io.Reader, error) {
} }
func mkTestTagStore(root string, t *testing.T) *TagStore { func mkTestTagStore(root string, t *testing.T) *TagStore {
driver, err := graphdriver.New(root) driver, err := graphdriver.New(root, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -293,7 +293,7 @@ func tempGraph(t *testing.T) (*graph.Graph, graphdriver.Driver) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
driver, err := graphdriver.New(tmp) driver, err := graphdriver.New(tmp, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -21,6 +21,42 @@ func HumanSize(size int64) string {
return fmt.Sprintf("%.4g %s", sizef, units[i]) 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 // Parses a human-readable string representing an amount of RAM
// in bytes, kibibytes, mebibytes or gibibytes, and returns the // in bytes, kibibytes, mebibytes or gibibytes, and returns the
// number of bytes, or -1 if the string is unparseable. // number of bytes, or -1 if the string is unparseable.

View file

@ -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) { func TestRAMInBytes(t *testing.T) {
assertRAMInBytes(t, "32", false, 32) assertRAMInBytes(t, "32", false, 32)
assertRAMInBytes(t, "32b", false, 32) assertRAMInBytes(t, "32b", false, 32)