diff --git a/apis/server/container_bridge.go b/apis/server/container_bridge.go index 3fc9d6413b..a4f72f2213 100644 --- a/apis/server/container_bridge.go +++ b/apis/server/container_bridge.go @@ -293,6 +293,13 @@ func (s *Server) getContainer(ctx context.Context, rw http.ResponseWriter, req * } } + if len(meta.Mounts) > 0 { + container.Mounts = []types.MountPoint{} + for _, mp := range meta.Mounts { + container.Mounts = append(container.Mounts, *mp) + } + } + return EncodeResponse(rw, http.StatusOK, container) } diff --git a/apis/swagger.yml b/apis/swagger.yml index c4db365793..8ea95f71e5 100644 --- a/apis/swagger.yml +++ b/apis/swagger.yml @@ -2777,6 +2777,8 @@ definitions: properties: Type: type: "string" + ID: + type: "string" Name: type: "string" Source: @@ -2789,6 +2791,12 @@ definitions: type: "string" RW: type: "boolean" + CopyData: + type: "boolean" + Named: + type: "boolean" + Replace: + type: "string" Propagation: type: "string" diff --git a/apis/types/mount_point.go b/apis/types/mount_point.go index 0d93b31db2..0f904365c9 100644 --- a/apis/types/mount_point.go +++ b/apis/types/mount_point.go @@ -17,24 +17,36 @@ import ( type MountPoint struct { + // copy data + CopyData bool `json:"CopyData,omitempty"` + // destination Destination string `json:"Destination,omitempty"` // driver Driver string `json:"Driver,omitempty"` + // ID + ID string `json:"ID,omitempty"` + // mode Mode string `json:"Mode,omitempty"` // name Name string `json:"Name,omitempty"` + // named + Named bool `json:"Named,omitempty"` + // propagation Propagation string `json:"Propagation,omitempty"` // r w RW bool `json:"RW,omitempty"` + // replace + Replace string `json:"Replace,omitempty"` + // source Source string `json:"Source,omitempty"` @@ -42,18 +54,26 @@ type MountPoint struct { Type string `json:"Type,omitempty"` } +/* polymorph MountPoint CopyData false */ + /* polymorph MountPoint Destination false */ /* polymorph MountPoint Driver false */ +/* polymorph MountPoint ID false */ + /* polymorph MountPoint Mode false */ /* polymorph MountPoint Name false */ +/* polymorph MountPoint Named false */ + /* polymorph MountPoint Propagation false */ /* polymorph MountPoint RW false */ +/* polymorph MountPoint Replace false */ + /* polymorph MountPoint Source false */ /* polymorph MountPoint Type false */ diff --git a/daemon/mgr/container.go b/daemon/mgr/container.go index 9533f063c8..ad857408f4 100644 --- a/daemon/mgr/container.go +++ b/daemon/mgr/container.go @@ -330,11 +330,6 @@ func (mgr *ContainerManager) Create(ctx context.Context, name string, config *ty return nil, errors.Wrap(errtypes.ErrAlreadyExisted, "container name: "+name) } - // parse volume config - if err := mgr.parseVolumes(ctx, id, config); err != nil { - return nil, errors.Wrap(err, "failed to parse volume argument") - } - // check the image existed or not, and convert image id to image ref image, err := mgr.ImageMgr.GetImage(ctx, config.Image) if err != nil { @@ -375,6 +370,11 @@ func (mgr *ContainerManager) Create(ctx context.Context, name string, config *ty HostConfig: config.HostConfig, } + // parse volume config + if err := mgr.parseBinds(ctx, meta); err != nil { + return nil, errors.Wrap(err, "failed to parse volume argument") + } + // set container basefs mgr.setBaseFS(ctx, meta, id) @@ -1055,92 +1055,150 @@ func (mgr *ContainerManager) execExitedAndRelease(id string, m *ctrd.Message) er return nil } -func (mgr *ContainerManager) parseVolumes(ctx context.Context, id string, c *types.ContainerCreateConfig) error { - logrus.Debugf("bind volumes: %v", c.HostConfig.Binds) +func (mgr *ContainerManager) bindVolume(ctx context.Context, name string, meta *ContainerMeta) (string, string, error) { + id := meta.ID - if c.Volumes == nil { - c.Volumes = make(map[string]interface{}) + ref := "" + driver := "local" + v, err := mgr.VolumeMgr.Get(ctx, name) + if err != nil || v == nil { + opts := map[string]string{ + "backend": "local", + } + if err := mgr.VolumeMgr.Create(ctx, name, meta.HostConfig.VolumeDriver, opts, nil); err != nil { + logrus.Errorf("failed to create volume: %s, err: %v", name, err) + return "", "", errors.Wrap(err, "failed to create volume") + } + } else { + ref = v.Option("ref") + driver = v.Driver() + } + + option := map[string]string{} + if ref == "" { + option["ref"] = id + } else { + option["ref"] = ref + "," + id + } + if _, err := mgr.VolumeMgr.Attach(ctx, name, option); err != nil { + logrus.Errorf("failed to attach volume: %s, err: %v", name, err) + return "", "", errors.Wrap(err, "failed to attach volume") } + mountPath, err := mgr.VolumeMgr.Path(ctx, name) + if err != nil { + logrus.Errorf("failed to get the mount path of volume: %s, err: %v", name, err) + return "", "", errors.Wrap(err, "failed to get volume mount path") + } + + return mountPath, driver, nil +} + +func (mgr *ContainerManager) parseBinds(ctx context.Context, meta *ContainerMeta) error { + logrus.Debugf("bind volumes: %v", meta.HostConfig.Binds) + + var err error + + if meta.Config.Volumes == nil { + meta.Config.Volumes = make(map[string]interface{}) + } + + if meta.Mounts == nil { + meta.Mounts = make([]*types.MountPoint, 0) + } + + defer func() { + if err != nil { + if err := mgr.detachVolumes(ctx, meta); err != nil { + logrus.Errorf("failed to detach volume, err: %v", err) + } + } + }() + // TODO: parse c.HostConfig.VolumesFrom - for i, b := range c.HostConfig.Binds { + for _, b := range meta.HostConfig.Binds { + var parts []string // TODO: when caused error, how to rollback. - arr, err := checkBind(b) + parts, err = checkBind(b) if err != nil { return err } - source := "" - destination := "" - switch len(arr) { + + mode := "" + mp := new(types.MountPoint) + + switch len(parts) { case 1: - source = "" - destination = arr[0] - case 2, 3: - source = arr[0] - destination = arr[1] + mp.Source = "" + mp.Destination = parts[0] + case 2: + mp.Source = parts[0] + mp.Destination = parts[1] + case 3: + mp.Source = parts[0] + mp.Destination = parts[1] + mode = parts[2] default: return errors.Errorf("unknown bind: %s", b) } - if source == "" { - source = randomid.Generate() + if mp.Source == "" { + mp.Source = randomid.Generate() } - if !path.IsAbs(source) { - ref := "" - v, err := mgr.VolumeMgr.Get(ctx, source) - if err != nil || v == nil { - opts := map[string]string{ - "backend": "local", - } - if err := mgr.VolumeMgr.Create(ctx, source, c.HostConfig.VolumeDriver, opts, nil); err != nil { - logrus.Errorf("failed to create volume: %s, err: %v", source, err) - return errors.Wrap(err, "failed to create volume") + + err = parseBindMode(mp, mode) + if err != nil { + logrus.Errorf("failed to parse bind mode: %s, err: %v", mode, err) + return err + } + + if !path.IsAbs(mp.Source) { + // volume bind. + name := mp.Source + if _, exist := meta.Config.Volumes[name]; !exist { + mp.Name = name + mp.Source, mp.Driver, err = mgr.bindVolume(ctx, name, meta) + if err != nil { + logrus.Errorf("failed to bind volume: %s, err: %v", name, err) + return errors.Wrap(err, "failed to bind volume") } - } else { - ref = v.Option("ref") + meta.Config.Volumes[mp.Name] = mp.Destination } - option := map[string]string{} - if ref == "" { - option["ref"] = id - } else { - option["ref"] = ref + "," + id - } - if _, err := mgr.VolumeMgr.Attach(ctx, source, option); err != nil { - logrus.Errorf("failed to attach volume: %s, err: %v", source, err) - return errors.Wrap(err, "failed to attach volume") - } + if mp.Replace != "" { + mp.Source, err = mgr.VolumeMgr.Path(ctx, name) + if err != nil { + return err + } - mountPath, err := mgr.VolumeMgr.Path(ctx, source) - if err != nil { - logrus.Errorf("failed to get the mount path of volume: %s, err: %v", source, err) - return errors.Wrap(err, "failed to get volume mount path") + switch mp.Replace { + case "dr": + mp.Source = path.Join(mp.Source, mp.Destination) + case "rr": + mp.Source = path.Join(mp.Source, randomid.Generate()) + } + + mp.Name = "" + mp.Named = false + mp.Driver = "" } + } - c.Volumes[source] = destination - source = mountPath - } else if _, err := os.Stat(source); err != nil { + if _, err = os.Stat(mp.Source); err != nil { + // host directory bind into container. if !os.IsNotExist(err) { - return errors.Errorf("failed to stat %q: %v", source, err) + return errors.Errorf("failed to stat %q: %v", mp.Source, err) } // Create the host path if it doesn't exist. - if err := os.MkdirAll(source, 0755); err != nil { - return errors.Errorf("failed to mkdir %q: %v", source, err) + if err = os.MkdirAll(mp.Source, 0755); err != nil { + return errors.Errorf("failed to mkdir %q: %v", mp.Source, err) } } - switch len(arr) { - case 1: - b = fmt.Sprintf("%s:%s", source, arr[0]) - case 2, 3: - arr[0] = source - b = strings.Join(arr, ":") - default: - } - - c.HostConfig.Binds[i] = b + meta.Mounts = append(meta.Mounts, mp) } + return nil } @@ -1241,3 +1299,48 @@ func checkBind(b string) ([]string, error) { 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": + 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("known bind mode: %s", mode) + } + } + + if defaultMode > 2 || rwMode > 2 || replaceMode > 2 || copyMode > 2 || propagationMode > 2 { + return fmt.Errorf("invalid bind mode: %s", mode) + } + + mp.Mode = mode + return nil +} diff --git a/daemon/mgr/spec_volume.go b/daemon/mgr/spec_volume.go index 5a751dc249..7d2ed5e153 100644 --- a/daemon/mgr/spec_volume.go +++ b/daemon/mgr/spec_volume.go @@ -3,7 +3,6 @@ package mgr import ( "context" "fmt" - "strings" specs "github.com/opencontainers/runtime-spec/specs-go" ) @@ -14,28 +13,43 @@ func setupMounts(ctx context.Context, c *ContainerMeta, spec *SpecWrapper) error if c.HostConfig == nil { return nil } - for _, v := range c.HostConfig.Binds { - sd := strings.Split(v, ":") - lensd := len(sd) - if lensd < 2 || lensd > 3 { - return fmt.Errorf("unknown bind: %s", v) + for _, mp := range c.Mounts { + // check duplicate mountpoint + for _, sm := range mounts { + if sm.Destination == mp.Destination { + return fmt.Errorf("duplicate mount point: %s", mp.Destination) + } } - opt := []string{"rbind"} - if lensd == 3 { - opt = append(opt, strings.Split(sd[2], ",")...) - // Set rootfs propagation, default setting is private. - if strings.Contains(sd[2], "rshared") { - s.Linux.RootfsPropagation = "rshared" + + pg := mp.Propagation + rootfspg := s.Linux.RootfsPropagation + // Set rootfs propagation, default setting is private. + switch pg { + case "shared", "rshare": + if rootfspg != "shared" && rootfspg != "rshare" { + s.Linux.RootfsPropagation = "shared" } - if strings.Contains(sd[2], "rslave") && s.Linux.RootfsPropagation != "rshared" { + case "slave", "rslave": + if rootfspg != "shared" && rootfspg != "rshare" && rootfspg != "slave" && rootfspg != "rslave" { s.Linux.RootfsPropagation = "rslave" } } + + opts := []string{"rbind"} + if !mp.RW { + opts = append(opts, "ro") + } + if pg != "" { + opts = append(opts, pg) + } + + // TODO: support copy data. + mounts = append(mounts, specs.Mount{ - Destination: sd[1], - Source: sd[0], + Source: mp.Source, + Destination: mp.Destination, Type: "bind", - Options: opt, + Options: opts, }) } s.Mounts = mounts