snapshot: provide naive/vfs POC implementation
Signed-off-by: Stephen J Day <stephen.day@docker.com>
This commit is contained in:
parent
65e668a6b8
commit
aee6045292
2 changed files with 224 additions and 0 deletions
117
snapshot/naive.go
Normal file
117
snapshot/naive.go
Normal file
|
@ -0,0 +1,117 @@
|
|||
package snapshot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/containerd"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Naive struct {
|
||||
root string
|
||||
|
||||
// TODO(stevvooe): Very lazy copying from the overlay driver. We'll have to
|
||||
// put *all* of this on disk in an easy to access format.
|
||||
active map[string]activeNaiveSnapshot
|
||||
parents map[string]string // mirror of what is on disk
|
||||
}
|
||||
|
||||
type activeNaiveSnapshot struct {
|
||||
parent string
|
||||
metadata string
|
||||
}
|
||||
|
||||
func NewNaive(root string) (*Naive, error) {
|
||||
if err := os.MkdirAll(root, 0777); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(stevvooe): Recover active transactions.
|
||||
|
||||
return &Naive{
|
||||
root: root,
|
||||
active: make(map[string]activeNaiveSnapshot),
|
||||
parents: make(map[string]string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Prepare works per the snapshot specification.
|
||||
//
|
||||
// For the naive driver, the data is checked out directly into dst and no
|
||||
// mounts are returned.
|
||||
func (lm *Naive) Prepare(dst, parent string) ([]containerd.Mount, error) {
|
||||
metadataRoot, err := ioutil.TempDir(lm.root, "active-")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to created transaction dir")
|
||||
}
|
||||
|
||||
// TODO(stevvooe): Write in driver metadata so it can be identified,
|
||||
// probably part of common manager type.
|
||||
|
||||
if err := ioutil.WriteFile(filepath.Join(metadataRoot, "target"), []byte(dst), 0777); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to write target to disk")
|
||||
}
|
||||
|
||||
if parent != "" {
|
||||
if _, ok := lm.parents[parent]; !ok {
|
||||
return nil, errors.Wrap(err, "specified parent does not exist")
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(filepath.Join(metadataRoot, "parent"), []byte(parent), 0777); err != nil {
|
||||
return nil, errors.Wrap(err, "error specifying parent")
|
||||
}
|
||||
|
||||
// Now, we copy the parent filesystem, just a directory, into dst.
|
||||
if err := archive.CopyWithTar(filepath.Join(parent, "data"), dst); err != nil { // note: src, dst args, ick!
|
||||
return nil, errors.Wrap(err, "copying of parent failed")
|
||||
}
|
||||
}
|
||||
|
||||
lm.active[dst] = activeNaiveSnapshot{
|
||||
parent: parent,
|
||||
metadata: metadataRoot,
|
||||
}
|
||||
|
||||
return nil, nil // no mounts!!
|
||||
}
|
||||
|
||||
// Commit just moves the metadata directory to the diff location.
|
||||
func (lm *Naive) Commit(diff, dst string) error {
|
||||
active, ok := lm.active[dst]
|
||||
if !ok {
|
||||
return errors.Errorf("%v is not an active transaction", dst)
|
||||
}
|
||||
|
||||
// Move the data into our metadata directory, we could probably save disk
|
||||
// space if we just saved the diff, but let's get something working.
|
||||
if err := archive.CopyWithTar(dst, filepath.Join(active.metadata, "data")); err != nil { // note: src, dst args, ick!
|
||||
return errors.Wrap(err, "copying of parent failed")
|
||||
}
|
||||
|
||||
if err := os.Rename(active.metadata, diff); err != nil {
|
||||
return errors.Wrap(err, "failed to rename metadata into diff")
|
||||
}
|
||||
|
||||
lm.parents[diff] = active.parent
|
||||
delete(lm.active, dst)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lm *Naive) Rollback(dst string) error {
|
||||
active, ok := lm.active[dst]
|
||||
if !ok {
|
||||
return fmt.Errorf("%q must be an active snapshot", dst)
|
||||
}
|
||||
|
||||
delete(lm.active, dst)
|
||||
return os.RemoveAll(active.metadata)
|
||||
}
|
||||
|
||||
func (lm *Naive) Parent(diff string) string {
|
||||
return lm.parents[diff]
|
||||
}
|
107
snapshot/naive_test.go
Normal file
107
snapshot/naive_test.go
Normal file
|
@ -0,0 +1,107 @@
|
|||
package snapshot
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/containerd"
|
||||
)
|
||||
|
||||
func TestSnapshotNaiveBasic(t *testing.T) {
|
||||
tmpDir, err := ioutil.TempDir("", "test-layman-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// defer os.RemoveAll(tmpDir)
|
||||
|
||||
t.Log(tmpDir)
|
||||
root := filepath.Join(tmpDir, "root")
|
||||
|
||||
lm, err := NewNaive(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
preparing := filepath.Join(tmpDir, "preparing")
|
||||
if err := os.MkdirAll(preparing, 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mounts, err := lm.Prepare(preparing, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, mount := range mounts {
|
||||
if !strings.HasPrefix(mount.Target, preparing) {
|
||||
t.Fatalf("expected mount target to be prefixed with tmpDir: %q does not startwith %q", mount.Target, preparing)
|
||||
}
|
||||
|
||||
t.Log(containerd.MountCommand(mount))
|
||||
}
|
||||
|
||||
if err := containerd.MountAll(mounts...); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(filepath.Join(preparing, "foo"), []byte("foo\n"), 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
os.MkdirAll(preparing+"/a/b/c", 0755)
|
||||
|
||||
// defer os.Remove(filepath.Join(tmpDir, "foo"))
|
||||
|
||||
committed := filepath.Join(lm.root, "committed")
|
||||
|
||||
if err := lm.Commit(committed, preparing); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if lm.Parent(preparing) != "" {
|
||||
t.Fatalf("parent of new layer should be empty, got lm.Parent(%q) == %q", preparing, lm.Parent(preparing))
|
||||
}
|
||||
|
||||
next := filepath.Join(tmpDir, "nextlayer")
|
||||
if err := os.MkdirAll(next, 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mounts, err = lm.Prepare(next, committed)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := containerd.MountAll(mounts...); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, mount := range mounts {
|
||||
if !strings.HasPrefix(mount.Target, next) {
|
||||
t.Fatalf("expected mount target to be prefixed with tmpDir: %q does not startwith %q", mount.Target, next)
|
||||
}
|
||||
|
||||
t.Log(containerd.MountCommand(mount))
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(filepath.Join(next, "bar"), []byte("bar\n"), 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// also, change content of foo to bar
|
||||
if err := ioutil.WriteFile(filepath.Join(next, "foo"), []byte("bar\n"), 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
os.RemoveAll(next + "/a/b")
|
||||
nextCommitted := filepath.Join(lm.root, "committed-next")
|
||||
if err := lm.Commit(nextCommitted, next); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if lm.Parent(nextCommitted) != committed {
|
||||
t.Fatalf("parent of new layer should be %q, got lm.Parent(%q) == %q (%#v)", committed, next, lm.Parent(next), lm.parents)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue