Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: add volumes-from for container #1131

Merged
merged 1 commit into from
Apr 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/common_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ func addCommonFlags(flagSet *pflag.FlagSet) *container {
flagSet.StringVar(&c.utsMode, "uts", "", "UTS namespace to use")

flagSet.StringSliceVarP(&c.volume, "volume", "v", nil, "Bind mount volumes to container, format is: [source:]<destination>[:mode], [source] can be volume or host's path, <destination> is container's path, [mode] can be \"ro/rw/dr/rr/z/Z/nocopy/private/rprivate/slave/rslave/shared/rshared\"")
flagSet.StringSliceVar(&c.volumesFrom, "volumes-from", nil, "set volumes from other containers, format is <container>[:mode]")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is other container, since we cannot set volumes for multiple containers.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, the api VolumesFrom is a slice, it means it can use more than one container's volumes.


flagSet.StringVarP(&c.workdir, "workdir", "w", "", "Set the working directory in a container")

Expand Down
30 changes: 16 additions & 14 deletions cli/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@ import (
)

type container struct {
labels []string
name string
tty bool
volume []string
runtime string
env []string
entrypoint string
workdir string
user string
groupAdd []string
hostname string
rm bool
labels []string
name string
tty bool
volume []string
volumesFrom []string
runtime string
env []string
entrypoint string
workdir string
user string
groupAdd []string
hostname string
rm bool

blkioWeight uint16
blkioWeightDevice WeightDevice
Expand Down Expand Up @@ -187,8 +188,9 @@ func (c *container) config() (*types.ContainerCreateConfig, error) {
},

HostConfig: &types.HostConfig{
Binds: c.volume,
Runtime: c.runtime,
Binds: c.volume,
VolumesFrom: c.volumesFrom,
Runtime: c.runtime,
Resources: types.Resources{
// cpu
CPUShares: c.cpushare,
Expand Down
125 changes: 47 additions & 78 deletions daemon/mgr/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/alibaba/pouch/pkg/collect"
"github.com/alibaba/pouch/pkg/errtypes"
"github.com/alibaba/pouch/pkg/meta"
"github.com/alibaba/pouch/pkg/opts"
"github.com/alibaba/pouch/pkg/quota"
"github.com/alibaba/pouch/pkg/randomid"
"github.com/alibaba/pouch/pkg/reference"
Expand Down Expand Up @@ -1449,12 +1450,54 @@ func (mgr *ContainerManager) parseBinds(ctx context.Context, meta *ContainerMeta
}
}()

// TODO: parse c.HostConfig.VolumesFrom
for _, v := range meta.HostConfig.VolumesFrom {
var containerID, mode string
containerID, mode, err = opts.ParseVolumesFrom(v)
if err != nil {
return err
}

var oldMeta *ContainerMeta
oldMeta, err = mgr.Get(ctx, containerID)
if err != nil {
return err
}

for _, oldMountPoint := range oldMeta.Mounts {
mp := &types.MountPoint{
Name: oldMountPoint.Name,
Source: oldMountPoint.Source,
Destination: oldMountPoint.Destination,
Driver: oldMountPoint.Driver,
Named: oldMountPoint.Named,
RW: oldMountPoint.RW,
Propagation: oldMountPoint.Propagation,
}

if _, exist := meta.Config.Volumes[oldMountPoint.Name]; !exist {
mp.Name = oldMountPoint.Name
mp.Source, mp.Driver, err = mgr.bindVolume(ctx, oldMountPoint.Name, meta)
if err != nil {
logrus.Errorf("failed to bind volume: %s, err: %v", oldMountPoint.Name, err)
return errors.Wrap(err, "failed to bind volume")
}
meta.Config.Volumes[mp.Name] = oldMountPoint.Destination
}

err = opts.ParseBindMode(mp, mode)
if err != nil {
logrus.Errorf("failed to parse volumes-from mode: %s, err: %v", mode, err)
return err
}

meta.Mounts = append(meta.Mounts, mp)
}

}

for _, b := range meta.HostConfig.Binds {
var parts []string
// TODO: when caused error, how to rollback.
parts, err = checkBind(b)
parts, err = opts.CheckBind(b)
if err != nil {
return err
}
Expand All @@ -1481,7 +1524,7 @@ func (mgr *ContainerManager) parseBinds(ctx context.Context, meta *ContainerMeta
mp.Source = randomid.Generate()
}

err = parseBindMode(mp, mode)
err = opts.ParseBindMode(mp, mode)
if err != nil {
logrus.Errorf("failed to parse bind mode: %s, err: %v", mode, err)
return err
Expand Down Expand Up @@ -1699,77 +1742,3 @@ func (mgr *ContainerManager) setBaseFS(ctx context.Context, meta *ContainerMeta,
// io.containerd.runtime.v1.linux as a const used by runc
meta.BaseFS = filepath.Join(mgr.Config.HomeDir, "containerd/state", "io.containerd.runtime.v1.linux", namespaces.Default, info.Name, "rootfs")
}

func checkBind(b string) ([]string, error) {
if strings.Count(b, ":") > 2 {
return nil, fmt.Errorf("unknown volume bind: %s", b)
}

arr := strings.SplitN(b, ":", 3)
switch len(arr) {
case 1:
if arr[0] == "" {
return nil, fmt.Errorf("unknown volume bind: %s", b)
}
if arr[0][:1] != "/" {
return nil, fmt.Errorf("invalid bind path: %s", arr[0])
}
case 2, 3:
if arr[1] == "" {
return nil, fmt.Errorf("unknown volume bind: %s", b)
}
if arr[1][:1] != "/" {
return nil, fmt.Errorf("invalid bind path: %s", arr[1])
}
default:
return nil, fmt.Errorf("unknown volume bind: %s", b)
}

return arr, nil
}

func parseBindMode(mp *types.MountPoint, mode string) error {
mp.RW = true
mp.CopyData = true

defaultMode := 0
rwMode := 0
labelMode := 0
replaceMode := 0
copyMode := 0
propagationMode := 0

for _, m := range strings.Split(mode, ",") {
switch m {
case "":
defaultMode++
case "ro":
mp.RW = false
rwMode++
case "rw":
mp.RW = true
rwMode++
case "dr", "rr":
// direct replace mode, random replace mode
mp.Replace = m
replaceMode++
case "z", "Z":
labelMode++
case "nocopy":
mp.CopyData = false
copyMode++
case "private", "rprivate", "slave", "rslave", "shared", "rshared":
mp.Propagation = m
propagationMode++
default:
return fmt.Errorf("unknown bind mode: %s", mode)
}
}

if defaultMode > 1 || rwMode > 1 || replaceMode > 1 || copyMode > 1 || propagationMode > 1 {
return fmt.Errorf("invalid bind mode: %s", mode)
}

mp.Mode = mode
return nil
}
104 changes: 104 additions & 0 deletions pkg/opts/mountpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package opts

import (
"fmt"
"strings"

"github.com/alibaba/pouch/apis/types"
)

// CheckBind is used to check the volume bind information.
func CheckBind(b string) ([]string, error) {
if strings.Count(b, ":") > 2 {
return nil, fmt.Errorf("unknown volume bind: %s", b)
}

arr := strings.SplitN(b, ":", 3)
switch len(arr) {
case 1:
if arr[0] == "" {
return nil, fmt.Errorf("unknown volume bind: %s", b)
}
if arr[0][:1] != "/" {
return nil, fmt.Errorf("invalid bind path: %s", arr[0])
}
case 2, 3:
if arr[1] == "" {
return nil, fmt.Errorf("unknown volume bind: %s", b)
}
if arr[1][:1] != "/" {
return nil, fmt.Errorf("invalid bind path: %s", arr[1])
}
default:
return nil, fmt.Errorf("unknown volume bind: %s", b)
}

return arr, nil
}

// ParseVolumesFrom is used to parse the parameter of VolumesFrom.
func ParseVolumesFrom(volume string) (string, string, error) {
if len(volume) == 0 {
return "", "", fmt.Errorf("invalid argument volumes-from")
}

parts := strings.Split(volume, ":")
containerID := parts[0]
mode := ""
if len(parts) > 1 {
mode = parts[1]
}

if containerID == "" {
return "", "", fmt.Errorf("failed to parse container's id")
}

return containerID, mode, nil
}

// ParseBindMode is used to parse the bind's mode.
func ParseBindMode(mp *types.MountPoint, mode string) error {
mp.RW = true
mp.CopyData = true

defaultMode := 0
rwMode := 0
labelMode := 0
replaceMode := 0
copyMode := 0
propagationMode := 0

for _, m := range strings.Split(mode, ",") {
switch m {
case "":
defaultMode++
case "ro":
mp.RW = false
rwMode++
case "rw":
mp.RW = true
rwMode++
case "dr", "rr":
// direct replace mode, random replace mode
mp.Replace = m
replaceMode++
case "z", "Z":
labelMode++
case "nocopy":
mp.CopyData = false
copyMode++
case "private", "rprivate", "slave", "rslave", "shared", "rshared":
mp.Propagation = m
propagationMode++
default:
return fmt.Errorf("unknown bind mode: %s", mode)
}
}

if defaultMode > 1 || rwMode > 1 || replaceMode > 1 || copyMode > 1 || propagationMode > 1 {
return fmt.Errorf("invalid bind mode: %s", mode)
}

mp.Mode = mode
return nil
}
37 changes: 34 additions & 3 deletions daemon/mgr/container_test.go → pkg/opts/mountpoint_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package mgr
package opts

import (
"fmt"
Expand Down Expand Up @@ -32,7 +32,7 @@ func TestCheckBind(t *testing.T) {
}

for _, p := range parseds {
arr, err := checkBind(p.bind)
arr, err := CheckBind(p.bind)
if p.err {
assert.Equal(err, p.expectErr)
} else {
Expand Down Expand Up @@ -63,7 +63,7 @@ func TestParseBindMode(t *testing.T) {

for _, p := range parseds {
mp := &types.MountPoint{}
err := parseBindMode(mp, p.mode)
err := ParseBindMode(mp, p.mode)
if p.err {
assert.Equal(err, p.expectErr)
} else {
Expand All @@ -74,3 +74,34 @@ func TestParseBindMode(t *testing.T) {
}
}
}

func TestParseVolumesFrom(t *testing.T) {
assert := assert.New(t)

type parsed struct {
volumesFrom string
expectID string
expectMode string
err bool
expectErr error
}

parseds := []parsed{
{volumesFrom: "123456789", expectID: "123456789", expectMode: "", err: false, expectErr: nil},
{volumesFrom: "123456789:nocopy", expectID: "123456789", expectMode: "nocopy", err: false, expectErr: nil},
{volumesFrom: "123456789:", expectID: "123456789", expectMode: "", err: false, expectErr: nil},
{volumesFrom: "", expectID: "", expectMode: "", err: true, expectErr: fmt.Errorf("invalid argument volumes-from")},
{volumesFrom: ":", expectID: "", expectMode: "", err: true, expectErr: fmt.Errorf("failed to parse container's id")},
}

for _, p := range parseds {
containerID, mode, err := ParseVolumesFrom(p.volumesFrom)
if p.err {
assert.Equal(err, p.expectErr)
} else {
assert.NoError(err, p.expectErr)
assert.Equal(p.expectID, containerID)
assert.Equal(p.expectMode, mode)
}
}
}
Loading