From f2739f0728429acfe4f0776405bf084633420120 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Fri, 26 May 2017 10:20:41 -0700 Subject: [PATCH] cachemanager: new active creation Signed-off-by: Tonis Tiigi --- cachemanager/cachemanager.go | 69 +++++++++++++++++++++++++------ cachemanager/cachemanager_test.go | 28 ++++++++++++- hack/dockerfiles/test.Dockerfile | 1 + snapshot/localmounter.go | 59 ++++++++++++++++++++++++++ snapshot/snapshotter.go | 8 ++++ 5 files changed, 152 insertions(+), 13 deletions(-) create mode 100644 snapshot/localmounter.go diff --git a/cachemanager/cachemanager.go b/cachemanager/cachemanager.go index ac676a5d..79690673 100644 --- a/cachemanager/cachemanager.go +++ b/cachemanager/cachemanager.go @@ -2,12 +2,15 @@ package cachemanager import ( "context" + "crypto/rand" + "encoding/hex" "os" "path/filepath" "sync" "time" "github.com/boltdb/bolt" + "github.com/containerd/containerd/mount" "github.com/pkg/errors" "github.com/tonistiigi/buildkit_poc/snapshot" ) @@ -45,23 +48,18 @@ type SnapshotRef interface { type ActiveRef interface { Mountable - Release() (SnapshotRef, error) + ReleaseActive() (SnapshotRef, error) ReleaseAndCommit(ctx context.Context) (SnapshotRef, error) Size() (int64, error) } type Mountable interface { - Mount() (Mount, error) -} - -type Mount interface { // replace with containerd.Mount - Mount() (string, error) - Unmount() error + Mount() ([]mount.Mount, error) } type CacheAccessor interface { Get(id string) (SnapshotRef, error) - New(id string, s SnapshotRef) (ActiveRef, error) + New(s SnapshotRef) (ActiveRef, error) GetActive(id string) (ActiveRef, error) // Rebase? } @@ -133,8 +131,25 @@ func (cm *cacheManager) Get(id string) (SnapshotRef, error) { } return rec.ref(), nil } -func (cm *cacheManager) New(id string, s SnapshotRef) (ActiveRef, error) { - return nil, errors.New("New not implemented") +func (cm *cacheManager) New(s SnapshotRef) (ActiveRef, error) { + if s != nil { + return nil, errors.New("snapshot parents not implemented") // TODO + } + + id := generateID() + + if _, err := cm.Snapshotter.Prepare(context.TODO(), id, ""); err != nil { + return nil, errors.Wrapf(err, "failed to prepare %s", id) + } + + rec := &cacheRecord{ + active: true, + id: id, + cm: cm, + refs: make(map[*snapshotRef]struct{}), + } + + return rec.ref(), nil } func (cm *cacheManager) GetActive(id string) (ActiveRef, error) { // Rebase? return nil, errors.New("GetActive not implemented") @@ -153,9 +168,12 @@ func (cm *cacheManager) GC(ctx context.Context) error { } type cacheRecord struct { + mu sync.Mutex active bool // meta SnapMeta refs map[*snapshotRef]struct{} + id string + cm *cacheManager } // hold manager lock before calling @@ -169,14 +187,41 @@ type snapshotRef struct { *cacheRecord } -func (sr *snapshotRef) Mount() (Mount, error) { - return nil, errors.New("Mount not implemented") +func (sr *snapshotRef) Mount() ([]mount.Mount, error) { + sr.mu.Lock() + defer sr.mu.Unlock() + + if sr.active { + m, err := sr.cm.Snapshotter.Mounts(context.TODO(), sr.id) + if err != nil { + return nil, errors.Wrapf(err, "failed to mount %s", sr.id) + } + return m, nil + } + + return nil, errors.New("snapshot mount not implemented") } func (sr *snapshotRef) Release() error { return errors.New("Release not implemented") } +func (sr *snapshotRef) ReleaseActive() (SnapshotRef, error) { + return nil, errors.New("ReleaseActive not implemented") +} + +func (sr *snapshotRef) ReleaseAndCommit(ctx context.Context) (SnapshotRef, error) { + return nil, errors.New("ReleaseActive not implemented") +} + func (sr *snapshotRef) Size() (int64, error) { return -1, errors.New("Size not implemented") } + +func generateID() string { + b := make([]byte, 32) + if _, err := rand.Read(b); err != nil { + panic(err) + } + return hex.EncodeToString(b) +} diff --git a/cachemanager/cachemanager_test.go b/cachemanager/cachemanager_test.go index e3481a8c..0c274ab0 100644 --- a/cachemanager/cachemanager_test.go +++ b/cachemanager/cachemanager_test.go @@ -3,9 +3,12 @@ package cachemanager import ( "io/ioutil" "os" + "path/filepath" "testing" + "github.com/containerd/containerd/snapshot/naive" "github.com/stretchr/testify/assert" + "github.com/tonistiigi/buildkit_poc/snapshot" ) func TestCacheManager(t *testing.T) { @@ -13,12 +16,35 @@ func TestCacheManager(t *testing.T) { assert.NoError(t, err) defer os.RemoveAll(tmpdir) - cm, err := NewCacheManager(CacheManagerOpt{Root: tmpdir}) + snapshotter, err := naive.NewSnapshotter(filepath.Join(tmpdir, "snapshots")) + assert.NoError(t, err) + + cm, err := NewCacheManager(CacheManagerOpt{ + Root: tmpdir, + Snapshotter: snapshotter, + }) assert.NoError(t, err) _, err = cm.Get("foobar") assert.Error(t, err) + active, err := cm.New(nil) + assert.NoError(t, err) + + m, err := active.Mount() + assert.NoError(t, err) + + lm := snapshot.LocalMounter(m) + target, err := lm.Mount() + assert.NoError(t, err) + + fi, err := os.Stat(target) + assert.NoError(t, err) + assert.Equal(t, fi.IsDir(), true) + + err = lm.Unmount() + assert.NoError(t, err) + err = cm.Close() assert.NoError(t, err) } diff --git a/hack/dockerfiles/test.Dockerfile b/hack/dockerfiles/test.Dockerfile index 67504a76..45ec6f69 100644 --- a/hack/dockerfiles/test.Dockerfile +++ b/hack/dockerfiles/test.Dockerfile @@ -1,4 +1,5 @@ FROM golang:1.8-alpine AS vndr +RUN apk add --no-cache g++ WORKDIR /go/src/github.com/tonistiigi/buildkit_poc COPY . . RUN go test ./... \ No newline at end of file diff --git a/snapshot/localmounter.go b/snapshot/localmounter.go new file mode 100644 index 00000000..cc6706a8 --- /dev/null +++ b/snapshot/localmounter.go @@ -0,0 +1,59 @@ +package snapshot + +import ( + "io/ioutil" + "os" + "sync" + + "github.com/containerd/containerd/mount" + "github.com/pkg/errors" +) + +type Mounter interface { + Mount() (string, error) + Unmount() error +} + +func LocalMounter(m []mount.Mount) Mounter { + return &localMounter{m: m} +} + +type localMounter struct { + mu sync.Mutex + m []mount.Mount + target string +} + +func (lm *localMounter) Mount() (string, error) { + lm.mu.Lock() + defer lm.mu.Unlock() + + if len(lm.m) == 1 && lm.m[0].Type == "bind" { + return lm.m[0].Source, nil + } + + dir, err := ioutil.TempDir("", "buildkit-mount") + if err != nil { + return "", errors.Wrap(err, "failed to create temp dir") + } + + if err := mount.MountAll(lm.m, dir); err != nil { + os.RemoveAll(dir) + return "", err + } + lm.target = dir + return "", nil +} + +func (lm *localMounter) Unmount() error { + lm.mu.Lock() + defer lm.mu.Unlock() + + if lm.target != "" { + if err := mount.Unmount(lm.target, 0); err != nil { + return err + } + lm.target = "" + } + return nil +} diff --git a/snapshot/snapshotter.go b/snapshot/snapshotter.go index 3875c51b..e26aba6e 100644 --- a/snapshot/snapshotter.go +++ b/snapshot/snapshotter.go @@ -1,5 +1,13 @@ package snapshot +import ( + "context" + + "github.com/containerd/containerd/mount" +) + // Snapshotter defines interface that any snapshot implementation should satisfy type Snapshotter interface { + Prepare(ctx context.Context, key, parent string) ([]mount.Mount, error) + Mounts(ctx context.Context, key string) ([]mount.Mount, error) }