diff --git a/api/handlers.go b/api/handlers.go index 0671064982..98ea62f728 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -10,6 +10,9 @@ import ( "sort" "strconv" "strings" + "os" + "io" + "path" dockerfilters "github.com/docker/docker/pkg/parsers/filters" "github.com/docker/swarm/cluster" @@ -270,6 +273,40 @@ func postImagesCreate(c *context, w http.ResponseWriter, r *http.Request) { } } +// POST /images/load +func postImagesLoad(c *context, w http.ResponseWriter, r *http.Request) { + //cache tar file + tmpImageDir, err := ioutil.TempDir("", "docker-import-") + if err != nil { + httpError(w, err.Error(), http.StatusInternalServerError) + return + } + defer os.RemoveAll(tmpImageDir) + + repoTarFile := path.Join(tmpImageDir, "repo.tar") + tarFile, err := os.Create(repoTarFile) + if err != nil { + httpError(w, err.Error(), http.StatusInternalServerError) + return + } + if _, err := io.Copy(tarFile, r.Body); err != nil { + httpError(w, err.Error(), http.StatusInternalServerError) + return + } + tarFile.Close() + + // call cluster to load image on every node + wf := NewWriteFlusher(w) + callback := func(what, status string) { + if status == "" { + fmt.Fprintf(wf, "%s:Loading Image...\n", what) + } else { + fmt.Fprintf(wf, "%s:Loading Image... %s\n", what,status) + } + } + c.cluster.Load(repoTarFile, callback) +} + // GET /events func getEvents(c *context, w http.ResponseWriter, r *http.Request) { c.eventsHandler.Add(r.RemoteAddr, w) diff --git a/cluster/cluster.go b/cluster/cluster.go index 3b0beaab7d..4fb1a9891d 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -33,6 +33,12 @@ type Cluster interface { // `status` is the current status, like "", "in progress" or "downloaded Pull(name string, callback func(what, status string)) + // Load images + // `callback` can be called multiple time + // `what` is what is being loaded + // `status` is the current status, like "", "in progress" or "loaded" + Load(tarFile string, callback func(what, status string)) + // Return some info about the cluster, like nb or containers / images // It is pretty open, so the implementation decides what to return. Info() [][2]string diff --git a/cluster/engine.go b/cluster/engine.go index 81d26bc0e4..9b7084094c 100644 --- a/cluster/engine.go +++ b/cluster/engine.go @@ -8,6 +8,7 @@ import ( "strings" "sync" "time" + "os" log "github.com/Sirupsen/logrus" "github.com/samalba/dockerclient" @@ -411,6 +412,19 @@ func (e *Engine) Pull(image string) error { return nil } +// Load an image on the engine +func (e *Engine) Load(tarFile string) error { + file, err := os.Open(tarFile) + if err != nil { + return err + } + + if err := e.client.LoadImage(file); err != nil { + return err + } + return nil +} + // RegisterEventHandler registers an event handler. func (e *Engine) RegisterEventHandler(h EventHandler) error { if e.eventHandler != nil { diff --git a/cluster/swarm/cluster.go b/cluster/swarm/cluster.go index 1ebd529463..dac0385db3 100644 --- a/cluster/swarm/cluster.go +++ b/cluster/swarm/cluster.go @@ -238,6 +238,31 @@ func (c *Cluster) Pull(name string, callback func(what, status string)) { wg.Wait() } +// Load image +func (c *Cluster) Load(tarFile string, callback func(what, status string)) { + size := len(c.engines) + done := make(chan bool, size) + for _, n := range c.engines { + go func(nn *cluster.Engine) { + if callback != nil { + callback(nn.Name, "") + } + err := nn.Load(tarFile) + if callback != nil { + if err != nil { + callback(nn.Name, err.Error()) + } else { + callback(nn.Name, "loaded") + } + } + done <- true + }(n) + } + for i := 0; i < size; i++ { + <-done + } +} + // Containers returns all the containers in the cluster. func (c *Cluster) Containers() []*cluster.Container { c.RLock()