Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
| ... | ... |
@@ -1,24 +1,35 @@ |
| 1 | 1 |
package mount |
| 2 | 2 |
|
| 3 |
+import ( |
|
| 4 |
+ "os" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 3 | 7 |
// Type represents the type of a mount. |
| 4 | 8 |
type Type string |
| 5 | 9 |
|
| 10 |
+// Type constants |
|
| 6 | 11 |
const ( |
| 7 |
- // TypeBind BIND |
|
| 12 |
+ // TypeBind is the type for mounting host dir |
|
| 8 | 13 |
TypeBind Type = "bind" |
| 9 |
- // TypeVolume VOLUME |
|
| 14 |
+ // TypeVolume is the type for remote storage volumes |
|
| 10 | 15 |
TypeVolume Type = "volume" |
| 16 |
+ // TypeTmpfs is the type for mounting tmpfs |
|
| 17 |
+ TypeTmpfs Type = "tmpfs" |
|
| 11 | 18 |
) |
| 12 | 19 |
|
| 13 | 20 |
// Mount represents a mount (volume). |
| 14 | 21 |
type Mount struct {
|
| 15 |
- Type Type `json:",omitempty"` |
|
| 22 |
+ Type Type `json:",omitempty"` |
|
| 23 |
+ // Source specifies the name of the mount. Depending on mount type, this |
|
| 24 |
+ // may be a volume name or a host path, or even ignored. |
|
| 25 |
+ // Source is not supported for tmpfs (must be an empty value) |
|
| 16 | 26 |
Source string `json:",omitempty"` |
| 17 | 27 |
Target string `json:",omitempty"` |
| 18 | 28 |
ReadOnly bool `json:",omitempty"` |
| 19 | 29 |
|
| 20 | 30 |
BindOptions *BindOptions `json:",omitempty"` |
| 21 | 31 |
VolumeOptions *VolumeOptions `json:",omitempty"` |
| 32 |
+ TmpfsOptions *TmpfsOptions `json:",omitempty"` |
|
| 22 | 33 |
} |
| 23 | 34 |
|
| 24 | 35 |
// Propagation represents the propagation of a mount. |
| ... | ... |
@@ -56,3 +67,37 @@ type Driver struct {
|
| 56 | 56 |
Name string `json:",omitempty"` |
| 57 | 57 |
Options map[string]string `json:",omitempty"` |
| 58 | 58 |
} |
| 59 |
+ |
|
| 60 |
+// TmpfsOptions defines options specific to mounts of type "tmpfs". |
|
| 61 |
+type TmpfsOptions struct {
|
|
| 62 |
+ // Size sets the size of the tmpfs, in bytes. |
|
| 63 |
+ // |
|
| 64 |
+ // This will be converted to an operating system specific value |
|
| 65 |
+ // depending on the host. For example, on linux, it will be convered to |
|
| 66 |
+ // use a 'k', 'm' or 'g' syntax. BSD, though not widely supported with |
|
| 67 |
+ // docker, uses a straight byte value. |
|
| 68 |
+ // |
|
| 69 |
+ // Percentages are not supported. |
|
| 70 |
+ SizeBytes int64 `json:",omitempty"` |
|
| 71 |
+ // Mode of the tmpfs upon creation |
|
| 72 |
+ Mode os.FileMode `json:",omitempty"` |
|
| 73 |
+ |
|
| 74 |
+ // TODO(stevvooe): There are several more tmpfs flags, specified in the |
|
| 75 |
+ // daemon, that are accepted. Only the most basic are added for now. |
|
| 76 |
+ // |
|
| 77 |
+ // From docker/docker/pkg/mount/flags.go: |
|
| 78 |
+ // |
|
| 79 |
+ // var validFlags = map[string]bool{
|
|
| 80 |
+ // "": true, |
|
| 81 |
+ // "size": true, X |
|
| 82 |
+ // "mode": true, X |
|
| 83 |
+ // "uid": true, |
|
| 84 |
+ // "gid": true, |
|
| 85 |
+ // "nr_inodes": true, |
|
| 86 |
+ // "nr_blocks": true, |
|
| 87 |
+ // "mpol": true, |
|
| 88 |
+ // } |
|
| 89 |
+ // |
|
| 90 |
+ // Some of these may be straightforward to add, but others, such as |
|
| 91 |
+ // uid/gid have implications in a clustered system. |
|
| 92 |
+} |
| ... | ... |
@@ -12,6 +12,7 @@ import ( |
| 12 | 12 |
|
| 13 | 13 |
"github.com/Sirupsen/logrus" |
| 14 | 14 |
containertypes "github.com/docker/docker/api/types/container" |
| 15 |
+ mounttypes "github.com/docker/docker/api/types/mount" |
|
| 15 | 16 |
"github.com/docker/docker/pkg/chrootarchive" |
| 16 | 17 |
"github.com/docker/docker/pkg/stringid" |
| 17 | 18 |
"github.com/docker/docker/pkg/symlink" |
| ... | ... |
@@ -406,7 +407,7 @@ func copyOwnership(source, destination string) error {
|
| 406 | 406 |
} |
| 407 | 407 |
|
| 408 | 408 |
// TmpfsMounts returns the list of tmpfs mounts |
| 409 |
-func (container *Container) TmpfsMounts() []Mount {
|
|
| 409 |
+func (container *Container) TmpfsMounts() ([]Mount, error) {
|
|
| 410 | 410 |
var mounts []Mount |
| 411 | 411 |
for dest, data := range container.HostConfig.Tmpfs {
|
| 412 | 412 |
mounts = append(mounts, Mount{
|
| ... | ... |
@@ -415,7 +416,20 @@ func (container *Container) TmpfsMounts() []Mount {
|
| 415 | 415 |
Data: data, |
| 416 | 416 |
}) |
| 417 | 417 |
} |
| 418 |
- return mounts |
|
| 418 |
+ for dest, mnt := range container.MountPoints {
|
|
| 419 |
+ if mnt.Type == mounttypes.TypeTmpfs {
|
|
| 420 |
+ data, err := volume.ConvertTmpfsOptions(mnt.Spec.TmpfsOptions) |
|
| 421 |
+ if err != nil {
|
|
| 422 |
+ return nil, err |
|
| 423 |
+ } |
|
| 424 |
+ mounts = append(mounts, Mount{
|
|
| 425 |
+ Source: "tmpfs", |
|
| 426 |
+ Destination: dest, |
|
| 427 |
+ Data: data, |
|
| 428 |
+ }) |
|
| 429 |
+ } |
|
| 430 |
+ } |
|
| 431 |
+ return mounts, nil |
|
| 419 | 432 |
} |
| 420 | 433 |
|
| 421 | 434 |
// cleanResourcePath cleans a resource path and prepares to combine with mnt path |
| ... | ... |
@@ -82,9 +82,9 @@ func (container *Container) UnmountVolumes(forceSyscall bool, volumeEventLog fun |
| 82 | 82 |
} |
| 83 | 83 |
|
| 84 | 84 |
// TmpfsMounts returns the list of tmpfs mounts |
| 85 |
-func (container *Container) TmpfsMounts() []Mount {
|
|
| 85 |
+func (container *Container) TmpfsMounts() ([]Mount, error) {
|
|
| 86 | 86 |
var mounts []Mount |
| 87 |
- return mounts |
|
| 87 |
+ return mounts, nil |
|
| 88 | 88 |
} |
| 89 | 89 |
|
| 90 | 90 |
// UpdateContainer updates configuration of a container |
| ... | ... |
@@ -473,7 +473,7 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c |
| 473 | 473 |
} |
| 474 | 474 |
|
| 475 | 475 |
if m.Source == "tmpfs" {
|
| 476 |
- data := c.HostConfig.Tmpfs[m.Destination] |
|
| 476 |
+ data := m.Data |
|
| 477 | 477 |
options := []string{"noexec", "nosuid", "nodev", string(volume.DefaultPropagationMode)}
|
| 478 | 478 |
if data != "" {
|
| 479 | 479 |
options = append(options, strings.Split(data, ",")...) |
| ... | ... |
@@ -707,7 +707,11 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
|
| 707 | 707 |
return nil, err |
| 708 | 708 |
} |
| 709 | 709 |
ms = append(ms, c.IpcMounts()...) |
| 710 |
- ms = append(ms, c.TmpfsMounts()...) |
|
| 710 |
+ tmpfsMounts, err := c.TmpfsMounts() |
|
| 711 |
+ if err != nil {
|
|
| 712 |
+ return nil, err |
|
| 713 |
+ } |
|
| 714 |
+ ms = append(ms, tmpfsMounts...) |
|
| 711 | 715 |
sort.Sort(mounts(ms)) |
| 712 | 716 |
if err := setMounts(daemon, &s, c, ms); err != nil {
|
| 713 | 717 |
return nil, fmt.Errorf("linux mounts: %v", err)
|
| ... | ... |
@@ -24,7 +24,11 @@ func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, er |
| 24 | 24 |
var mounts []container.Mount |
| 25 | 25 |
// TODO: tmpfs mounts should be part of Mountpoints |
| 26 | 26 |
tmpfsMounts := make(map[string]bool) |
| 27 |
- for _, m := range c.TmpfsMounts() {
|
|
| 27 |
+ tmpfsMountInfo, err := c.TmpfsMounts() |
|
| 28 |
+ if err != nil {
|
|
| 29 |
+ return nil, err |
|
| 30 |
+ } |
|
| 31 |
+ for _, m := range tmpfsMountInfo {
|
|
| 28 | 32 |
tmpfsMounts[m.Destination] = true |
| 29 | 33 |
} |
| 30 | 34 |
for _, m := range c.MountPoints {
|
| ... | ... |
@@ -139,7 +139,7 @@ This section lists each version from latest to oldest. Each listing includes a |
| 139 | 139 |
* `DELETE /volumes/(name)` now accepts a `force` query parameter to force removal of volumes that were already removed out of band by the volume driver plugin. |
| 140 | 140 |
* `POST /containers/create/` and `POST /containers/(name)/update` now validates restart policies. |
| 141 | 141 |
* `POST /containers/create` now validates IPAMConfig in NetworkingConfig, and returns error for invalid IPv4 and IPv6 addresses (`--ip` and `--ip6` in `docker create/run`). |
| 142 |
-* `POST /containers/create` now takes a `Mounts` field in `HostConfig` which replaces `Binds` and `Volumes`. *note*: `Binds` and `Volumes` are still available but are exclusive with `Mounts` |
|
| 142 |
+* `POST /containers/create` now takes a `Mounts` field in `HostConfig` which replaces `Binds`, `Volumes`, and `Tmpfs`. *note*: `Binds`, `Volumes`, and `Tmpfs` are still available and can be combined with `Mounts`. |
|
| 143 | 143 |
* `POST /build` now performs a preliminary validation of the `Dockerfile` before starting the build, and returns an error if the syntax is incorrect. Note that this change is _unversioned_ and applied to all API versions. |
| 144 | 144 |
* `POST /build` accepts `cachefrom` parameter to specify images used for build cache. |
| 145 | 145 |
* `GET /networks/` endpoint now correctly returns a list of *all* networks, |
| ... | ... |
@@ -511,10 +511,11 @@ Create a container |
| 511 | 511 |
- **Mounts** – Specification for mounts to be added to the container. |
| 512 | 512 |
- **Target** – Container path. |
| 513 | 513 |
- **Source** – Mount source (e.g. a volume name, a host path). |
| 514 |
- - **Type** – The mount type (`bind`, or `volume`). |
|
| 514 |
+ - **Type** – The mount type (`bind`, `volume`, or `tmpfs`). |
|
| 515 | 515 |
Available types (for the `Type` field): |
| 516 | 516 |
- **bind** - Mounts a file or directory from the host into the container. Must exist prior to creating the container. |
| 517 | 517 |
- **volume** - Creates a volume with the given name and options (or uses a pre-existing volume with the same name and options). These are **not** removed when the container is removed. |
| 518 |
+ - **tmpfs** - Create a tmpfs with the given options. The mount source cannot be specified for tmpfs. |
|
| 518 | 519 |
- **ReadOnly** – A boolean indicating whether the mount should be read-only. |
| 519 | 520 |
- **BindOptions** - Optional configuration for the `bind` type. |
| 520 | 521 |
- **Propagation** – A propagation mode with the value `[r]private`, `[r]shared`, or `[r]slave`. |
| ... | ... |
@@ -525,6 +526,9 @@ Create a container |
| 525 | 525 |
- **DriverConfig** – Map of driver-specific options. |
| 526 | 526 |
- **Name** - Name of the driver to use to create the volume. |
| 527 | 527 |
- **Options** - key/value map of driver specific options. |
| 528 |
+ - **TmpfsOptions** – Optional configuration for the `tmpfs` type. |
|
| 529 |
+ - **SizeBytes** – The size for the tmpfs mount in bytes. |
|
| 530 |
+ - **Mode** – The permission mode for the tmpfs mount in an integer. |
|
| 528 | 531 |
|
| 529 | 532 |
|
| 530 | 533 |
**Query parameters**: |
| ... | ... |
@@ -1569,13 +1569,80 @@ func (s *DockerSuite) TestContainersAPICreateMountsValidation(c *check.C) {
|
| 1569 | 1569 |
notExistPath := prefix + slash + "notexist" |
| 1570 | 1570 |
|
| 1571 | 1571 |
cases := []testCase{
|
| 1572 |
- {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "notreal", Target: destPath}}}}, http.StatusBadRequest, "mount type unknown"},
|
|
| 1573 |
- {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind"}}}}, http.StatusBadRequest, "Target must not be empty"},
|
|
| 1574 |
- {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind", Target: destPath}}}}, http.StatusBadRequest, "Source must not be empty"},
|
|
| 1575 |
- {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind", Source: notExistPath, Target: destPath}}}}, http.StatusBadRequest, "bind source path does not exist"},
|
|
| 1576 |
- {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "volume"}}}}, http.StatusBadRequest, "Target must not be empty"},
|
|
| 1577 |
- {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "volume", Source: "hello", Target: destPath}}}}, http.StatusCreated, ""},
|
|
| 1578 |
- {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "volume", Source: "hello2", Target: destPath, VolumeOptions: &mounttypes.VolumeOptions{DriverConfig: &mounttypes.Driver{Name: "local"}}}}}}, http.StatusCreated, ""},
|
|
| 1572 |
+ {
|
|
| 1573 |
+ config: cfg{
|
|
| 1574 |
+ Image: "busybox", |
|
| 1575 |
+ HostConfig: hc{
|
|
| 1576 |
+ Mounts: []m{{
|
|
| 1577 |
+ Type: "notreal", |
|
| 1578 |
+ Target: destPath}}}}, |
|
| 1579 |
+ status: http.StatusBadRequest, |
|
| 1580 |
+ msg: "mount type unknown", |
|
| 1581 |
+ }, |
|
| 1582 |
+ {
|
|
| 1583 |
+ config: cfg{
|
|
| 1584 |
+ Image: "busybox", |
|
| 1585 |
+ HostConfig: hc{
|
|
| 1586 |
+ Mounts: []m{{
|
|
| 1587 |
+ Type: "bind"}}}}, |
|
| 1588 |
+ status: http.StatusBadRequest, |
|
| 1589 |
+ msg: "Target must not be empty", |
|
| 1590 |
+ }, |
|
| 1591 |
+ {
|
|
| 1592 |
+ config: cfg{
|
|
| 1593 |
+ Image: "busybox", |
|
| 1594 |
+ HostConfig: hc{
|
|
| 1595 |
+ Mounts: []m{{
|
|
| 1596 |
+ Type: "bind", |
|
| 1597 |
+ Target: destPath}}}}, |
|
| 1598 |
+ status: http.StatusBadRequest, |
|
| 1599 |
+ msg: "Source must not be empty", |
|
| 1600 |
+ }, |
|
| 1601 |
+ {
|
|
| 1602 |
+ config: cfg{
|
|
| 1603 |
+ Image: "busybox", |
|
| 1604 |
+ HostConfig: hc{
|
|
| 1605 |
+ Mounts: []m{{
|
|
| 1606 |
+ Type: "bind", |
|
| 1607 |
+ Source: notExistPath, |
|
| 1608 |
+ Target: destPath}}}}, |
|
| 1609 |
+ status: http.StatusBadRequest, |
|
| 1610 |
+ msg: "bind source path does not exist", |
|
| 1611 |
+ }, |
|
| 1612 |
+ {
|
|
| 1613 |
+ config: cfg{
|
|
| 1614 |
+ Image: "busybox", |
|
| 1615 |
+ HostConfig: hc{
|
|
| 1616 |
+ Mounts: []m{{
|
|
| 1617 |
+ Type: "volume"}}}}, |
|
| 1618 |
+ status: http.StatusBadRequest, |
|
| 1619 |
+ msg: "Target must not be empty", |
|
| 1620 |
+ }, |
|
| 1621 |
+ {
|
|
| 1622 |
+ config: cfg{
|
|
| 1623 |
+ Image: "busybox", |
|
| 1624 |
+ HostConfig: hc{
|
|
| 1625 |
+ Mounts: []m{{
|
|
| 1626 |
+ Type: "volume", |
|
| 1627 |
+ Source: "hello", |
|
| 1628 |
+ Target: destPath}}}}, |
|
| 1629 |
+ status: http.StatusCreated, |
|
| 1630 |
+ msg: "", |
|
| 1631 |
+ }, |
|
| 1632 |
+ {
|
|
| 1633 |
+ config: cfg{
|
|
| 1634 |
+ Image: "busybox", |
|
| 1635 |
+ HostConfig: hc{
|
|
| 1636 |
+ Mounts: []m{{
|
|
| 1637 |
+ Type: "volume", |
|
| 1638 |
+ Source: "hello2", |
|
| 1639 |
+ Target: destPath, |
|
| 1640 |
+ VolumeOptions: &mounttypes.VolumeOptions{
|
|
| 1641 |
+ DriverConfig: &mounttypes.Driver{
|
|
| 1642 |
+ Name: "local"}}}}}}, |
|
| 1643 |
+ status: http.StatusCreated, |
|
| 1644 |
+ msg: "", |
|
| 1645 |
+ }, |
|
| 1579 | 1646 |
} |
| 1580 | 1647 |
|
| 1581 | 1648 |
if SameHostDaemon.Condition() {
|
| ... | ... |
@@ -1583,14 +1650,85 @@ func (s *DockerSuite) TestContainersAPICreateMountsValidation(c *check.C) {
|
| 1583 | 1583 |
c.Assert(err, checker.IsNil) |
| 1584 | 1584 |
defer os.RemoveAll(tmpDir) |
| 1585 | 1585 |
cases = append(cases, []testCase{
|
| 1586 |
- {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind", Source: tmpDir, Target: destPath}}}}, http.StatusCreated, ""},
|
|
| 1587 |
- {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "bind", Source: tmpDir, Target: destPath, VolumeOptions: &mounttypes.VolumeOptions{}}}}}, http.StatusBadRequest, "VolumeOptions must not be specified"},
|
|
| 1586 |
+ {
|
|
| 1587 |
+ config: cfg{
|
|
| 1588 |
+ Image: "busybox", |
|
| 1589 |
+ HostConfig: hc{
|
|
| 1590 |
+ Mounts: []m{{
|
|
| 1591 |
+ Type: "bind", |
|
| 1592 |
+ Source: tmpDir, |
|
| 1593 |
+ Target: destPath}}}}, |
|
| 1594 |
+ status: http.StatusCreated, |
|
| 1595 |
+ msg: "", |
|
| 1596 |
+ }, |
|
| 1597 |
+ {
|
|
| 1598 |
+ config: cfg{
|
|
| 1599 |
+ Image: "busybox", |
|
| 1600 |
+ HostConfig: hc{
|
|
| 1601 |
+ Mounts: []m{{
|
|
| 1602 |
+ Type: "bind", |
|
| 1603 |
+ Source: tmpDir, |
|
| 1604 |
+ Target: destPath, |
|
| 1605 |
+ VolumeOptions: &mounttypes.VolumeOptions{}}}}},
|
|
| 1606 |
+ status: http.StatusBadRequest, |
|
| 1607 |
+ msg: "VolumeOptions must not be specified", |
|
| 1608 |
+ }, |
|
| 1588 | 1609 |
}...) |
| 1589 | 1610 |
} |
| 1590 | 1611 |
|
| 1591 | 1612 |
if DaemonIsLinux.Condition() {
|
| 1592 | 1613 |
cases = append(cases, []testCase{
|
| 1593 |
- {cfg{Image: "busybox", HostConfig: hc{Mounts: []m{{Type: "volume", Source: "hello3", Target: destPath, VolumeOptions: &mounttypes.VolumeOptions{DriverConfig: &mounttypes.Driver{Name: "local", Options: map[string]string{"o": "size=1"}}}}}}}, http.StatusCreated, ""},
|
|
| 1614 |
+ {
|
|
| 1615 |
+ config: cfg{
|
|
| 1616 |
+ Image: "busybox", |
|
| 1617 |
+ HostConfig: hc{
|
|
| 1618 |
+ Mounts: []m{{
|
|
| 1619 |
+ Type: "volume", |
|
| 1620 |
+ Source: "hello3", |
|
| 1621 |
+ Target: destPath, |
|
| 1622 |
+ VolumeOptions: &mounttypes.VolumeOptions{
|
|
| 1623 |
+ DriverConfig: &mounttypes.Driver{
|
|
| 1624 |
+ Name: "local", |
|
| 1625 |
+ Options: map[string]string{"o": "size=1"}}}}}}},
|
|
| 1626 |
+ status: http.StatusCreated, |
|
| 1627 |
+ msg: "", |
|
| 1628 |
+ }, |
|
| 1629 |
+ {
|
|
| 1630 |
+ config: cfg{
|
|
| 1631 |
+ Image: "busybox", |
|
| 1632 |
+ HostConfig: hc{
|
|
| 1633 |
+ Mounts: []m{{
|
|
| 1634 |
+ Type: "tmpfs", |
|
| 1635 |
+ Target: destPath}}}}, |
|
| 1636 |
+ status: http.StatusCreated, |
|
| 1637 |
+ msg: "", |
|
| 1638 |
+ }, |
|
| 1639 |
+ {
|
|
| 1640 |
+ config: cfg{
|
|
| 1641 |
+ Image: "busybox", |
|
| 1642 |
+ HostConfig: hc{
|
|
| 1643 |
+ Mounts: []m{{
|
|
| 1644 |
+ Type: "tmpfs", |
|
| 1645 |
+ Target: destPath, |
|
| 1646 |
+ TmpfsOptions: &mounttypes.TmpfsOptions{
|
|
| 1647 |
+ SizeBytes: 4096 * 1024, |
|
| 1648 |
+ Mode: 0700, |
|
| 1649 |
+ }}}}}, |
|
| 1650 |
+ status: http.StatusCreated, |
|
| 1651 |
+ msg: "", |
|
| 1652 |
+ }, |
|
| 1653 |
+ |
|
| 1654 |
+ {
|
|
| 1655 |
+ config: cfg{
|
|
| 1656 |
+ Image: "busybox", |
|
| 1657 |
+ HostConfig: hc{
|
|
| 1658 |
+ Mounts: []m{{
|
|
| 1659 |
+ Type: "tmpfs", |
|
| 1660 |
+ Source: "/shouldnotbespecified", |
|
| 1661 |
+ Target: destPath}}}}, |
|
| 1662 |
+ status: http.StatusBadRequest, |
|
| 1663 |
+ msg: "Source must not be specified", |
|
| 1664 |
+ }, |
|
| 1594 | 1665 |
}...) |
| 1595 | 1666 |
|
| 1596 | 1667 |
} |
| ... | ... |
@@ -1759,3 +1897,45 @@ func (s *DockerSuite) TestContainersAPICreateMountsCreate(c *check.C) {
|
| 1759 | 1759 |
} |
| 1760 | 1760 |
} |
| 1761 | 1761 |
} |
| 1762 |
+ |
|
| 1763 |
+func (s *DockerSuite) TestContainersAPICreateMountsTmpfs(c *check.C) {
|
|
| 1764 |
+ testRequires(c, DaemonIsLinux) |
|
| 1765 |
+ type testCase struct {
|
|
| 1766 |
+ cfg map[string]interface{}
|
|
| 1767 |
+ expectedOptions []string |
|
| 1768 |
+ } |
|
| 1769 |
+ target := "/foo" |
|
| 1770 |
+ cases := []testCase{
|
|
| 1771 |
+ {
|
|
| 1772 |
+ cfg: map[string]interface{}{
|
|
| 1773 |
+ "Type": "tmpfs", |
|
| 1774 |
+ "Target": target}, |
|
| 1775 |
+ expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime"},
|
|
| 1776 |
+ }, |
|
| 1777 |
+ {
|
|
| 1778 |
+ cfg: map[string]interface{}{
|
|
| 1779 |
+ "Type": "tmpfs", |
|
| 1780 |
+ "Target": target, |
|
| 1781 |
+ "TmpfsOptions": map[string]interface{}{
|
|
| 1782 |
+ "SizeBytes": 4096 * 1024, "Mode": 0700}}, |
|
| 1783 |
+ expectedOptions: []string{"rw", "nosuid", "nodev", "noexec", "relatime", "size=4096k", "mode=700"},
|
|
| 1784 |
+ }, |
|
| 1785 |
+ } |
|
| 1786 |
+ |
|
| 1787 |
+ for i, x := range cases {
|
|
| 1788 |
+ cName := fmt.Sprintf("test-tmpfs-%d", i)
|
|
| 1789 |
+ data := map[string]interface{}{
|
|
| 1790 |
+ "Image": "busybox", |
|
| 1791 |
+ "Cmd": []string{"/bin/sh", "-c",
|
|
| 1792 |
+ fmt.Sprintf("mount | grep 'tmpfs on %s'", target)},
|
|
| 1793 |
+ "HostConfig": map[string]interface{}{"Mounts": []map[string]interface{}{x.cfg}},
|
|
| 1794 |
+ } |
|
| 1795 |
+ status, resp, err := sockRequest("POST", "/containers/create?name="+cName, data)
|
|
| 1796 |
+ c.Assert(err, checker.IsNil, check.Commentf(string(resp))) |
|
| 1797 |
+ c.Assert(status, checker.Equals, http.StatusCreated, check.Commentf(string(resp))) |
|
| 1798 |
+ out, _ := dockerCmd(c, "start", "-a", cName) |
|
| 1799 |
+ for _, option := range x.expectedOptions {
|
|
| 1800 |
+ c.Assert(out, checker.Contains, option) |
|
| 1801 |
+ } |
|
| 1802 |
+ } |
|
| 1803 |
+} |
| ... | ... |
@@ -48,7 +48,7 @@ func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostCon |
| 48 | 48 |
} |
| 49 | 49 |
|
| 50 | 50 |
// Now validate all the volumes and binds |
| 51 |
- if err := validateVolumesAndBindSettings(w.Config, hc); err != nil {
|
|
| 51 |
+ if err := validateMountSettings(w.Config, hc); err != nil {
|
|
| 52 | 52 |
return nil, nil, nil, err |
| 53 | 53 |
} |
| 54 | 54 |
} |
| ... | ... |
@@ -76,22 +76,10 @@ func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostCon |
| 76 | 76 |
return w.Config, hc, w.NetworkingConfig, nil |
| 77 | 77 |
} |
| 78 | 78 |
|
| 79 |
-// validateVolumesAndBindSettings validates each of the volumes and bind settings |
|
| 79 |
+// validateMountSettings validates each of the volumes and bind settings |
|
| 80 | 80 |
// passed by the caller to ensure they are valid. |
| 81 |
-func validateVolumesAndBindSettings(c *container.Config, hc *container.HostConfig) error {
|
|
| 82 |
- if len(hc.Mounts) > 0 {
|
|
| 83 |
- if len(hc.Binds) > 0 {
|
|
| 84 |
- return conflictError(fmt.Errorf("must not specify both Binds and Mounts"))
|
|
| 85 |
- } |
|
| 86 |
- |
|
| 87 |
- if len(c.Volumes) > 0 {
|
|
| 88 |
- return conflictError(fmt.Errorf("must not specify both Volumes and Mounts"))
|
|
| 89 |
- } |
|
| 90 |
- |
|
| 91 |
- if len(hc.VolumeDriver) > 0 {
|
|
| 92 |
- return conflictError(fmt.Errorf("must not specify both VolumeDriver and Mounts"))
|
|
| 93 |
- } |
|
| 94 |
- } |
|
| 81 |
+func validateMountSettings(c *container.Config, hc *container.HostConfig) error {
|
|
| 82 |
+ // it is ok to have len(hc.Mounts) > 0 && (len(hc.Binds) > 0 || len (c.Volumes) > 0 || len (hc.Tmpfs) > 0 ) |
|
| 95 | 83 |
|
| 96 | 84 |
// Ensure all volumes and binds are valid. |
| 97 | 85 |
for spec := range c.Volumes {
|
| ... | ... |
@@ -87,6 +87,13 @@ func validateMountConfig(mnt *mount.Mount, options ...func(*validateOpts)) error |
| 87 | 87 |
return &errMountConfig{mnt, err}
|
| 88 | 88 |
} |
| 89 | 89 |
} |
| 90 |
+ case mount.TypeTmpfs: |
|
| 91 |
+ if len(mnt.Source) != 0 {
|
|
| 92 |
+ return &errMountConfig{mnt, errExtraField("Source")}
|
|
| 93 |
+ } |
|
| 94 |
+ if _, err := ConvertTmpfsOptions(mnt.TmpfsOptions); err != nil {
|
|
| 95 |
+ return &errMountConfig{mnt, err}
|
|
| 96 |
+ } |
|
| 90 | 97 |
default: |
| 91 | 98 |
return &errMountConfig{mnt, errors.New("mount type unknown")}
|
| 92 | 99 |
} |
| 292 | 294 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,57 @@ |
| 0 |
+// +build linux |
|
| 1 |
+ |
|
| 2 |
+package volume |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "strings" |
|
| 7 |
+ |
|
| 8 |
+ mounttypes "github.com/docker/docker/api/types/mount" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+// ConvertTmpfsOptions converts *mounttypes.TmpfsOptions to the raw option string |
|
| 12 |
+// for mount(2). |
|
| 13 |
+// The logic is copy-pasted from daemon/cluster/executer/container.getMountMask. |
|
| 14 |
+// It will be deduplicated when we migrated the cluster to the new mount scheme. |
|
| 15 |
+func ConvertTmpfsOptions(opt *mounttypes.TmpfsOptions) (string, error) {
|
|
| 16 |
+ if opt == nil {
|
|
| 17 |
+ return "", nil |
|
| 18 |
+ } |
|
| 19 |
+ var rawOpts []string |
|
| 20 |
+ if opt.Mode != 0 {
|
|
| 21 |
+ rawOpts = append(rawOpts, fmt.Sprintf("mode=%o", opt.Mode))
|
|
| 22 |
+ } |
|
| 23 |
+ |
|
| 24 |
+ if opt.SizeBytes != 0 {
|
|
| 25 |
+ // calculate suffix here, making this linux specific, but that is |
|
| 26 |
+ // okay, since API is that way anyways. |
|
| 27 |
+ |
|
| 28 |
+ // we do this by finding the suffix that divides evenly into the |
|
| 29 |
+ // value, returing the value itself, with no suffix, if it fails. |
|
| 30 |
+ // |
|
| 31 |
+ // For the most part, we don't enforce any semantic to this values. |
|
| 32 |
+ // The operating system will usually align this and enforce minimum |
|
| 33 |
+ // and maximums. |
|
| 34 |
+ var ( |
|
| 35 |
+ size = opt.SizeBytes |
|
| 36 |
+ suffix string |
|
| 37 |
+ ) |
|
| 38 |
+ for _, r := range []struct {
|
|
| 39 |
+ suffix string |
|
| 40 |
+ divisor int64 |
|
| 41 |
+ }{
|
|
| 42 |
+ {"g", 1 << 30},
|
|
| 43 |
+ {"m", 1 << 20},
|
|
| 44 |
+ {"k", 1 << 10},
|
|
| 45 |
+ } {
|
|
| 46 |
+ if size%r.divisor == 0 {
|
|
| 47 |
+ size = size / r.divisor |
|
| 48 |
+ suffix = r.suffix |
|
| 49 |
+ break |
|
| 50 |
+ } |
|
| 51 |
+ } |
|
| 52 |
+ |
|
| 53 |
+ rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix))
|
|
| 54 |
+ } |
|
| 55 |
+ return strings.Join(rawOpts, ","), nil |
|
| 56 |
+} |
| 0 | 57 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,23 @@ |
| 0 |
+// +build linux |
|
| 1 |
+ |
|
| 2 |
+package volume |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "testing" |
|
| 6 |
+ |
|
| 7 |
+ mounttypes "github.com/docker/docker/api/types/mount" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func TestConvertTmpfsOptions(t *testing.T) {
|
|
| 11 |
+ type testCase struct {
|
|
| 12 |
+ opt mounttypes.TmpfsOptions |
|
| 13 |
+ } |
|
| 14 |
+ cases := []testCase{
|
|
| 15 |
+ {mounttypes.TmpfsOptions{SizeBytes: 1024 * 1024, Mode: 0700}},
|
|
| 16 |
+ } |
|
| 17 |
+ for _, c := range cases {
|
|
| 18 |
+ if _, err := ConvertTmpfsOptions(&c.opt); err != nil {
|
|
| 19 |
+ t.Fatalf("could not convert %+v to string: %v", c.opt, err)
|
|
| 20 |
+ } |
|
| 21 |
+ } |
|
| 22 |
+} |
| 0 | 23 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,16 @@ |
| 0 |
+// +build !linux |
|
| 1 |
+ |
|
| 2 |
+package volume |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "runtime" |
|
| 7 |
+ |
|
| 8 |
+ mounttypes "github.com/docker/docker/api/types/mount" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+// ConvertTmpfsOptions converts *mounttypes.TmpfsOptions to the raw option string |
|
| 12 |
+// for mount(2). |
|
| 13 |
+func ConvertTmpfsOptions(opt *mounttypes.TmpfsOptions) (string, error) {
|
|
| 14 |
+ return "", fmt.Errorf("%s does not support tmpfs", runtime.GOOS)
|
|
| 15 |
+} |