Skip to content

Commit b9e8fa6

Browse files
committed
feature: support creating container by just specifying rootfs
Signed-off-by: Michael Wan <[email protected]>
1 parent 5dc2082 commit b9e8fa6

File tree

6 files changed

+171
-27
lines changed

6 files changed

+171
-27
lines changed

ctrd/container.go

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -345,35 +345,45 @@ func (c *Client) createContainer(ctx context.Context, ref, id string, container
345345
return fmt.Errorf("failed to get a containerd grpc client: %v", err)
346346
}
347347

348-
// get image
349-
img, err := wrapperCli.client.GetImage(ctx, ref)
350-
if err != nil {
351-
if errdefs.IsNotFound(err) {
352-
return errors.Wrapf(errtypes.ErrNotfound, "image %s", ref)
348+
// if creating the container by specify rootfs, we no need use the image
349+
if !container.RootFSProvided {
350+
// get image
351+
img, err := wrapperCli.client.GetImage(ctx, ref)
352+
if err != nil {
353+
if errdefs.IsNotFound(err) {
354+
return errors.Wrapf(errtypes.ErrNotfound, "image %s", ref)
355+
}
356+
return errors.Wrapf(err, "failed to get image %s", ref)
353357
}
354-
return errors.Wrapf(err, "failed to get image %s", ref)
355-
}
356358

357-
logrus.Infof("success to get image %s, container id %s", img.Name(), id)
358-
359-
// create container
360-
specOptions := []oci.SpecOpts{
361-
oci.WithRootFSPath("rootfs"),
359+
logrus.Infof("success to get image %s, container id %s", img.Name(), id)
362360
}
363361

362+
// create container
364363
options := []containerd.NewContainerOpts{
365-
containerd.WithSpec(container.Spec, specOptions...),
366364
containerd.WithRuntime(fmt.Sprintf("io.containerd.runtime.v1.%s", runtime.GOOS), &runctypes.RuncOptions{
367365
Runtime: container.Runtime,
368366
RuntimeRoot: runtimeRoot,
369367
}),
370368
}
371369

372-
// check snapshot exist or not.
373-
if _, err := c.GetSnapshot(ctx, id); err != nil {
374-
return errors.Wrapf(err, "failed to create container %s", id)
370+
rootFSPath := "rootfs"
371+
// if container is taken over by pouch, not created by pouch
372+
if container.RootFSProvided {
373+
rootFSPath = container.BaseFS
374+
} else { // containers created by pouch must first create snapshot
375+
// check snapshot exist or not.
376+
if _, err := c.GetSnapshot(ctx, id); err != nil {
377+
return errors.Wrapf(err, "failed to create container %s", id)
378+
}
379+
options = append(options, containerd.WithSnapshot(id))
380+
}
381+
382+
// specify Spec for new container
383+
specOptions := []oci.SpecOpts{
384+
oci.WithRootFSPath(rootFSPath),
375385
}
376-
options = append(options, containerd.WithSnapshot(id))
386+
options = append(options, containerd.WithSpec(container.Spec, specOptions...))
377387

378388
nc, err := wrapperCli.client.NewContainer(ctx, id, options...)
379389
if err != nil {

ctrd/container_types.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,22 @@ import (
66
specs "github.com/opencontainers/runtime-spec/specs-go"
77
)
88

9-
// Container wraps container's info.
9+
// Container wraps container's info. there have two kind of containers now:
10+
// One is created by pouch: first using image to create snapshot,
11+
// then create container by specifying the snapshot;
12+
// The other is create container by specify container rootfs, we use `RootFSProvided` flag to mark it,
1013
type Container struct {
1114
ID string
1215
Image string
1316
Runtime string
1417
IO *containerio.IO
1518
Spec *specs.Spec
19+
20+
// BaseFS is rootfs used by containerd container
21+
BaseFS string
22+
23+
// RootFSProvided is a flag to point the container is created by specifying rootfs
24+
RootFSProvided bool
1625
}
1726

1827
// Process wraps exec process's info.

daemon/mgr/container.go

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ import (
2525
"github.com/alibaba/pouch/pkg/collect"
2626
"github.com/alibaba/pouch/pkg/errtypes"
2727
"github.com/alibaba/pouch/pkg/meta"
28+
mountutils "github.com/alibaba/pouch/pkg/mount"
2829
"github.com/alibaba/pouch/pkg/randomid"
2930
"github.com/alibaba/pouch/pkg/utils"
3031
"github.com/alibaba/pouch/storage/quota"
3132
volumetypes "github.com/alibaba/pouch/storage/volume/types"
3233

3334
"github.com/containerd/containerd/errdefs"
35+
"github.com/containerd/containerd/mount"
3436
"github.com/containerd/containerd/namespaces"
3537
"github.com/docker/libnetwork"
3638
"github.com/go-openapi/strfmt"
@@ -597,14 +599,24 @@ func (mgr *ContainerManager) createContainerdContainer(ctx context.Context, c *C
597599

598600
c.Lock()
599601
ctrdContainer := &ctrd.Container{
600-
ID: c.ID,
601-
Image: c.Config.Image,
602-
Runtime: c.HostConfig.Runtime,
603-
Spec: sw.s,
604-
IO: io,
602+
ID: c.ID,
603+
Image: c.Config.Image,
604+
Runtime: c.HostConfig.Runtime,
605+
Spec: sw.s,
606+
IO: io,
607+
RootFSProvided: c.RootFSProvided,
608+
BaseFS: c.BaseFS,
605609
}
606610
c.Unlock()
607611

612+
// if creating the container by specified the rootfs, we must check
613+
// whether the rootfs is mounted before creation
614+
if c.RootFSProvided {
615+
if err := mgr.ensureRootFSMounted(c.BaseFS, c.Snapshotter.Data); err != nil {
616+
return fmt.Errorf("failed to mount container rootfs: %v", err)
617+
}
618+
}
619+
608620
if err := mgr.Client.CreateContainer(ctx, ctrdContainer); err != nil {
609621
logrus.Errorf("failed to create new containerd container: %v", err)
610622

@@ -631,6 +643,53 @@ func (mgr *ContainerManager) createContainerdContainer(ctx context.Context, c *C
631643
return c.Write(mgr.Store)
632644
}
633645

646+
func (mgr *ContainerManager) ensureRootFSMounted(rootfs string, snapData map[string]string) error {
647+
if rootfs == "" || len(snapData) == 0 {
648+
return fmt.Errorf("container rootfs or snapshotter data is empty")
649+
}
650+
651+
// check if rootfs already mounted
652+
notMounted, err := mountutils.IsLikelyNotMountPoint(rootfs)
653+
if err != nil {
654+
return err
655+
}
656+
// rootfs already mounted
657+
if !notMounted {
658+
return nil
659+
}
660+
661+
var workDir, upperDir, lowerDir string
662+
for _, dir := range []string{"WorkDir", "UpperDir", "LowerDir"} {
663+
if v, ok := snapData[dir]; ok {
664+
switch dir {
665+
case "WorkDir":
666+
workDir = v
667+
case "UpperDir":
668+
upperDir = v
669+
case "LowerDir":
670+
lowerDir = v
671+
}
672+
}
673+
}
674+
675+
if workDir == "" || upperDir == "" || lowerDir == "" {
676+
return fmt.Errorf("faile to mount overlay: one or more dirs in WorkDir, UpperDir and LowerDir are empty")
677+
}
678+
679+
options := []string{
680+
fmt.Sprintf("workdir=%s", snapData["WorkDir"]),
681+
fmt.Sprintf("upperdir=%s", snapData["UpperDir"]),
682+
fmt.Sprintf("lowerdir=%s", snapData["LowerDir"]),
683+
}
684+
mount := mount.Mount{
685+
Type: "overlay",
686+
Source: "overlay",
687+
Options: options,
688+
}
689+
690+
return mount.Mount(rootfs)
691+
}
692+
634693
// Stop stops a running container.
635694
func (mgr *ContainerManager) Stop(ctx context.Context, name string, timeout int64) error {
636695
c, err := mgr.container(name)
@@ -949,9 +1008,13 @@ func (mgr *ContainerManager) Remove(ctx context.Context, name string, options *t
9491008
logrus.Errorf("failed to detach volume: %v", err)
9501009
}
9511010

952-
// remove snapshot
953-
if err := mgr.Client.RemoveSnapshot(ctx, c.ID); err != nil {
954-
logrus.Errorf("failed to remove snapshot of container %s: %v", c.ID, err)
1011+
// if creating the container by specify rootfs,
1012+
// there is no snapshot for this container.
1013+
if !c.RootFSProvided {
1014+
// remove snapshot
1015+
if err := mgr.Client.RemoveSnapshot(ctx, c.ID); err != nil {
1016+
logrus.Errorf("failed to remove snapshot of container %s: %v", c.ID, err)
1017+
}
9551018
}
9561019

9571020
// When removing a container, we have set up such rule for object removing sequences:
@@ -967,7 +1030,6 @@ func (mgr *ContainerManager) Remove(ctx context.Context, name string, options *t
9671030
if err := mgr.Store.Remove(c.Key()); err != nil {
9681031
logrus.Errorf("failed to remove container %s from meta store: %v", c.ID, err)
9691032
}
970-
9711033
return nil
9721034
}
9731035

daemon/mgr/container_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ type Container struct {
180180

181181
// Escape keys for detach
182182
DetachKeys string
183+
184+
// RootFSProvided is a flag to point the container is created by specify rootfs
185+
RootFSProvided bool
183186
}
184187

185188
// Key returns container's id.

pkg/mount/mount.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package mount
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
"syscall"
8+
)
9+
10+
// IsLikelyNotMountPoint determines if a directory is not a mountpoint.
11+
// It is fast but not necessarily ALWAYS correct. If the path is in fact
12+
// a bind mount from one part of a mount to another it will not be detected.
13+
// mkdir /tmp/a /tmp/b; mount --bin /tmp/a /tmp/b; IsLikelyNotMountPoint("/tmp/b")
14+
// will return true. When in fact /tmp/b is a mount point. If this situation
15+
// if of interest to you, don't use this function...
16+
func IsLikelyNotMountPoint(file string) (bool, error) {
17+
stat, err := os.Stat(file)
18+
if err != nil {
19+
return true, err
20+
}
21+
rootStat, err := os.Lstat(filepath.Dir(strings.TrimSuffix(file, "/")))
22+
if err != nil {
23+
return true, err
24+
}
25+
// If the directory has a different device as parent, then it is a mountpoint.
26+
if stat.Sys().(*syscall.Stat_t).Dev != rootStat.Sys().(*syscall.Stat_t).Dev {
27+
return false, nil
28+
}
29+
30+
return true, nil
31+
}

pkg/mount/mount_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package mount
2+
3+
import "testing"
4+
5+
func TestIsLikelyNotMountPoint(t *testing.T) {
6+
type args struct {
7+
file string
8+
}
9+
tests := []struct {
10+
name string
11+
args args
12+
want bool
13+
wantErr bool
14+
}{
15+
// TODO: Add test cases.
16+
}
17+
for _, tt := range tests {
18+
t.Run(tt.name, func(t *testing.T) {
19+
got, err := IsLikelyNotMountPoint(tt.args.file)
20+
if (err != nil) != tt.wantErr {
21+
t.Errorf("IsLikelyNotMountPoint() error = %v, wantErr %v", err, tt.wantErr)
22+
return
23+
}
24+
if got != tt.want {
25+
t.Errorf("IsLikelyNotMountPoint() = %v, want %v", got, tt.want)
26+
}
27+
})
28+
}
29+
}

0 commit comments

Comments
 (0)