Skip to content

Commit 41464fa

Browse files
committed
feature: support creating container by just specifying rootfs
Signed-off-by: Michael Wan <[email protected]>
1 parent 94ac747 commit 41464fa

File tree

6 files changed

+170
-26
lines changed

6 files changed

+170
-26
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-
}
356-
357-
logrus.Infof("success to get image %s, container id %s", img.Name(), id)
358358

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: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,21 @@ 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, then create container by specify snapshot;
11+
// The other is create container by specify container rootfs, we use `RootFSProvided` flag to mark it,
1012
type Container struct {
1113
ID string
1214
Image string
1315
Runtime string
1416
IO *containerio.IO
1517
Spec *specs.Spec
18+
19+
// BaseFS is rootfs used by containerd container
20+
BaseFS string
21+
22+
// RootFSProvided is a flag to point the container is created by specify rootfs
23+
RootFSProvided bool
1624
}
1725

1826
// Process wraps exec process's info.

daemon/mgr/container.go

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

3435
"github.com/containerd/containerd/errdefs"
36+
"github.com/containerd/containerd/mount"
3537
"github.com/containerd/containerd/namespaces"
3638
"github.com/docker/libnetwork"
3739
"github.com/go-openapi/strfmt"
@@ -571,14 +573,24 @@ func (mgr *ContainerManager) createContainerdContainer(ctx context.Context, c *C
571573

572574
c.Lock()
573575
ctrdContainer := &ctrd.Container{
574-
ID: c.ID,
575-
Image: c.Config.Image,
576-
Runtime: c.HostConfig.Runtime,
577-
Spec: sw.s,
578-
IO: io,
576+
ID: c.ID,
577+
Image: c.Config.Image,
578+
Runtime: c.HostConfig.Runtime,
579+
Spec: sw.s,
580+
IO: io,
581+
RootFSProvided: c.RootFSProvided,
582+
BaseFS: c.BaseFS,
579583
}
580584
c.Unlock()
581585

586+
// if creating the container by specified the rootfs, we must check
587+
// whether the rootfs is mounted before creation
588+
if c.RootFSProvided {
589+
if err := mgr.ensureRootFSMounted(c.BaseFS, c.Snapshotter.Data); err != nil {
590+
return fmt.Errorf("failed to mount container rootfs: %v", err)
591+
}
592+
}
593+
582594
if err := mgr.Client.CreateContainer(ctx, ctrdContainer); err != nil {
583595
logrus.Errorf("failed to create new containerd container: %v", err)
584596

@@ -605,6 +617,53 @@ func (mgr *ContainerManager) createContainerdContainer(ctx context.Context, c *C
605617
return c.Write(mgr.Store)
606618
}
607619

620+
func (mgr *ContainerManager) ensureRootFSMounted(rootfs string, snapData map[string]string) error {
621+
if rootfs == "" || len(snapData) == 0 {
622+
return fmt.Errorf("container rootfs or snapshotter data is empty")
623+
}
624+
625+
// check if rootfs already mounted
626+
notMounted, err := mountutils.IsLikelyNotMountPoint(rootfs)
627+
if err != nil {
628+
return err
629+
}
630+
// rootfs already mounted
631+
if !notMounted {
632+
return nil
633+
}
634+
635+
var workDir, upperDir, lowerDir string
636+
for _, dir := range []string{"WorkDir", "UpperDir", "LowerDir"} {
637+
if v, ok := snapData[dir]; ok {
638+
switch dir {
639+
case "WorkDir":
640+
workDir = v
641+
case "UpperDir":
642+
upperDir = v
643+
case "LowerDir":
644+
lowerDir = v
645+
}
646+
}
647+
}
648+
649+
if workDir == "" || upperDir == "" || lowerDir == "" {
650+
return fmt.Errorf("faile to mount overlay: one or more dirs in WorkDir, UpperDir and LowerDir are empty")
651+
}
652+
653+
options := []string{
654+
fmt.Sprintf("workdir=%s", snapData["WorkDir"]),
655+
fmt.Sprintf("upperdir=%s", snapData["UpperDir"]),
656+
fmt.Sprintf("lowerdir=%s", snapData["LowerDir"]),
657+
}
658+
mount := mount.Mount{
659+
Type: "overlay",
660+
Source: "overlay",
661+
Options: options,
662+
}
663+
664+
return mount.Mount(rootfs)
665+
}
666+
608667
// Stop stops a running container.
609668
func (mgr *ContainerManager) Stop(ctx context.Context, name string, timeout int64) error {
610669
c, err := mgr.container(name)
@@ -934,9 +993,13 @@ func (mgr *ContainerManager) Remove(ctx context.Context, name string, options *t
934993
// remove container cache
935994
mgr.cache.Remove(c.ID)
936995

937-
// remove snapshot
938-
if err := mgr.Client.RemoveSnapshot(ctx, c.ID); err != nil {
939-
logrus.Errorf("failed to remove snapshot of container %s: %v", c.ID, err)
996+
// if creating the container by specify rootfs,
997+
// there is no snapshot for this container.
998+
if !c.RootFSProvided {
999+
// remove snapshot
1000+
if err := mgr.Client.RemoveSnapshot(ctx, c.ID); err != nil {
1001+
logrus.Errorf("failed to remove snapshot of container %s: %v", c.ID, err)
1002+
}
9401003
}
9411004

9421005
return nil

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)