From b16decfccfdb0749c490be9272cb7b4789be87b4 Mon Sep 17 00:00:00 2001 From: Shishir Mahajan Date: Sun, 20 Mar 2016 00:42:58 -0400 Subject: [PATCH] CLI flag for docker create(run) to change block device size. Signed-off-by: Shishir Mahajan --- contrib/docker-device-tool/device_tool.go | 2 +- daemon/create.go | 4 +- daemon/graphdriver/aufs/aufs.go | 7 ++- daemon/graphdriver/aufs/aufs_test.go | 58 +++++++++---------- daemon/graphdriver/btrfs/btrfs.go | 7 ++- daemon/graphdriver/btrfs/btrfs_test.go | 2 +- daemon/graphdriver/devmapper/deviceset.go | 54 +++++++++++++++-- daemon/graphdriver/devmapper/driver.go | 4 +- daemon/graphdriver/driver.go | 2 +- .../graphdriver/graphtest/graphtest_unix.go | 6 +- daemon/graphdriver/overlay/overlay.go | 7 ++- daemon/graphdriver/proxy.go | 2 +- daemon/graphdriver/vfs/driver.go | 6 +- daemon/graphdriver/windows/windows.go | 8 ++- daemon/graphdriver/zfs/zfs.go | 6 +- distribution/xfer/download_test.go | 2 +- docs/reference/api/docker_remote_api.md | 2 + docs/reference/api/docker_remote_api_v1.24.md | 4 ++ docs/reference/commandline/create.md | 8 +++ docs/reference/commandline/run.md | 8 +++ integration-cli/docker_cli_create_test.go | 21 +++++++ ...cker_cli_external_graphdriver_unix_test.go | 2 +- layer/layer.go | 2 +- layer/layer_store.go | 12 ++-- layer/layer_test.go | 8 +-- layer/migration_test.go | 12 ++-- layer/mount_test.go | 6 +- man/docker-create.1.md | 8 +++ man/docker-run.1.md | 8 +++ runconfig/opts/parse.go | 22 +++++++ 30 files changed, 228 insertions(+), 72 deletions(-) diff --git a/contrib/docker-device-tool/device_tool.go b/contrib/docker-device-tool/device_tool.go index cb538f28fd8ca..73e1bfdf5ba10 100644 --- a/contrib/docker-device-tool/device_tool.go +++ b/contrib/docker-device-tool/device_tool.go @@ -137,7 +137,7 @@ func main() { usage() } - err := devices.AddDevice(args[1], args[2]) + err := devices.AddDevice(args[1], args[2], nil) if err != nil { fmt.Println("Can't create snap device: ", err) os.Exit(1) diff --git a/daemon/create.go b/daemon/create.go index 34f0aa2d6c4ef..9246453a0a484 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -84,6 +84,8 @@ func (daemon *Daemon) create(params types.ContainerCreateConfig) (retC *containe return nil, err } + container.HostConfig.StorageOpt = params.HostConfig.StorageOpt + // Set RWLayer for container after mount labels have been set if err := daemon.setRWLayer(container); err != nil { return nil, err @@ -156,7 +158,7 @@ func (daemon *Daemon) setRWLayer(container *container.Container) error { } layerID = img.RootFS.ChainID() } - rwLayer, err := daemon.layerStore.CreateRWLayer(container.ID, layerID, container.MountLabel, daemon.setupInitLayer) + rwLayer, err := daemon.layerStore.CreateRWLayer(container.ID, layerID, container.MountLabel, daemon.setupInitLayer, container.HostConfig.StorageOpt) if err != nil { return err } diff --git a/daemon/graphdriver/aufs/aufs.go b/daemon/graphdriver/aufs/aufs.go index ec9454e72af5c..4209cd37d0d15 100644 --- a/daemon/graphdriver/aufs/aufs.go +++ b/daemon/graphdriver/aufs/aufs.go @@ -195,7 +195,12 @@ func (a *Driver) Exists(id string) bool { // Create three folders for each id // mnt, layers, and diff -func (a *Driver) Create(id, parent, mountLabel string) error { +func (a *Driver) Create(id, parent, mountLabel string, storageOpt map[string]string) error { + + if len(storageOpt) != 0 { + return fmt.Errorf("--storage-opt is not supported for aufs") + } + if err := a.createDirsFor(id); err != nil { return err } diff --git a/daemon/graphdriver/aufs/aufs_test.go b/daemon/graphdriver/aufs/aufs_test.go index b0ddf89a2cec0..0ee9aa12019d4 100644 --- a/daemon/graphdriver/aufs/aufs_test.go +++ b/daemon/graphdriver/aufs/aufs_test.go @@ -101,7 +101,7 @@ func TestCreateNewDir(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", "", ""); err != nil { + if err := d.Create("1", "", "", nil); err != nil { t.Fatal(err) } } @@ -110,7 +110,7 @@ func TestCreateNewDirStructure(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", "", ""); err != nil { + if err := d.Create("1", "", "", nil); err != nil { t.Fatal(err) } @@ -131,7 +131,7 @@ func TestRemoveImage(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", "", ""); err != nil { + if err := d.Create("1", "", "", nil); err != nil { t.Fatal(err) } @@ -156,7 +156,7 @@ func TestGetWithoutParent(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", "", ""); err != nil { + if err := d.Create("1", "", "", nil); err != nil { t.Fatal(err) } @@ -183,7 +183,7 @@ func TestCleanupWithDir(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", "", ""); err != nil { + if err := d.Create("1", "", "", nil); err != nil { t.Fatal(err) } @@ -196,7 +196,7 @@ func TestMountedFalseResponse(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", "", ""); err != nil { + if err := d.Create("1", "", "", nil); err != nil { t.Fatal(err) } @@ -215,10 +215,10 @@ func TestMountedTrueReponse(t *testing.T) { defer os.RemoveAll(tmp) defer d.Cleanup() - if err := d.Create("1", "", ""); err != nil { + if err := d.Create("1", "", "", nil); err != nil { t.Fatal(err) } - if err := d.Create("2", "1", ""); err != nil { + if err := d.Create("2", "1", "", nil); err != nil { t.Fatal(err) } @@ -241,10 +241,10 @@ func TestMountWithParent(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", "", ""); err != nil { + if err := d.Create("1", "", "", nil); err != nil { t.Fatal(err) } - if err := d.Create("2", "1", ""); err != nil { + if err := d.Create("2", "1", "", nil); err != nil { t.Fatal(err) } @@ -272,10 +272,10 @@ func TestRemoveMountedDir(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", "", ""); err != nil { + if err := d.Create("1", "", "", nil); err != nil { t.Fatal(err) } - if err := d.Create("2", "1", ""); err != nil { + if err := d.Create("2", "1", "", nil); err != nil { t.Fatal(err) } @@ -311,7 +311,7 @@ func TestCreateWithInvalidParent(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", "docker", ""); err == nil { + if err := d.Create("1", "docker", "", nil); err == nil { t.Fatalf("Error should not be nil with parent does not exist") } } @@ -320,7 +320,7 @@ func TestGetDiff(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", "", ""); err != nil { + if err := d.Create("1", "", "", nil); err != nil { t.Fatal(err) } @@ -354,10 +354,10 @@ func TestChanges(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", "", ""); err != nil { + if err := d.Create("1", "", "", nil); err != nil { t.Fatal(err) } - if err := d.Create("2", "1", ""); err != nil { + if err := d.Create("2", "1", "", nil); err != nil { t.Fatal(err) } @@ -403,7 +403,7 @@ func TestChanges(t *testing.T) { t.Fatalf("Change kind should be ChangeAdd got %s", change.Kind) } - if err := d.Create("3", "2", ""); err != nil { + if err := d.Create("3", "2", "", nil); err != nil { t.Fatal(err) } mntPoint, err = d.Get("3", "") @@ -448,7 +448,7 @@ func TestDiffSize(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", "", ""); err != nil { + if err := d.Create("1", "", "", nil); err != nil { t.Fatal(err) } @@ -490,7 +490,7 @@ func TestChildDiffSize(t *testing.T) { defer os.RemoveAll(tmp) defer d.Cleanup() - if err := d.Create("1", "", ""); err != nil { + if err := d.Create("1", "", "", nil); err != nil { t.Fatal(err) } @@ -526,7 +526,7 @@ func TestChildDiffSize(t *testing.T) { t.Fatalf("Expected size to be %d got %d", size, diffSize) } - if err := d.Create("2", "1", ""); err != nil { + if err := d.Create("2", "1", "", nil); err != nil { t.Fatal(err) } @@ -545,7 +545,7 @@ func TestExists(t *testing.T) { defer os.RemoveAll(tmp) defer d.Cleanup() - if err := d.Create("1", "", ""); err != nil { + if err := d.Create("1", "", "", nil); err != nil { t.Fatal(err) } @@ -563,7 +563,7 @@ func TestStatus(t *testing.T) { defer os.RemoveAll(tmp) defer d.Cleanup() - if err := d.Create("1", "", ""); err != nil { + if err := d.Create("1", "", "", nil); err != nil { t.Fatal(err) } @@ -592,7 +592,7 @@ func TestApplyDiff(t *testing.T) { defer os.RemoveAll(tmp) defer d.Cleanup() - if err := d.Create("1", "", ""); err != nil { + if err := d.Create("1", "", "", nil); err != nil { t.Fatal(err) } @@ -618,10 +618,10 @@ func TestApplyDiff(t *testing.T) { t.Fatal(err) } - if err := d.Create("2", "", ""); err != nil { + if err := d.Create("2", "", "", nil); err != nil { t.Fatal(err) } - if err := d.Create("3", "2", ""); err != nil { + if err := d.Create("3", "2", "", nil); err != nil { t.Fatal(err) } @@ -671,7 +671,7 @@ func testMountMoreThan42Layers(t *testing.T, mountPath string) { } current = hash(current) - if err := d.Create(current, parent, ""); err != nil { + if err := d.Create(current, parent, "", nil); err != nil { t.Logf("Current layer %d", i) t.Error(err) } @@ -750,11 +750,11 @@ func BenchmarkConcurrentAccess(b *testing.B) { ids = append(ids, stringid.GenerateNonCryptoID()) } - if err := d.Create(ids[0], "", ""); err != nil { + if err := d.Create(ids[0], "", "", nil); err != nil { b.Fatal(err) } - if err := d.Create(ids[1], ids[0], ""); err != nil { + if err := d.Create(ids[1], ids[0], "", nil); err != nil { b.Fatal(err) } @@ -770,7 +770,7 @@ func BenchmarkConcurrentAccess(b *testing.B) { for _, id := range ids { go func(id string) { defer outerGroup.Done() - if err := d.Create(id, parent, ""); err != nil { + if err := d.Create(id, parent, "", nil); err != nil { b.Logf("Create %s failed", id) chErr <- err return diff --git a/daemon/graphdriver/btrfs/btrfs.go b/daemon/graphdriver/btrfs/btrfs.go index 5ca86a5b6f648..9d69474b1c2b8 100644 --- a/daemon/graphdriver/btrfs/btrfs.go +++ b/daemon/graphdriver/btrfs/btrfs.go @@ -242,7 +242,12 @@ func (d *Driver) subvolumesDirID(id string) string { } // Create the filesystem with given id. -func (d *Driver) Create(id, parent, mountLabel string) error { +func (d *Driver) Create(id, parent, mountLabel string, storageOpt map[string]string) error { + + if len(storageOpt) != 0 { + return fmt.Errorf("--storage-opt is not supported for btrfs") + } + subvolumes := path.Join(d.home, "subvolumes") rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) if err != nil { diff --git a/daemon/graphdriver/btrfs/btrfs_test.go b/daemon/graphdriver/btrfs/btrfs_test.go index 7494218d61544..7d708ca41c03d 100644 --- a/daemon/graphdriver/btrfs/btrfs_test.go +++ b/daemon/graphdriver/btrfs/btrfs_test.go @@ -30,7 +30,7 @@ func TestBtrfsCreateSnap(t *testing.T) { func TestBtrfsSubvolDelete(t *testing.T) { d := graphtest.GetDriver(t, "btrfs") - if err := d.Create("test", "", ""); err != nil { + if err := d.Create("test", "", "", nil); err != nil { t.Fatal(err) } defer graphtest.PutDriver(t) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index cb3bf742a087b..c40d20fbd3e87 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -839,7 +839,7 @@ func (devices *DeviceSet) createRegisterDevice(hash string) (*devInfo, error) { return info, nil } -func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *devInfo) error { +func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *devInfo, size uint64) error { if err := devices.poolHasFreeSpace(); err != nil { return err } @@ -878,7 +878,7 @@ func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *devInf break } - if _, err := devices.registerDevice(deviceID, hash, baseInfo.Size, devices.OpenTransactionID); err != nil { + if _, err := devices.registerDevice(deviceID, hash, size, devices.OpenTransactionID); err != nil { devicemapper.DeleteDevice(devices.getPoolDevName(), deviceID) devices.markDeviceIDFree(deviceID) logrus.Debugf("devmapper: Error registering device: %s", err) @@ -1830,7 +1830,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { } // AddDevice adds a device and registers in the hash. -func (devices *DeviceSet) AddDevice(hash, baseHash string) error { +func (devices *DeviceSet) AddDevice(hash, baseHash string, storageOpt map[string]string) error { logrus.Debugf("devmapper: AddDevice(hash=%s basehash=%s)", hash, baseHash) defer logrus.Debugf("devmapper: AddDevice(hash=%s basehash=%s) END", hash, baseHash) @@ -1856,10 +1856,56 @@ func (devices *DeviceSet) AddDevice(hash, baseHash string) error { return fmt.Errorf("devmapper: device %s already exists. Deleted=%v", hash, info.Deleted) } - if err := devices.createRegisterSnapDevice(hash, baseInfo); err != nil { + devinfo := &devInfo{} + + if err := devices.parseStorageOpt(storageOpt, devinfo); err != nil { + return err + } + + if devinfo.Size == 0 { + devinfo.Size = baseInfo.Size + } + + if devinfo.Size < baseInfo.Size { + return fmt.Errorf("devmapper: Container size cannot be smaller than %s", units.HumanSize(float64(baseInfo.Size))) + } + + if err := devices.createRegisterSnapDevice(hash, baseInfo, devinfo.Size); err != nil { return err } + // Grow the container rootfs. + if devinfo.Size > 0 { + info, err := devices.lookupDevice(hash) + if err != nil { + return err + } + + if err := devices.growFS(info); err != nil { + return err + } + } + + return nil +} + +func (devices *DeviceSet) parseStorageOpt(storageOpt map[string]string, devinfo *devInfo) error { + + // Read size to change the block device size per container. + for key, val := range storageOpt { + key := strings.ToLower(key) + switch key { + case "size": + size, err := units.RAMInBytes(val) + if err != nil { + return err + } + devinfo.Size = uint64(size) + default: + return fmt.Errorf("Unknown option %s", key) + } + } + return nil } diff --git a/daemon/graphdriver/devmapper/driver.go b/daemon/graphdriver/devmapper/driver.go index 7de6907c80e0d..97366b4a71fb4 100644 --- a/daemon/graphdriver/devmapper/driver.go +++ b/daemon/graphdriver/devmapper/driver.go @@ -118,8 +118,8 @@ func (d *Driver) Cleanup() error { } // Create adds a device with a given id and the parent. -func (d *Driver) Create(id, parent, mountLabel string) error { - if err := d.DeviceSet.AddDevice(id, parent); err != nil { +func (d *Driver) Create(id, parent, mountLabel string, storageOpt map[string]string) error { + if err := d.DeviceSet.AddDevice(id, parent, storageOpt); err != nil { return err } diff --git a/daemon/graphdriver/driver.go b/daemon/graphdriver/driver.go index ced960b421139..a3b912f29ace5 100644 --- a/daemon/graphdriver/driver.go +++ b/daemon/graphdriver/driver.go @@ -48,7 +48,7 @@ type ProtoDriver interface { String() string // Create creates a new, empty, filesystem layer with the // specified id and parent and mountLabel. Parent and mountLabel may be "". - Create(id, parent, mountLabel string) error + Create(id, parent, mountLabel string, storageOpt map[string]string) error // Remove attempts to remove the filesystem layer with this id. Remove(id string) error // Get returns the mountpoint for the layered filesystem referred diff --git a/daemon/graphdriver/graphtest/graphtest_unix.go b/daemon/graphdriver/graphtest/graphtest_unix.go index 534f2e586be5e..6ea3ed04222a7 100644 --- a/daemon/graphdriver/graphtest/graphtest_unix.go +++ b/daemon/graphdriver/graphtest/graphtest_unix.go @@ -177,7 +177,7 @@ func DriverTestCreateEmpty(t *testing.T, drivername string) { driver := GetDriver(t, drivername) defer PutDriver(t) - if err := driver.Create("empty", "", ""); err != nil { + if err := driver.Create("empty", "", "", nil); err != nil { t.Fatal(err) } @@ -215,7 +215,7 @@ func createBase(t *testing.T, driver graphdriver.Driver, name string) { oldmask := syscall.Umask(0) defer syscall.Umask(oldmask) - if err := driver.Create(name, "", ""); err != nil { + if err := driver.Create(name, "", "", nil); err != nil { t.Fatal(err) } @@ -283,7 +283,7 @@ func DriverTestCreateSnap(t *testing.T, drivername string) { createBase(t, driver, "Base") - if err := driver.Create("Snap", "Base", ""); err != nil { + if err := driver.Create("Snap", "Base", "", nil); err != nil { t.Fatal(err) } diff --git a/daemon/graphdriver/overlay/overlay.go b/daemon/graphdriver/overlay/overlay.go index 476b7899c9876..a1cc1451b5dff 100644 --- a/daemon/graphdriver/overlay/overlay.go +++ b/daemon/graphdriver/overlay/overlay.go @@ -222,7 +222,12 @@ func (d *Driver) Cleanup() error { // Create is used to create the upper, lower, and merge directories required for overlay fs for a given id. // The parent filesystem is used to configure these directories for the overlay. -func (d *Driver) Create(id, parent, mountLabel string) (retErr error) { +func (d *Driver) Create(id, parent, mountLabel string, storageOpt map[string]string) (retErr error) { + + if len(storageOpt) != 0 { + return fmt.Errorf("--storage-opt is not supported for overlay") + } + dir := d.dir(id) rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) diff --git a/daemon/graphdriver/proxy.go b/daemon/graphdriver/proxy.go index 28657fef16c4d..df31c01dcb918 100644 --- a/daemon/graphdriver/proxy.go +++ b/daemon/graphdriver/proxy.go @@ -54,7 +54,7 @@ func (d *graphDriverProxy) String() string { return d.name } -func (d *graphDriverProxy) Create(id, parent, mountLabel string) error { +func (d *graphDriverProxy) Create(id, parent, mountLabel string, storageOpt map[string]string) error { args := &graphDriverRequest{ ID: id, Parent: parent, diff --git a/daemon/graphdriver/vfs/driver.go b/daemon/graphdriver/vfs/driver.go index 00d9f8eca8f75..8094b3e9005f4 100644 --- a/daemon/graphdriver/vfs/driver.go +++ b/daemon/graphdriver/vfs/driver.go @@ -69,7 +69,11 @@ func (d *Driver) Cleanup() error { } // Create prepares the filesystem for the VFS driver and copies the directory for the given id under the parent. -func (d *Driver) Create(id, parent, mountLabel string) error { +func (d *Driver) Create(id, parent, mountLabel string, storageOpt map[string]string) error { + if len(storageOpt) != 0 { + return fmt.Errorf("--storage-opt is not supported for vfs") + } + dir := d.dir(id) rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) if err != nil { diff --git a/daemon/graphdriver/windows/windows.go b/daemon/graphdriver/windows/windows.go index dd659dad0a590..d1c6cc920bec8 100644 --- a/daemon/graphdriver/windows/windows.go +++ b/daemon/graphdriver/windows/windows.go @@ -107,7 +107,11 @@ func (d *Driver) Exists(id string) bool { } // Create creates a new layer with the given id. -func (d *Driver) Create(id, parent, mountLabel string) error { +func (d *Driver) Create(id, parent, mountLabel string, storageOpt map[string]string) error { + if len(storageOpt) != 0 { + return fmt.Errorf("--storage-opt is not supported for windows") + } + rPId, err := d.resolveID(parent) if err != nil { return err @@ -432,7 +436,7 @@ func (d *Driver) GetCustomImageInfos() ([]CustomImageInfo, error) { h := sha512.Sum384([]byte(folderName)) id := fmt.Sprintf("%x", h[:32]) - if err := d.Create(id, "", ""); err != nil { + if err := d.Create(id, "", "", nil); err != nil { return nil, err } // Create the alternate ID file. diff --git a/daemon/graphdriver/zfs/zfs.go b/daemon/graphdriver/zfs/zfs.go index e92045bd830ff..90347dd02fdae 100644 --- a/daemon/graphdriver/zfs/zfs.go +++ b/daemon/graphdriver/zfs/zfs.go @@ -241,7 +241,11 @@ func (d *Driver) mountPath(id string) string { } // Create prepares the dataset and filesystem for the ZFS driver for the given id under the parent. -func (d *Driver) Create(id string, parent string, mountLabel string) error { +func (d *Driver) Create(id string, parent string, mountLabel string, storageOpt map[string]string) error { + if len(storageOpt) != 0 { + return fmt.Errorf("--storage-opt is not supported for zfs") + } + err := d.create(id, parent) if err == nil { return nil diff --git a/distribution/xfer/download_test.go b/distribution/xfer/download_test.go index 2e4d724cd2002..330882f24f9e1 100644 --- a/distribution/xfer/download_test.go +++ b/distribution/xfer/download_test.go @@ -106,7 +106,7 @@ func (ls *mockLayerStore) Get(chainID layer.ChainID) (layer.Layer, error) { func (ls *mockLayerStore) Release(l layer.Layer) ([]layer.Metadata, error) { return []layer.Metadata{}, nil } -func (ls *mockLayerStore) CreateRWLayer(string, layer.ChainID, string, layer.MountInit) (layer.RWLayer, error) { +func (ls *mockLayerStore) CreateRWLayer(string, layer.ChainID, string, layer.MountInit, map[string]string) (layer.RWLayer, error) { return nil, errors.New("not implemented") } diff --git a/docs/reference/api/docker_remote_api.md b/docs/reference/api/docker_remote_api.md index 033aea63d0f8c..59eccf1e66caf 100644 --- a/docs/reference/api/docker_remote_api.md +++ b/docs/reference/api/docker_remote_api.md @@ -116,6 +116,8 @@ This section lists each version from latest to oldest. Each listing includes a [Docker Remote API v1.24](docker_remote_api_v1.24.md) documentation +* `POST /containers/create` now takes `StorageOpt` field. + ### v1.23 API changes [Docker Remote API v1.23](docker_remote_api_v1.23.md) documentation diff --git a/docs/reference/api/docker_remote_api_v1.24.md b/docs/reference/api/docker_remote_api_v1.24.md index 0341f3855b7a6..9ee3fbb0f2c3a 100644 --- a/docs/reference/api/docker_remote_api_v1.24.md +++ b/docs/reference/api/docker_remote_api_v1.24.md @@ -325,6 +325,7 @@ Create a container "Ulimits": [{}], "LogConfig": { "Type": "json-file", "Config": {} }, "SecurityOpt": [""], + "StorageOpt": {}, "CgroupParent": "", "VolumeDriver": "", "ShmSize": 67108864 @@ -444,6 +445,8 @@ Json Parameters: `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard": 2048 }` - **SecurityOpt**: A list of string values to customize labels for MLS systems, such as SELinux. + - **StorageOpt**: Storage driver options per container. Options can be passed in the form + `{"size":"120G"}` - **LogConfig** - Log configuration for the container, specified as a JSON object in the form `{ "Type": "", "Config": {"key1": "val1"}}`. Available types: `json-file`, `syslog`, `journald`, `gelf`, `fluentd`, `awslogs`, `splunk`, `etwlogs`, `none`. @@ -568,6 +571,7 @@ Return low-level information on the container `id` "Type": "json-file" }, "SecurityOpt": null, + "StorageOpt": null, "VolumesFrom": null, "Ulimits": [{}], "VolumeDriver": "", diff --git a/docs/reference/commandline/create.md b/docs/reference/commandline/create.md index 70c0e4c35addb..0540b3f34ad95 100644 --- a/docs/reference/commandline/create.md +++ b/docs/reference/commandline/create.md @@ -81,6 +81,7 @@ Creates a new container. --security-opt=[] Security options --stop-signal="SIGTERM" Signal to stop a container --shm-size=[] Size of `/dev/shm`. The format is ``. `number` must be greater than `0`. Unit is optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes), or `g` (gigabytes). If you omit the unit, the system uses bytes. If you omit the size entirely, the system uses `64m`. + --storage-opt=[] Set storage driver options per container -t, --tty Allocate a pseudo-TTY -u, --user="" Username or UID --userns="" Container user namespace @@ -145,6 +146,13 @@ then be used from the subsequent container: drwx--S--- 2 1000 staff 460 Dec 5 00:51 .ssh drwxr-xr-x 32 1000 staff 1140 Dec 5 04:01 docker +Set storage driver options per container. + + $ docker create -it --storage-opt size=120G fedora /bin/bash + +This (size) will allow to set the container rootfs size to 120G at creation time. +User cannot pass a size less than the Default BaseFS Size. + ### Specify isolation technology for container (--isolation) This option is useful in situations where you are running Docker containers on diff --git a/docs/reference/commandline/run.md b/docs/reference/commandline/run.md index 97553a67dcda3..00438bebad3d3 100644 --- a/docs/reference/commandline/run.md +++ b/docs/reference/commandline/run.md @@ -83,6 +83,7 @@ parent = "smn_cli" --security-opt=[] Security Options --sig-proxy=true Proxy received signals to the process --stop-signal="SIGTERM" Signal to stop a container + --storage-opt=[] Set storage driver options per container -t, --tty Allocate a pseudo-TTY -u, --user="" Username or UID (format: [:]) --userns="" Container user namespace @@ -167,6 +168,13 @@ flag exists to allow special use-cases, like running Docker within Docker. The `-w` lets the command being executed inside directory given, here `/path/to/dir/`. If the path does not exists it is created inside the container. +### Set storage driver options per container + + $ docker create -it --storage-opt size=120G fedora /bin/bash + +This (size) will allow to set the container rootfs size to 120G at creation time. +User cannot pass a size less than the Default BaseFS Size. + ### Mount tmpfs (--tmpfs) $ docker run -d --tmpfs /run:rw,noexec,nosuid,size=65536k my_image diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go index 03a26d1d42771..23098da2b2bee 100644 --- a/integration-cli/docker_cli_create_test.go +++ b/integration-cli/docker_cli_create_test.go @@ -60,6 +60,27 @@ func (s *DockerSuite) TestCreateArgs(c *check.C) { } +// Make sure we can grow the container's rootfs at creation time. +func (s *DockerSuite) TestCreateGrowRootfs(c *check.C) { + testRequires(c, Devicemapper) + out, _ := dockerCmd(c, "create", "--storage-opt", "size=120G", "busybox") + + cleanedContainerID := strings.TrimSpace(out) + + inspectOut := inspectField(c, cleanedContainerID, "HostConfig.StorageOpt") + c.Assert(inspectOut, checker.Equals, "[size=120G]") +} + +// Make sure we cannot shrink the container's rootfs at creation time. +func (s *DockerSuite) TestCreateShrinkRootfs(c *check.C) { + testRequires(c, Devicemapper) + + // Ensure this fails + out, _, err := dockerCmdWithError("create", "--storage-opt", "size=80G", "busybox") + c.Assert(err, check.NotNil, check.Commentf(out)) + c.Assert(out, checker.Contains, "Container size cannot be smaller than") +} + // Make sure we can set hostconfig options too func (s *DockerSuite) TestCreateHostConfig(c *check.C) { out, _ := dockerCmd(c, "create", "-P", "busybox", "echo") diff --git a/integration-cli/docker_cli_external_graphdriver_unix_test.go b/integration-cli/docker_cli_external_graphdriver_unix_test.go index 9fd36ec9c114f..257c60f029b14 100644 --- a/integration-cli/docker_cli_external_graphdriver_unix_test.go +++ b/integration-cli/docker_cli_external_graphdriver_unix_test.go @@ -122,7 +122,7 @@ func (s *DockerExternalGraphdriverSuite) SetUpSuite(c *check.C) { if err := decReq(r.Body, &req, w); err != nil { return } - if err := driver.Create(req.ID, req.Parent, ""); err != nil { + if err := driver.Create(req.ID, req.Parent, "", nil); err != nil { respond(w, err) return } diff --git a/layer/layer.go b/layer/layer.go index be3fd8329c9f5..5d3b8c672a28f 100644 --- a/layer/layer.go +++ b/layer/layer.go @@ -171,7 +171,7 @@ type Store interface { Get(ChainID) (Layer, error) Release(Layer) ([]Metadata, error) - CreateRWLayer(id string, parent ChainID, mountLabel string, initFunc MountInit) (RWLayer, error) + CreateRWLayer(id string, parent ChainID, mountLabel string, initFunc MountInit, storageOpt map[string]string) (RWLayer, error) GetRWLayer(id string) (RWLayer, error) GetMountID(id string) (string, error) ReleaseRWLayer(RWLayer) ([]Metadata, error) diff --git a/layer/layer_store.go b/layer/layer_store.go index fa436f098ba00..d08fa624a8c6b 100644 --- a/layer/layer_store.go +++ b/layer/layer_store.go @@ -263,7 +263,7 @@ func (ls *layerStore) Register(ts io.Reader, parent ChainID) (Layer, error) { references: map[Layer]struct{}{}, } - if err = ls.driver.Create(layer.cacheID, pid, ""); err != nil { + if err = ls.driver.Create(layer.cacheID, pid, "", nil); err != nil { return nil, err } @@ -414,7 +414,7 @@ func (ls *layerStore) Release(l Layer) ([]Metadata, error) { return ls.releaseLayer(layer) } -func (ls *layerStore) CreateRWLayer(name string, parent ChainID, mountLabel string, initFunc MountInit) (RWLayer, error) { +func (ls *layerStore) CreateRWLayer(name string, parent ChainID, mountLabel string, initFunc MountInit, storageOpt map[string]string) (RWLayer, error) { ls.mountL.Lock() defer ls.mountL.Unlock() m, ok := ls.mounts[name] @@ -451,14 +451,14 @@ func (ls *layerStore) CreateRWLayer(name string, parent ChainID, mountLabel stri } if initFunc != nil { - pid, err = ls.initMount(m.mountID, pid, mountLabel, initFunc) + pid, err = ls.initMount(m.mountID, pid, mountLabel, initFunc, storageOpt) if err != nil { return nil, err } m.initID = pid } - if err = ls.driver.Create(m.mountID, pid, ""); err != nil { + if err = ls.driver.Create(m.mountID, pid, "", storageOpt); err != nil { return nil, err } @@ -561,14 +561,14 @@ func (ls *layerStore) saveMount(mount *mountedLayer) error { return nil } -func (ls *layerStore) initMount(graphID, parent, mountLabel string, initFunc MountInit) (string, error) { +func (ls *layerStore) initMount(graphID, parent, mountLabel string, initFunc MountInit, storageOpt map[string]string) (string, error) { // Use "-init" to maintain compatibility with graph drivers // which are expecting this layer with this special name. If all // graph drivers can be updated to not rely on knowing about this layer // then the initID should be randomly generated. initID := fmt.Sprintf("%s-init", graphID) - if err := ls.driver.Create(initID, parent, mountLabel); err != nil { + if err := ls.driver.Create(initID, parent, mountLabel, storageOpt); err != nil { return "", err } p, err := ls.driver.Get(initID, "") diff --git a/layer/layer_test.go b/layer/layer_test.go index 7fb792dcd84d8..85687cebb4988 100644 --- a/layer/layer_test.go +++ b/layer/layer_test.go @@ -84,7 +84,7 @@ type layerInit func(root string) error func createLayer(ls Store, parent ChainID, layerFunc layerInit) (Layer, error) { containerID := stringid.GenerateRandomID() - mount, err := ls.CreateRWLayer(containerID, parent, "", nil) + mount, err := ls.CreateRWLayer(containerID, parent, "", nil, nil) if err != nil { return nil, err } @@ -279,7 +279,7 @@ func TestMountAndRegister(t *testing.T) { size, _ := layer.Size() t.Logf("Layer size: %d", size) - mount2, err := ls.CreateRWLayer("new-test-mount", layer.ChainID(), "", nil) + mount2, err := ls.CreateRWLayer("new-test-mount", layer.ChainID(), "", nil, nil) if err != nil { t.Fatal(err) } @@ -387,7 +387,7 @@ func TestStoreRestore(t *testing.T) { t.Fatal(err) } - m, err := ls.CreateRWLayer("some-mount_name", layer3.ChainID(), "", nil) + m, err := ls.CreateRWLayer("some-mount_name", layer3.ChainID(), "", nil, nil) if err != nil { t.Fatal(err) } @@ -421,7 +421,7 @@ func TestStoreRestore(t *testing.T) { assertLayerEqual(t, layer3b, layer3) // Create again with same name, should return error - if _, err := ls2.CreateRWLayer("some-mount_name", layer3b.ChainID(), "", nil); err == nil { + if _, err := ls2.CreateRWLayer("some-mount_name", layer3b.ChainID(), "", nil, nil); err == nil { t.Fatal("Expected error creating mount with same name") } else if err != ErrMountNameConflict { t.Fatal(err) diff --git a/layer/migration_test.go b/layer/migration_test.go index 0ac73a4c64946..7e2e2a64892c7 100644 --- a/layer/migration_test.go +++ b/layer/migration_test.go @@ -78,7 +78,7 @@ func TestLayerMigration(t *testing.T) { } graphID1 := stringid.GenerateRandomID() - if err := graph.Create(graphID1, "", ""); err != nil { + if err := graph.Create(graphID1, "", "", nil); err != nil { t.Fatal(err) } if _, err := graph.ApplyDiff(graphID1, "", archive.Reader(bytes.NewReader(tar1))); err != nil { @@ -123,7 +123,7 @@ func TestLayerMigration(t *testing.T) { } graphID2 := stringid.GenerateRandomID() - if err := graph.Create(graphID2, graphID1, ""); err != nil { + if err := graph.Create(graphID2, graphID1, "", nil); err != nil { t.Fatal(err) } if _, err := graph.ApplyDiff(graphID2, graphID1, archive.Reader(bytes.NewReader(tar2))); err != nil { @@ -165,7 +165,7 @@ func tarFromFilesInGraph(graph graphdriver.Driver, graphID, parentID string, fil return nil, err } - if err := graph.Create(graphID, parentID, ""); err != nil { + if err := graph.Create(graphID, parentID, "", nil); err != nil { return nil, err } if _, err := graph.ApplyDiff(graphID, parentID, archive.Reader(bytes.NewReader(t))); err != nil { @@ -320,14 +320,14 @@ func TestMountMigration(t *testing.T) { containerID := stringid.GenerateRandomID() containerInit := fmt.Sprintf("%s-init", containerID) - if err := graph.Create(containerInit, graphID1, ""); err != nil { + if err := graph.Create(containerInit, graphID1, "", nil); err != nil { t.Fatal(err) } if _, err := graph.ApplyDiff(containerInit, graphID1, archive.Reader(bytes.NewReader(initTar))); err != nil { t.Fatal(err) } - if err := graph.Create(containerID, containerInit, ""); err != nil { + if err := graph.Create(containerID, containerInit, "", nil); err != nil { t.Fatal(err) } if _, err := graph.ApplyDiff(containerID, containerInit, archive.Reader(bytes.NewReader(mountTar))); err != nil { @@ -382,7 +382,7 @@ func TestMountMigration(t *testing.T) { assertActivityCount(t, rwLayer1, 1) - if _, err := ls.CreateRWLayer("migration-mount", layer1.ChainID(), "", nil); err == nil { + if _, err := ls.CreateRWLayer("migration-mount", layer1.ChainID(), "", nil, nil); err == nil { t.Fatal("Expected error creating mount with same name") } else if err != ErrMountNameConflict { t.Fatal(err) diff --git a/layer/mount_test.go b/layer/mount_test.go index 5967c2b9b5389..7a8637eae9172 100644 --- a/layer/mount_test.go +++ b/layer/mount_test.go @@ -32,7 +32,7 @@ func TestMountInit(t *testing.T) { return initfile.ApplyFile(root) } - m, err := ls.CreateRWLayer("fun-mount", layer.ChainID(), "", mountInit) + m, err := ls.CreateRWLayer("fun-mount", layer.ChainID(), "", mountInit, nil) if err != nil { t.Fatal(err) } @@ -89,7 +89,7 @@ func TestMountSize(t *testing.T) { return newTestFile("file-init", contentInit, 0777).ApplyFile(root) } - m, err := ls.CreateRWLayer("mount-size", layer.ChainID(), "", mountInit) + m, err := ls.CreateRWLayer("mount-size", layer.ChainID(), "", mountInit, nil) if err != nil { t.Fatal(err) } @@ -138,7 +138,7 @@ func TestMountChanges(t *testing.T) { return initfile.ApplyFile(root) } - m, err := ls.CreateRWLayer("mount-changes", layer.ChainID(), "", mountInit) + m, err := ls.CreateRWLayer("mount-changes", layer.ChainID(), "", mountInit, nil) if err != nil { t.Fatal(err) } diff --git a/man/docker-create.1.md b/man/docker-create.1.md index f12edb5483ea3..d3cb85c78c5e6 100644 --- a/man/docker-create.1.md +++ b/man/docker-create.1.md @@ -64,6 +64,7 @@ docker-create - Create a new container [**--read-only**] [**--restart**[=*RESTART*]] [**--security-opt**[=*[]*]] +[**--storage-opt**[=*[]*]] [**--stop-signal**[=*SIGNAL*]] [**--shm-size**[=*[]*]] [**-t**|**--tty**] @@ -325,6 +326,13 @@ unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap. "seccomp:unconfined" : Turn off seccomp confinement for the container "seccomp:profile.json : White listed syscalls seccomp Json file to be used as a seccomp filter +**--storage-opt**=[] + Storage driver options per container + + $ docker create -it --storage-opt size=120G fedora /bin/bash + + This (size) will allow to set the container rootfs size to 120G at creation time. User cannot pass a size less than the Default BaseFS Size. + **--stop-signal**=*SIGTERM* Signal to stop a container. Default is SIGTERM. diff --git a/man/docker-run.1.md b/man/docker-run.1.md index 57808f33a09de..e6757fc5126b1 100644 --- a/man/docker-run.1.md +++ b/man/docker-run.1.md @@ -67,6 +67,7 @@ docker-run - Run a command in a new container [**--restart**[=*RESTART*]] [**--rm**] [**--security-opt**[=*[]*]] +[**--storage-opt**[=*[]*]] [**--stop-signal**[=*SIGNAL*]] [**--shm-size**[=*[]*]] [**--sig-proxy**[=*true*]] @@ -476,6 +477,13 @@ its root filesystem mounted as read only prohibiting any writes. "apparmor=unconfined" : Turn off apparmor confinement for the container "apparmor=your-profile" : Set the apparmor confinement profile for the container +**--storage-opt**=[] + Storage driver options per container + + $ docker run -it --storage-opt size=120G fedora /bin/bash + + This (size) will allow to set the container rootfs size to 120G at creation time. User cannot pass a size less than the Default BaseFS Size. + **--stop-signal**=*SIGTERM* Signal to stop a container. Default is SIGTERM. diff --git a/runconfig/opts/parse.go b/runconfig/opts/parse.go index 6543b406dffb1..d30adcedc1ed0 100644 --- a/runconfig/opts/parse.go +++ b/runconfig/opts/parse.go @@ -55,6 +55,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host flCapDrop = opts.NewListOpts(nil) flGroupAdd = opts.NewListOpts(nil) flSecurityOpt = opts.NewListOpts(nil) + flStorageOpt = opts.NewListOpts(nil) flLabelsFile = opts.NewListOpts(nil) flLoggingOpts = opts.NewListOpts(nil) flPrivileged = cmd.Bool([]string{"-privileged"}, false, "Give extended privileges to this container") @@ -124,6 +125,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capabilities") cmd.Var(&flGroupAdd, []string{"-group-add"}, "Add additional groups to join") cmd.Var(&flSecurityOpt, []string{"-security-opt"}, "Security Options") + cmd.Var(&flStorageOpt, []string{"-storage-opt"}, "Set storage driver options per container") cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options") cmd.Var(&flLoggingOpts, []string{"-log-opt"}, "Log driver options") @@ -337,6 +339,11 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host return nil, nil, nil, cmd, err } + storageOpts, err := parseStorageOpts(flStorageOpt.GetAll()) + if err != nil { + return nil, nil, nil, cmd, err + } + resources := container.Resources{ CgroupParent: *flCgroupParent, Memory: flMemory, @@ -415,6 +422,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host GroupAdd: flGroupAdd.GetAll(), RestartPolicy: restartPolicy, SecurityOpt: securityOpts, + StorageOpt: storageOpts, ReadonlyRootfs: *flReadonlyRootfs, LogConfig: container.LogConfig{Type: *flLoggingDriver, Config: loggingOpts}, VolumeDriver: *flVolumeDriver, @@ -531,6 +539,20 @@ func parseSecurityOpts(securityOpts []string) ([]string, error) { return securityOpts, nil } +// parse storage options per container into a map +func parseStorageOpts(storageOpts []string) (map[string]string, error) { + m := make(map[string]string) + for _, option := range storageOpts { + if strings.Contains(option, "=") { + opt := strings.SplitN(option, "=", 2) + m[opt[0]] = opt[1] + } else { + return nil, fmt.Errorf("Invalid storage option.") + } + } + return m, nil +} + // ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect func ParseRestartPolicy(policy string) (container.RestartPolicy, error) { p := container.RestartPolicy{}