From c885a05bdf337864f50443ae6dc5795ce55533ca Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 25 Jan 2013 11:32:37 -0800 Subject: [PATCH] First implementation of a layer store. 'docker pull' and 'docker put' now really work (but containers are still fake) --- dockerd/dockerd.go | 43 ++++++++++++--- future/layers.go | 135 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 9 deletions(-) create mode 100644 future/layers.go diff --git a/dockerd/dockerd.go b/dockerd/dockerd.go index eb1923e7f1..b129008272 100644 --- a/dockerd/dockerd.go +++ b/dockerd/dockerd.go @@ -19,6 +19,7 @@ import ( "sort" "os" "time" + "net/http" ) @@ -138,9 +139,16 @@ func (docker *Docker) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...str if len(args) < 1 { return errors.New("Not enough arguments") } - time.Sleep(2 * time.Second) - layer := docker.addContainer(args[0], "download", 0) - fmt.Fprintln(stdout, layer.Id) + resp, err := http.Get(args[0]) + if err != nil { + return err + } + layer, err := docker.layers.AddLayer(resp.Body, stdout) + if err != nil { + return err + } + docker.addContainer(args[0], "download", 0) + fmt.Fprintln(stdout, layer.Id()) return nil } @@ -148,12 +156,17 @@ func (docker *Docker) CmdPut(stdin io.ReadCloser, stdout io.Writer, args ...stri if len(args) < 1 { return errors.New("Not enough arguments") } - time.Sleep(1 * time.Second) - layer := docker.addContainer(args[0], "upload", 0) - fmt.Fprintln(stdout, layer.Id) + fmt.Printf("Adding layer\n") + layer, err := docker.layers.AddLayer(stdin, stdout) + if err != nil { + return err + } + docker.addContainer(args[0], "upload", 0) + fmt.Fprintln(stdout, layer.Id()) return nil } + func (docker *Docker) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error { flags := rcli.Subcmd(stdout, "fork", "[OPTIONS] CONTAINER [DEST]", @@ -391,7 +404,10 @@ func startCommand(cmd *exec.Cmd, interactive bool) (io.WriteCloser, io.ReadClose func main() { future.Seed() flag.Parse() - docker := New() + docker, err := New() + if err != nil { + log.Fatal(err) + } go func() { if err := rcli.ListenAndServeHTTP(":8080", docker); err != nil { log.Fatal(err) @@ -402,11 +418,19 @@ func main() { } } -func New() *Docker { +func New() (*Docker, error) { + store, err := future.NewStore("/var/lib/docker/layers") + if err != nil { + return nil, err + } + if err := store.Init(); err != nil { + return nil, err + } return &Docker{ containersByName: make(map[string]*ByDate), containers: make(map[string]*Container), - } + layers: store, + }, nil } @@ -453,6 +477,7 @@ func (docker *Docker) CmdWeb(stdin io.ReadCloser, stdout io.Writer, args ...stri type Docker struct { containers map[string]*Container containersByName map[string]*ByDate + layers *future.Store } type Container struct { diff --git a/future/layers.go b/future/layers.go new file mode 100644 index 0000000000..b85802dc4f --- /dev/null +++ b/future/layers.go @@ -0,0 +1,135 @@ +package future + +import ( + "errors" + "path" + "path/filepath" + "io" + "os" + "os/exec" +) + +type Store struct { + Root string +} + + +func NewStore(root string) (*Store, error) { + abspath, err := filepath.Abs(root) + if err != nil { + return nil, err + } + return &Store{ + Root: abspath, + }, nil +} + +func (store *Store) Get(id string) (*Layer, bool) { + layer := &Layer{Path: store.layerPath(id)} + if !layer.Exists() { + return nil, false + } + return layer, true +} + +func (store *Store) Exists() (bool, error) { + if stat, err := os.Stat(store.Root); err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } else if !stat.IsDir() { + return false, errors.New("Not a directory: " + store.Root) + } + return true, nil +} + +func (store *Store) Init() error { + if exists, err := store.Exists(); err != nil { + return err + } else if exists { + return nil + } + return os.Mkdir(store.Root, 0700) +} + + +func (store *Store) Mktemp() (string, error) { + tmpName := RandomId() + tmpPath := path.Join(store.Root, "tmp-" + tmpName) + if err := os.Mkdir(tmpPath, 0700); err != nil { + return "", err + } + return tmpPath, nil +} + +func (store *Store) layerPath(id string) string { + return path.Join(store.Root, id) +} + + +func (store *Store) AddLayer(archive io.Reader, stderr io.Writer) (*Layer, error) { + tmp, err := store.Mktemp() + defer os.RemoveAll(tmp) + if err != nil { + return nil, err + } + untarCmd := exec.Command("tar", "-C", tmp, "-x") + untarW, err := untarCmd.StdinPipe() + if err != nil { + return nil, err + } + untarStderr, err := untarCmd.StderrPipe() + if err != nil { + return nil, err + } + go io.Copy(stderr, untarStderr) + untarStdout, err := untarCmd.StdoutPipe() + if err != nil { + return nil, err + } + go io.Copy(stderr, untarStdout) + untarCmd.Start() + hashR, hashW := io.Pipe() + job_copy := Go(func() error { + _, err := io.Copy(io.MultiWriter(hashW, untarW), archive) + hashW.Close() + untarW.Close() + return err + }) + id, err := ComputeId(hashR) + if err != nil { + return nil, err + } + if err := untarCmd.Wait(); err != nil { + return nil, err + } + if err := <-job_copy; err != nil { + return nil, err + } + layer := &Layer{Path: store.layerPath(id)} + if !layer.Exists() { + if err := os.Rename(tmp, layer.Path); err != nil { + return nil, err + } + } + + return layer, nil +} + + +type Layer struct { + Path string +} + +func (layer *Layer) Exists() bool { + st, err := os.Stat(layer.Path) + if err != nil { + return false + } + return st.IsDir() +} + +func (layer *Layer) Id() string { + return path.Base(layer.Path) +}