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
// 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
}

View file

@ -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

View file

@ -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)

View file

@ -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

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/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 {

View file

@ -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 {

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)

View file

@ -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,
}

View file

@ -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
}

View file

@ -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)

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

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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.

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) {
assertRAMInBytes(t, "32", false, 32)
assertRAMInBytes(t, "32b", false, 32)