Browse Source

docker/fs: initial support for filesystem layers (adapted from image/layers.go)

Solomon Hykes 12 years ago
parent
commit
6372a1a0d0
3 changed files with 103 additions and 7 deletions
  1. 10 6
      fs/layers.go
  2. 80 0
      fs/layers_test.go
  3. 13 1
      fs/store.go

+ 10 - 6
image/layers.go → fs/layers.go

@@ -1,4 +1,4 @@
-package image
+package fs
 
 import (
 	"errors"
@@ -20,6 +20,10 @@ func NewLayerStore(root string) (*LayerStore, error) {
 	if err != nil {
 		return nil, err
 	}
+	// Create the root directory if it doesn't exists
+	if err := os.Mkdir(root, 0700); err != nil && !os.IsExist(err) {
+		return nil, err
+	}
 	return &LayerStore{
 		Root: abspath,
 	}, nil
@@ -82,7 +86,10 @@ func (store *LayerStore) layerPath(id string) string {
 }
 
 
-func (store *LayerStore) AddLayer(archive io.Reader, stderr io.Writer, compression Compression) (string, error) {
+func (store *LayerStore) AddLayer(id string, archive Archive, stderr io.Writer, compression Compression) (string, error) {
+	if _, err := os.Stat(store.layerPath(id)); err == nil {
+		return "", errors.New("Layer already exists: " + id)
+	}
 	tmp, err := store.Mktemp()
 	defer os.RemoveAll(tmp)
 	if err != nil {
@@ -110,14 +117,11 @@ func (store *LayerStore) AddLayer(archive io.Reader, stderr io.Writer, compressi
 	}
 	go io.Copy(stderr, untarStdout)
 	untarCmd.Start()
-	hashR, hashW := io.Pipe()
 	job_copy := future.Go(func() error {
-		_, err := io.Copy(io.MultiWriter(hashW, untarW), archive)
-		hashW.Close()
+		_, err := io.Copy(untarW, archive)
 		untarW.Close()
 		return err
 	})
-	id, err := future.ComputeId(hashR)
 	if err != nil {
 		return "", err
 	}

+ 80 - 0
fs/layers_test.go

@@ -0,0 +1,80 @@
+package fs
+
+import (
+	"io/ioutil"
+	"testing"
+	"os"
+	"github.com/dotcloud/docker/fake"
+)
+
+
+
+func TestLayersInit(t *testing.T) {
+	store := tempStore(t)
+	defer os.RemoveAll(store.Root)
+	// Root should exist
+	if _, err := os.Stat(store.Root); err != nil {
+		t.Fatal(err)
+	}
+	// List() should be empty
+	if l := store.List(); len(l) != 0 {
+		t.Fatalf("List() should return %d, not %d", 0, len(l))
+	}
+}
+
+func TestAddLayer(t *testing.T) {
+	store := tempStore(t)
+	defer os.RemoveAll(store.Root)
+	layer, err := store.AddLayer("foo", testArchive(t), os.Stderr, Uncompressed)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// Layer path should exist
+	if _, err := os.Stat(layer); err != nil {
+		t.Fatal(err)
+	}
+	// List() should return 1 layer
+	if l := store.List(); len(l) != 1 {
+		t.Fatalf("List() should return %d elements, not %d", 1, len(l))
+	}
+	// Get("foo") should return the correct layer
+	if foo := store.Get("foo"); foo != layer {
+		t.Fatalf("get(\"foo\") should return '%d', not '%d'", layer, foo)
+	}
+}
+
+func TestAddLayerDuplicate(t *testing.T) {
+	store := tempStore(t)
+	defer os.RemoveAll(store.Root)
+	if _, err := store.AddLayer("foobar123", testArchive(t), os.Stderr, Uncompressed); err != nil {
+		t.Fatal(err)
+	}
+	if _, err := store.AddLayer("foobar123", testArchive(t), os.Stderr, Uncompressed); err == nil {
+		t.Fatalf("Creating duplicate layer should fail")
+	}
+}
+
+
+/*
+ * HELPER FUNCTIONS
+ */
+
+func tempStore(t *testing.T) *LayerStore {
+	tmp, err := ioutil.TempDir("", "docker-fs-layerstore-")
+	if err != nil {
+		t.Fatal(err)
+	}
+	store, err := NewLayerStore(tmp)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return store
+}
+
+func testArchive(t *testing.T) Archive {
+	archive, err := fake.FakeTar()
+	if err != nil {
+		t.Fatal(err)
+	}
+	return archive
+}

+ 13 - 1
fs/store.go

@@ -14,6 +14,7 @@ type Store struct {
 	Root	string
 	db	*sql.DB
 	orm	*gorp.DbMap
+	layers	*LayerStore
 }
 
 type Archive io.Reader
@@ -33,10 +34,15 @@ func New(root string) (*Store, error) {
 	if err := orm.CreateTables(); err != nil {
 		return nil, err
 	}
+	layers, err := NewLayerStore(path.Join(root, "layers"))
+	if err != nil {
+		return nil, err
+	}
 	return &Store{
 		Root: root,
 		db: db,
 		orm: orm,
+		layers: layers,
 	}, nil
 }
 
@@ -88,13 +94,19 @@ func (store *Store) Get(id string) (*Image, error) {
 	return img.(*Image), err
 }
 
-func (store *Store) Create(layer Archive, parent *Image, pth, comment string) (*Image, error) {
+func (store *Store) Create(layerData Archive, parent *Image, pth, comment string) (*Image, error) {
 	// FIXME: actually do something with the layer...
 	img := &Image{
 		Id :		future.RandomId(),
 		Comment:	comment,
 		store:		store,
 	}
+	// FIXME: we shouldn't have to pass os.Stderr to AddLayer()...
+	// FIXME: Archive should contain compression info. For now we only support uncompressed.
+	_, err := store.layers.AddLayer(img.Id, layerData, os.Stderr, Uncompressed)
+	if err != nil {
+		return nil, err
+	}
 	path := &Path{
 		Path:		path.Clean(pth),
 		Image:		img.Id,