diff --git a/image/layers.go b/fs/layers.go similarity index 86% rename from image/layers.go rename to fs/layers.go index f856ff81d2..fa03ca5865 100644 --- a/image/layers.go +++ b/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 } diff --git a/fs/layers_test.go b/fs/layers_test.go new file mode 100644 index 0000000000..3d8e9e32a7 --- /dev/null +++ b/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 +} diff --git a/fs/store.go b/fs/store.go index 4ba23ae7dd..9bbc52e18e 100644 --- a/fs/store.go +++ b/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,