diff --git a/Makefile b/Makefile index b60b2a4d00..257fefdfee 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all binary build cross default docs docs-build docs-shell shell test test-unit test-integration test-integration-cli test-docker-py validate +.PHONY: all binary build cross default docs docs-build docs-shell shell test test-unit test-integration-cli test-docker-py validate # env vars passed through directly to Docker's build scripts # to allow things like `make DOCKER_CLIENTONLY=1 binary` easily @@ -62,14 +62,11 @@ docs-test: docs-build $(DOCKER_RUN_DOCS) "$(DOCKER_DOCS_IMAGE)" ./test.sh test: build - $(DOCKER_RUN_DOCKER) hack/make.sh binary cross test-unit test-integration test-integration-cli test-docker-py + $(DOCKER_RUN_DOCKER) hack/make.sh binary cross test-unit test-integration-cli test-docker-py test-unit: build $(DOCKER_RUN_DOCKER) hack/make.sh test-unit -test-integration: build - $(DOCKER_RUN_DOCKER) hack/make.sh test-integration - test-integration-cli: build $(DOCKER_RUN_DOCKER) hack/make.sh binary test-integration-cli diff --git a/api/client/start.go b/api/client/start.go index d3dec9489d..b290524caf 100644 --- a/api/client/start.go +++ b/api/client/start.go @@ -1,13 +1,14 @@ package client import ( + "encoding/json" "fmt" "io" "net/url" "os" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/signal" @@ -65,12 +66,12 @@ func (cli *DockerCli) CmdStart(args ...string) error { return err } - env := engine.Env{} - if err := env.Decode(stream); err != nil { + var c types.ContainerJSON + if err := json.NewDecoder(stream).Decode(&c); err != nil { return err } - config := env.GetSubEnv("Config") - tty = config.GetBool("Tty") + + tty = c.Config.Tty if !tty { sigc := cli.forwardAllSignals(cmd.Arg(0)) @@ -82,7 +83,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { v := url.Values{} v.Set("stream", "1") - if *openStdin && config.GetBool("OpenStdin") { + if *openStdin && c.Config.OpenStdin { v.Set("stdin", "1") in = cli.in } diff --git a/api/client/utils.go b/api/client/utils.go index 7a52ad25f4..eed1163f83 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -22,7 +22,6 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/cliconfig" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/stdcopy" @@ -42,18 +41,8 @@ func (cli *DockerCli) HTTPClient() *http.Client { func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) { params := bytes.NewBuffer(nil) if data != nil { - if env, ok := data.(engine.Env); ok { - if err := env.Encode(params); err != nil { - return nil, err - } - } else { - buf, err := json.Marshal(data) - if err != nil { - return nil, err - } - if _, err := params.Write(buf); err != nil { - return nil, err - } + if err := json.NewEncoder(params).Encode(data); err != nil { + return nil, err } } return params, nil diff --git a/api/client/version.go b/api/client/version.go index 25a7e367e2..2fb6f8a8d5 100644 --- a/api/client/version.go +++ b/api/client/version.go @@ -1,13 +1,14 @@ package client import ( + "encoding/json" "fmt" "runtime" "github.com/Sirupsen/logrus" "github.com/docker/docker/api" + "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" - "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" ) @@ -32,28 +33,24 @@ func (cli *DockerCli) CmdVersion(args ...string) error { } fmt.Fprintf(cli.out, "OS/Arch (client): %s/%s\n", runtime.GOOS, runtime.GOARCH) - body, _, err := readBody(cli.call("GET", "/version", nil, nil)) + stream, _, err := cli.call("GET", "/version", nil, nil) if err != nil { return err } - out := engine.NewOutput() - remoteVersion, err := out.AddEnv() - if err != nil { + var v types.Version + if err := json.NewDecoder(stream).Decode(&v); err != nil { logrus.Errorf("Error reading remote version: %s", err) return err } - if _, err := out.Write(body); err != nil { - logrus.Errorf("Error reading remote version: %s", err) - return err + + fmt.Fprintf(cli.out, "Server version: %s\n", v.Version) + if v.ApiVersion != "" { + fmt.Fprintf(cli.out, "Server API version: %s\n", v.ApiVersion) } - out.Close() - fmt.Fprintf(cli.out, "Server version: %s\n", remoteVersion.Get("Version")) - if apiVersion := remoteVersion.Get("ApiVersion"); apiVersion != "" { - fmt.Fprintf(cli.out, "Server API version: %s\n", apiVersion) - } - fmt.Fprintf(cli.out, "Go version (server): %s\n", remoteVersion.Get("GoVersion")) - fmt.Fprintf(cli.out, "Git commit (server): %s\n", remoteVersion.Get("GitCommit")) - fmt.Fprintf(cli.out, "OS/Arch (server): %s/%s\n", remoteVersion.Get("Os"), remoteVersion.Get("Arch")) + fmt.Fprintf(cli.out, "Go version (server): %s\n", v.GoVersion) + fmt.Fprintf(cli.out, "Git commit (server): %s\n", v.GitCommit) + fmt.Fprintf(cli.out, "OS/Arch (server): %s/%s\n", v.Os, v.Arch) + return nil } diff --git a/api/server/server.go b/api/server/server.go index 61e8162659..3a7975fe69 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1,9 +1,6 @@ package server import ( - "runtime" - "time" - "encoding/base64" "encoding/json" "fmt" @@ -11,8 +8,10 @@ import ( "net" "net/http" "os" + "runtime" "strconv" "strings" + "time" "code.google.com/p/go.net/websocket" "github.com/gorilla/mux" @@ -25,7 +24,6 @@ import ( "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/networkdriver/bridge" - "github.com/docker/docker/engine" "github.com/docker/docker/graph" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/parsers" @@ -53,26 +51,31 @@ type ServerConfig struct { } type Server struct { - daemon *daemon.Daemon - cfg *ServerConfig - router *mux.Router - start chan struct{} - - // TODO: delete engine - eng *engine.Engine + daemon *daemon.Daemon + cfg *ServerConfig + router *mux.Router + start chan struct{} + servers []serverCloser } -func New(cfg *ServerConfig, eng *engine.Engine) *Server { +func New(cfg *ServerConfig) *Server { srv := &Server{ cfg: cfg, start: make(chan struct{}), - eng: eng, } - r := createRouter(srv, eng) + r := createRouter(srv) srv.router = r return srv } +func (s *Server) Close() { + for _, srv := range s.servers { + if err := srv.Close(); err != nil { + logrus.Error(err) + } + } +} + func (s *Server) SetDaemon(d *daemon.Daemon) { s.daemon = d } @@ -92,19 +95,15 @@ func (s *Server) ServeApi(protoAddrs []string) error { if len(protoAddrParts) != 2 { return fmt.Errorf("bad format, expected PROTO://ADDR") } + srv, err := s.newServer(protoAddrParts[0], protoAddrParts[1]) + if err != nil { + return err + } + s.servers = append(s.servers, srv) + go func(proto, addr string) { logrus.Infof("Listening for HTTP on %s (%s)", proto, addr) - srv, err := s.newServer(proto, addr) - if err != nil { - chErrors <- err - return - } - s.eng.OnShutdown(func() { - if err := srv.Close(); err != nil { - logrus.Error(err) - } - }) - if err = srv.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") { + if err := srv.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") { err = nil } chErrors <- err @@ -133,7 +132,7 @@ func (s *HttpServer) Close() error { return s.l.Close() } -type HttpApiFunc func(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error +type HttpApiFunc func(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { conn, _, err := w.(http.Hijacker).Hijack() @@ -230,16 +229,7 @@ func writeJSON(w http.ResponseWriter, code int, v interface{}) error { return json.NewEncoder(w).Encode(v) } -func streamJSON(out *engine.Output, w http.ResponseWriter, flush bool) { - w.Header().Set("Content-Type", "application/json") - if flush { - out.Add(utils.NewWriteFlusher(w)) - } else { - out.Add(w) - } -} - -func (s *Server) postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postAuth(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { var config *cliconfig.AuthConfig err := json.NewDecoder(r.Body).Decode(&config) r.Body.Close() @@ -255,7 +245,7 @@ func (s *Server) postAuth(eng *engine.Engine, version version.Version, w http.Re }) } -func (s *Server) getVersion(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getVersion(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") v := &types.Version{ @@ -273,7 +263,7 @@ func (s *Server) getVersion(eng *engine.Engine, version version.Version, w http. return writeJSON(w, http.StatusOK, v) } -func (s *Server) postContainersKill(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersKill(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -308,7 +298,7 @@ func (s *Server) postContainersKill(eng *engine.Engine, version version.Version, return nil } -func (s *Server) postContainersPause(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersPause(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -332,7 +322,7 @@ func (s *Server) postContainersPause(eng *engine.Engine, version version.Version return nil } -func (s *Server) postContainersUnpause(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersUnpause(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -356,7 +346,7 @@ func (s *Server) postContainersUnpause(eng *engine.Engine, version version.Versi return nil } -func (s *Server) getContainersExport(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersExport(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -364,7 +354,7 @@ func (s *Server) getContainersExport(eng *engine.Engine, version version.Version return s.daemon.ContainerExport(vars["name"], w) } -func (s *Server) getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesJSON(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -405,7 +395,7 @@ func (s *Server) getImagesJSON(eng *engine.Engine, version version.Version, w ht return writeJSON(w, http.StatusOK, legacyImages) } -func (s *Server) getInfo(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getInfo(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") info, err := s.daemon.SystemInfo() @@ -416,7 +406,7 @@ func (s *Server) getInfo(eng *engine.Engine, version version.Version, w http.Res return writeJSON(w, http.StatusOK, info) } -func (s *Server) getEvents(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getEvents(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -520,7 +510,7 @@ func (s *Server) getEvents(eng *engine.Engine, version version.Version, w http.R } } -func (s *Server) getImagesHistory(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesHistory(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -534,7 +524,7 @@ func (s *Server) getImagesHistory(eng *engine.Engine, version version.Version, w return writeJSON(w, http.StatusOK, history) } -func (s *Server) getContainersChanges(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersChanges(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -553,7 +543,7 @@ func (s *Server) getContainersChanges(eng *engine.Engine, version version.Versio return writeJSON(w, http.StatusOK, changes) } -func (s *Server) getContainersTop(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersTop(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if version.LessThan("1.4") { return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.") } @@ -574,7 +564,7 @@ func (s *Server) getContainersTop(eng *engine.Engine, version version.Version, w return writeJSON(w, http.StatusOK, procList) } -func (s *Server) getContainersJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersJSON(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -603,7 +593,7 @@ func (s *Server) getContainersJSON(eng *engine.Engine, version version.Version, return writeJSON(w, http.StatusOK, containers) } -func (s *Server) getContainersStats(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersStats(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -614,7 +604,7 @@ func (s *Server) getContainersStats(eng *engine.Engine, version version.Version, return s.daemon.ContainerStats(vars["name"], utils.NewWriteFlusher(w)) } -func (s *Server) getContainersLogs(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersLogs(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -644,7 +634,7 @@ func (s *Server) getContainersLogs(eng *engine.Engine, version version.Version, return nil } -func (s *Server) postImagesTag(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postImagesTag(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -662,7 +652,7 @@ func (s *Server) postImagesTag(eng *engine.Engine, version version.Version, w ht return nil } -func (s *Server) postCommit(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postCommit(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -708,7 +698,7 @@ func (s *Server) postCommit(eng *engine.Engine, version version.Version, w http. } // Creates an image from Pull or from Import -func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postImagesCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -788,7 +778,7 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w return nil } -func (s *Server) getImagesSearch(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesSearch(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -818,7 +808,7 @@ func (s *Server) getImagesSearch(eng *engine.Engine, version version.Version, w return json.NewEncoder(w).Encode(query.Results) } -func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postImagesPush(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -875,7 +865,7 @@ func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w h } -func (s *Server) getImagesGet(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesGet(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -907,11 +897,11 @@ func (s *Server) getImagesGet(eng *engine.Engine, version version.Version, w htt } -func (s *Server) postImagesLoad(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postImagesLoad(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { return s.daemon.Repositories().Load(r.Body, w) } -func (s *Server) postContainersCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return nil } @@ -939,7 +929,7 @@ func (s *Server) postContainersCreate(eng *engine.Engine, version version.Versio }) } -func (s *Server) postContainersRestart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersRestart(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -961,7 +951,7 @@ func (s *Server) postContainersRestart(eng *engine.Engine, version version.Versi return nil } -func (s *Server) postContainerRename(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainerRename(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -978,7 +968,7 @@ func (s *Server) postContainerRename(eng *engine.Engine, version version.Version return nil } -func (s *Server) deleteContainers(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) deleteContainers(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -1006,7 +996,7 @@ func (s *Server) deleteContainers(eng *engine.Engine, version version.Version, w return nil } -func (s *Server) deleteImages(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) deleteImages(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -1026,7 +1016,7 @@ func (s *Server) deleteImages(eng *engine.Engine, version version.Version, w htt return writeJSON(w, http.StatusOK, list) } -func (s *Server) postContainersStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersStart(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -1062,7 +1052,7 @@ func (s *Server) postContainersStart(eng *engine.Engine, version version.Version return nil } -func (s *Server) postContainersStop(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersStop(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -1087,7 +1077,7 @@ func (s *Server) postContainersStop(eng *engine.Engine, version version.Version, return nil } -func (s *Server) postContainersWait(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersWait(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -1105,7 +1095,7 @@ func (s *Server) postContainersWait(eng *engine.Engine, version version.Version, }) } -func (s *Server) postContainersResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersResize(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -1130,7 +1120,7 @@ func (s *Server) postContainersResize(eng *engine.Engine, version version.Versio return cont.Resize(height, width) } -func (s *Server) postContainersAttach(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersAttach(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -1185,7 +1175,7 @@ func (s *Server) postContainersAttach(eng *engine.Engine, version version.Versio return nil } -func (s *Server) wsContainersAttach(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) wsContainersAttach(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -1211,7 +1201,7 @@ func (s *Server) wsContainersAttach(eng *engine.Engine, version version.Version, return nil } -func (s *Server) getContainersByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersByName(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -1232,7 +1222,7 @@ func (s *Server) getContainersByName(eng *engine.Engine, version version.Version return writeJSON(w, http.StatusOK, containerJSON) } -func (s *Server) getExecByID(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getExecByID(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter 'id'") } @@ -1245,7 +1235,7 @@ func (s *Server) getExecByID(eng *engine.Engine, version version.Version, w http return writeJSON(w, http.StatusOK, eConfig) } -func (s *Server) getImagesByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesByName(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -1268,7 +1258,7 @@ func (s *Server) getImagesByName(eng *engine.Engine, version version.Version, w return writeJSON(w, http.StatusOK, imageInspect) } -func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if version.LessThan("1.3") { return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.") } @@ -1363,7 +1353,7 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R return nil } -func (s *Server) postContainersCopy(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersCopy(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -1413,7 +1403,7 @@ func (s *Server) postContainersCopy(eng *engine.Engine, version version.Version, return nil } -func (s *Server) postContainerExecCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainerExecCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return nil } @@ -1442,7 +1432,7 @@ func (s *Server) postContainerExecCreate(eng *engine.Engine, version version.Ver } // TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start. -func (s *Server) postContainerExecStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainerExecStart(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return nil } @@ -1495,7 +1485,7 @@ func (s *Server) postContainerExecStart(eng *engine.Engine, version version.Vers return nil } -func (s *Server) postContainerExecResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainerExecResize(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -1515,7 +1505,7 @@ func (s *Server) postContainerExecResize(eng *engine.Engine, version version.Ver return s.daemon.ContainerExecResize(vars["name"], height, width) } -func (s *Server) optionsHandler(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) optionsHandler(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.WriteHeader(http.StatusOK) return nil } @@ -1526,12 +1516,12 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS") } -func (s *Server) ping(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) ping(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { _, err := w.Write([]byte{'O', 'K'}) return err } -func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, corsHeaders string, dockerVersion version.Version) http.HandlerFunc { +func makeHttpHandler(logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, corsHeaders string, dockerVersion version.Version) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // log the request logrus.Debugf("Calling %s %s", localMethod, localRoute) @@ -1559,7 +1549,7 @@ func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, local return } - if err := handlerFunc(eng, version, w, r, mux.Vars(r)); err != nil { + if err := handlerFunc(version, w, r, mux.Vars(r)); err != nil { logrus.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, err) httpError(w, err) } @@ -1567,7 +1557,7 @@ func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, local } // we keep enableCors just for legacy usage, need to be removed in the future -func createRouter(s *Server, eng *engine.Engine) *mux.Router { +func createRouter(s *Server) *mux.Router { r := mux.NewRouter() if os.Getenv("DEBUG") != "" { ProfilerSetup(r, "/debug/") @@ -1644,7 +1634,7 @@ func createRouter(s *Server, eng *engine.Engine) *mux.Router { localMethod := method // build the handler function - f := makeHttpHandler(eng, s.cfg.Logging, localMethod, localRoute, localFct, corsHeaders, version.Version(s.cfg.Version)) + f := makeHttpHandler(s.cfg.Logging, localMethod, localRoute, localFct, corsHeaders, version.Version(s.cfg.Version)) // add the new route if localRoute == "" { @@ -1659,23 +1649,6 @@ func createRouter(s *Server, eng *engine.Engine) *mux.Router { return r } -// ServeRequest processes a single http request to the docker remote api. -// FIXME: refactor this to be part of Server and not require re-creating a new -// router each time. This requires first moving ListenAndServe into Server. -func ServeRequest(eng *engine.Engine, apiversion version.Version, w http.ResponseWriter, req *http.Request) { - cfg := &ServerConfig{ - EnableCors: true, - Version: string(apiversion), - } - api := New(cfg, eng) - daemon, _ := eng.HackGetGlobalVar("httpapi.daemon").(*daemon.Daemon) - api.AcceptConnections(daemon) - router := createRouter(api, eng) - // Insert APIVERSION into the request as a convenience - req.URL.Path = fmt.Sprintf("/v%s%s", apiversion, req.URL.Path) - router.ServeHTTP(w, req) -} - func allocateDaemonPort(addr string) error { host, port, err := net.SplitHostPort(addr) if err != nil { diff --git a/daemon/container.go b/daemon/container.go index 9bd8cc1eb9..ef42295344 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -26,7 +26,6 @@ import ( "github.com/docker/docker/daemon/logger/syslog" "github.com/docker/docker/daemon/network" "github.com/docker/docker/daemon/networkdriver/bridge" - "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/links" "github.com/docker/docker/nat" @@ -600,10 +599,7 @@ func (container *Container) AllocateNetwork() error { return nil } - var ( - err error - eng = container.daemon.eng - ) + var err error networkSettings, err := bridge.Allocate(container.ID, container.Config.MacAddress, "", "") if err != nil { @@ -650,7 +646,7 @@ func (container *Container) AllocateNetwork() error { container.NetworkSettings.PortMapping = nil for port := range portSpecs { - if err = container.allocatePort(eng, port, bindings); err != nil { + if err = container.allocatePort(port, bindings); err != nil { bridge.Release(container.ID) return err } @@ -686,8 +682,6 @@ func (container *Container) RestoreNetwork() error { return nil } - eng := container.daemon.eng - // Re-allocate the interface with the same IP and MAC address. if _, err := bridge.Allocate(container.ID, container.NetworkSettings.MacAddress, container.NetworkSettings.IPAddress, ""); err != nil { return err @@ -695,7 +689,7 @@ func (container *Container) RestoreNetwork() error { // Re-allocate any previously allocated ports. for port := range container.NetworkSettings.Ports { - if err := container.allocatePort(eng, port, container.NetworkSettings.Ports); err != nil { + if err := container.allocatePort(port, container.NetworkSettings.Ports); err != nil { return err } } @@ -1483,7 +1477,7 @@ func (container *Container) waitForStart() error { return nil } -func (container *Container) allocatePort(eng *engine.Engine, port nat.Port, bindings nat.PortMap) error { +func (container *Container) allocatePort(port nat.Port, bindings nat.PortMap) error { binding := bindings[port] if container.hostConfig.PublishAllPorts && len(binding) == 0 { binding = append(binding, nat.PortBinding{}) diff --git a/daemon/daemon.go b/daemon/daemon.go index 05de402174..130fcc46f2 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -27,7 +27,6 @@ import ( _ "github.com/docker/docker/daemon/graphdriver/vfs" "github.com/docker/docker/daemon/network" "github.com/docker/docker/daemon/networkdriver/bridge" - "github.com/docker/docker/engine" "github.com/docker/docker/graph" "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" @@ -38,7 +37,6 @@ import ( "github.com/docker/docker/pkg/namesgenerator" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/parsers/kernel" - "github.com/docker/docker/pkg/pidfile" "github.com/docker/docker/pkg/resolvconf" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/sysinfo" @@ -103,7 +101,6 @@ type Daemon struct { idIndex *truncindex.TruncIndex sysInfo *sysinfo.SysInfo volumes *volumes.Repository - eng *engine.Engine config *Config containerGraph *graphdb.Database driver graphdriver.Driver @@ -114,14 +111,6 @@ type Daemon struct { EventsService *events.Events } -// Install installs daemon capabilities to eng. -func (daemon *Daemon) Install(eng *engine.Engine) error { - // FIXME: this hack is necessary for legacy integration tests to access - // the daemon object. - eng.HackSetGlobalVar("httpapi.daemon", daemon) - return nil -} - // Get looks for a container using the provided information, which could be // one of the following inputs from the caller: // - A full container ID, which will exact match a container in daemon's list @@ -741,16 +730,7 @@ func (daemon *Daemon) RegisterLinks(container *Container, hostConfig *runconfig. return nil } -// FIXME: harmonize with NewGraph() -func NewDaemon(config *Config, eng *engine.Engine, registryService *registry.Service) (*Daemon, error) { - daemon, err := NewDaemonFromDirectory(config, eng, registryService) - if err != nil { - return nil, err - } - return daemon, nil -} - -func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService *registry.Service) (*Daemon, error) { +func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemon, err error) { if config.Mtu == 0 { config.Mtu = getDefaultNetworkMtu() } @@ -766,19 +746,6 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService } config.DisableNetwork = config.Bridge.Iface == disableNetworkBridge - // Claim the pidfile first, to avoid any and all unexpected race conditions. - // Some of the init doesn't need a pidfile lock - but let's not try to be smart. - if config.Pidfile != "" { - file, err := pidfile.New(config.Pidfile) - if err != nil { - return nil, err - } - eng.OnShutdown(func() { - // Always release the pidfile last, just in case - file.Remove() - }) - } - // Check that the system is supported and we have sufficient privileges if runtime.GOOS != "linux" { return nil, fmt.Errorf("The Docker daemon is only supported on linux") @@ -826,17 +793,22 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService return nil, fmt.Errorf("error initializing graphdriver: %v", err) } logrus.Debugf("Using graph driver %s", driver) - // register cleanup for graph driver - eng.OnShutdown(func() { - if err := driver.Cleanup(); err != nil { - logrus.Errorf("Error during graph storage driver.Cleanup(): %v", err) + + d := &Daemon{} + d.driver = driver + + defer func() { + if err != nil { + if err := d.Shutdown(); err != nil { + logrus.Error(err) + } } - }) + }() if config.EnableSelinuxSupport { if selinuxEnabled() { // As Docker on btrfs and SELinux are incompatible at present, error on both being enabled - if driver.String() == "btrfs" { + if d.driver.String() == "btrfs" { return nil, fmt.Errorf("SELinux is not supported with the BTRFS graph driver") } logrus.Debug("SELinux enabled successfully") @@ -854,12 +826,12 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService } // Migrate the container if it is aufs and aufs is enabled - if err = migrateIfAufs(driver, config.Root); err != nil { + if err := migrateIfAufs(d.driver, config.Root); err != nil { return nil, err } logrus.Debug("Creating images graph") - g, err := graph.NewGraph(path.Join(config.Root, "graph"), driver) + g, err := graph.NewGraph(path.Join(config.Root, "graph"), d.driver) if err != nil { return nil, err } @@ -897,7 +869,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService Events: eventsService, Trust: trustService, } - repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), tagCfg) + repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+d.driver.String()), tagCfg) if err != nil { return nil, fmt.Errorf("Couldn't create Tag store: %s", err) } @@ -913,12 +885,8 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService if err != nil { return nil, err } - // register graph close on shutdown - eng.OnShutdown(func() { - if err := graph.Close(); err != nil { - logrus.Errorf("Error during container graph.Close(): %v", err) - } - }) + + d.containerGraph = graph localCopy := path.Join(config.Root, "init", fmt.Sprintf("dockerinit-%s", dockerversion.VERSION)) sysInitPath := utils.DockerInitPath(localCopy) @@ -947,66 +915,67 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService return nil, err } - daemon := &Daemon{ - ID: trustKey.PublicKey().KeyID(), - repository: daemonRepo, - containers: &contStore{s: make(map[string]*Container)}, - execCommands: newExecStore(), - graph: g, - repositories: repositories, - idIndex: truncindex.NewTruncIndex([]string{}), - sysInfo: sysInfo, - volumes: volumes, - config: config, - containerGraph: graph, - driver: driver, - sysInitPath: sysInitPath, - execDriver: ed, - eng: eng, - statsCollector: newStatsCollector(1 * time.Second), - defaultLogConfig: config.LogConfig, - RegistryService: registryService, - EventsService: eventsService, - } + d.ID = trustKey.PublicKey().KeyID() + d.repository = daemonRepo + d.containers = &contStore{s: make(map[string]*Container)} + d.execCommands = newExecStore() + d.graph = g + d.repositories = repositories + d.idIndex = truncindex.NewTruncIndex([]string{}) + d.sysInfo = sysInfo + d.volumes = volumes + d.config = config + d.sysInitPath = sysInitPath + d.execDriver = ed + d.statsCollector = newStatsCollector(1 * time.Second) + d.defaultLogConfig = config.LogConfig + d.RegistryService = registryService + d.EventsService = eventsService - eng.OnShutdown(func() { - if err := daemon.shutdown(); err != nil { - logrus.Errorf("Error during daemon.shutdown(): %v", err) - } - }) - - if err := daemon.restore(); err != nil { + if err := d.restore(); err != nil { return nil, err } // set up filesystem watch on resolv.conf for network changes - if err := daemon.setupResolvconfWatcher(); err != nil { + if err := d.setupResolvconfWatcher(); err != nil { return nil, err } - return daemon, nil + return d, nil } -func (daemon *Daemon) shutdown() error { - group := sync.WaitGroup{} - logrus.Debug("starting clean shutdown of all containers...") - for _, container := range daemon.List() { - c := container - if c.IsRunning() { - logrus.Debugf("stopping %s", c.ID) - group.Add(1) - - go func() { - defer group.Done() - if err := c.KillSig(15); err != nil { - logrus.Debugf("kill 15 error for %s - %s", c.ID, err) - } - c.WaitStop(-1 * time.Second) - logrus.Debugf("container stopped %s", c.ID) - }() +func (daemon *Daemon) Shutdown() error { + if daemon.containerGraph != nil { + if err := daemon.containerGraph.Close(); err != nil { + logrus.Errorf("Error during container graph.Close(): %v", err) } } - group.Wait() + if daemon.driver != nil { + if err := daemon.driver.Cleanup(); err != nil { + logrus.Errorf("Error during graph storage driver.Cleanup(): %v", err) + } + } + if daemon.containers != nil { + group := sync.WaitGroup{} + logrus.Debug("starting clean shutdown of all containers...") + for _, container := range daemon.List() { + c := container + if c.IsRunning() { + logrus.Debugf("stopping %s", c.ID) + group.Add(1) + + go func() { + defer group.Done() + if err := c.KillSig(15); err != nil { + logrus.Debugf("kill 15 error for %s - %s", c.ID, err) + } + c.WaitStop(-1 * time.Second) + logrus.Debugf("container stopped %s", c.ID) + }() + } + } + group.Wait() + } return nil } @@ -1087,26 +1056,6 @@ func (daemon *Daemon) UnsubscribeToContainerStats(name string, ch chan interface return nil } -// Nuke kills all containers then removes all content -// from the content root, including images, volumes and -// container filesystems. -// Again: this will remove your entire docker daemon! -// FIXME: this is deprecated, and only used in legacy -// tests. Please remove. -func (daemon *Daemon) Nuke() error { - var wg sync.WaitGroup - for _, container := range daemon.List() { - wg.Add(1) - go func(c *Container) { - c.Kill() - wg.Done() - }(container) - } - wg.Wait() - - return os.RemoveAll(daemon.config.Root) -} - // FIXME: this is a convenience function for integration tests // which need direct access to daemon.graph. // Once the tests switch to using engine and jobs, this method diff --git a/docker/daemon.go b/docker/daemon.go index c6241b6060..c78879784a 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -7,6 +7,7 @@ import ( "io" "os" "path/filepath" + "time" "github.com/Sirupsen/logrus" apiserver "github.com/docker/docker/api/server" @@ -14,9 +15,9 @@ import ( "github.com/docker/docker/daemon" _ "github.com/docker/docker/daemon/execdriver/lxc" _ "github.com/docker/docker/daemon/execdriver/native" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/homedir" flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/pidfile" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/timeutils" @@ -83,14 +84,45 @@ func mainDaemon() { logrus.SetFormatter(&logrus.TextFormatter{TimestampFormat: timeutils.RFC3339NanoFixed}) - eng := engine.New() - signal.Trap(eng.Shutdown) + var pfile *pidfile.PidFile + if daemonCfg.Pidfile != "" { + pf, err := pidfile.New(daemonCfg.Pidfile) + if err != nil { + logrus.Fatalf("Error starting daemon: %v", err) + } + pfile = pf + defer func() { + if err := pfile.Remove(); err != nil { + logrus.Error(err) + } + }() + } if err := migrateKey(); err != nil { logrus.Fatal(err) } daemonCfg.TrustKeyPath = *flTrustKey + registryService := registry.NewService(registryCfg) + d, err := daemon.NewDaemon(daemonCfg, registryService) + if err != nil { + if pfile != nil { + if err := pfile.Remove(); err != nil { + logrus.Error(err) + } + } + logrus.Fatalf("Error starting daemon: %v", err) + } + + logrus.Info("Daemon has completed initialization") + + logrus.WithFields(logrus.Fields{ + "version": dockerversion.VERSION, + "commit": dockerversion.GITCOMMIT, + "execdriver": d.ExecutionDriver().Name(), + "graphdriver": d.GraphDriver().String(), + }).Info("Docker daemon") + serverConfig := &apiserver.ServerConfig{ Logging: true, EnableCors: daemonCfg.EnableCors, @@ -104,7 +136,7 @@ func mainDaemon() { TlsKey: *flKey, } - api := apiserver.New(serverConfig, eng) + api := apiserver.New(serverConfig) // The serve API routine never exits unless an error occurs // We need to start it as a goroutine and wait on it so @@ -119,40 +151,52 @@ func mainDaemon() { serveAPIWait <- nil }() - registryService := registry.NewService(registryCfg) - d, err := daemon.NewDaemon(daemonCfg, eng, registryService) - if err != nil { - eng.Shutdown() - logrus.Fatalf("Error starting daemon: %v", err) - } - - if err := d.Install(eng); err != nil { - eng.Shutdown() - logrus.Fatalf("Error starting daemon: %v", err) - } - - logrus.Info("Daemon has completed initialization") - - logrus.WithFields(logrus.Fields{ - "version": dockerversion.VERSION, - "commit": dockerversion.GITCOMMIT, - "execdriver": d.ExecutionDriver().Name(), - "graphdriver": d.GraphDriver().String(), - }).Info("Docker daemon") + signal.Trap(func() { + api.Close() + <-serveAPIWait + shutdownDaemon(d, 15) + if pfile != nil { + if err := pfile.Remove(); err != nil { + logrus.Error(err) + } + } + }) // after the daemon is done setting up we can tell the api to start // accepting connections with specified daemon api.AcceptConnections(d) // Daemon is fully initialized and handling API traffic - // Wait for serve API job to complete + // Wait for serve API to complete errAPI := <-serveAPIWait - eng.Shutdown() + shutdownDaemon(d, 15) if errAPI != nil { + if pfile != nil { + if err := pfile.Remove(); err != nil { + logrus.Error(err) + } + } logrus.Fatalf("Shutting down due to ServeAPI error: %v", errAPI) } } +// shutdownDaemon just wraps daemon.Shutdown() to handle a timeout in case +// d.Shutdown() is waiting too long to kill container or worst it's +// blocked there +func shutdownDaemon(d *daemon.Daemon, timeout time.Duration) { + ch := make(chan struct{}) + go func() { + d.Shutdown() + close(ch) + }() + select { + case <-ch: + logrus.Debug("Clean shutdown succeded") + case <-time.After(timeout * time.Second): + logrus.Error("Force shutdown daemon") + } +} + // currentUserIsOwner checks whether the current user is the owner of the given // file. func currentUserIsOwner(f string) bool { diff --git a/engine/engine.go b/engine/engine.go deleted file mode 100644 index 79fae51cc3..0000000000 --- a/engine/engine.go +++ /dev/null @@ -1,255 +0,0 @@ -package engine - -import ( - "bufio" - "fmt" - "io" - "os" - "sort" - "strings" - "sync" - "time" - - "github.com/docker/docker/pkg/ioutils" - "github.com/docker/docker/pkg/stringid" -) - -// Installer is a standard interface for objects which can "install" themselves -// on an engine by registering handlers. -// This can be used as an entrypoint for external plugins etc. -type Installer interface { - Install(*Engine) error -} - -type Handler func(*Job) error - -var globalHandlers map[string]Handler - -func init() { - globalHandlers = make(map[string]Handler) -} - -func Register(name string, handler Handler) error { - _, exists := globalHandlers[name] - if exists { - return fmt.Errorf("Can't overwrite global handler for command %s", name) - } - globalHandlers[name] = handler - return nil -} - -func unregister(name string) { - delete(globalHandlers, name) -} - -// The Engine is the core of Docker. -// It acts as a store for *containers*, and allows manipulation of these -// containers by executing *jobs*. -type Engine struct { - handlers map[string]Handler - catchall Handler - hack Hack // data for temporary hackery (see hack.go) - id string - Stdout io.Writer - Stderr io.Writer - Stdin io.Reader - Logging bool - tasks sync.WaitGroup - l sync.RWMutex // lock for shutdown - shutdownWait sync.WaitGroup - shutdown bool - onShutdown []func() // shutdown handlers -} - -func (eng *Engine) Register(name string, handler Handler) error { - _, exists := eng.handlers[name] - if exists { - return fmt.Errorf("Can't overwrite handler for command %s", name) - } - eng.handlers[name] = handler - return nil -} - -func (eng *Engine) RegisterCatchall(catchall Handler) { - eng.catchall = catchall -} - -// New initializes a new engine. -func New() *Engine { - eng := &Engine{ - handlers: make(map[string]Handler), - id: stringid.GenerateRandomID(), - Stdout: os.Stdout, - Stderr: os.Stderr, - Stdin: os.Stdin, - Logging: true, - } - eng.Register("commands", func(job *Job) error { - for _, name := range eng.commands() { - job.Printf("%s\n", name) - } - return nil - }) - // Copy existing global handlers - for k, v := range globalHandlers { - eng.handlers[k] = v - } - return eng -} - -func (eng *Engine) String() string { - return fmt.Sprintf("%s", eng.id[:8]) -} - -// Commands returns a list of all currently registered commands, -// sorted alphabetically. -func (eng *Engine) commands() []string { - names := make([]string, 0, len(eng.handlers)) - for name := range eng.handlers { - names = append(names, name) - } - sort.Strings(names) - return names -} - -// Job creates a new job which can later be executed. -// This function mimics `Command` from the standard os/exec package. -func (eng *Engine) Job(name string, args ...string) *Job { - job := &Job{ - Eng: eng, - Name: name, - Args: args, - Stdin: NewInput(), - Stdout: NewOutput(), - Stderr: NewOutput(), - env: &Env{}, - closeIO: true, - - cancelled: make(chan struct{}), - } - if eng.Logging { - job.Stderr.Add(ioutils.NopWriteCloser(eng.Stderr)) - } - - // Catchall is shadowed by specific Register. - if handler, exists := eng.handlers[name]; exists { - job.handler = handler - } else if eng.catchall != nil && name != "" { - // empty job names are illegal, catchall or not. - job.handler = eng.catchall - } - return job -} - -// OnShutdown registers a new callback to be called by Shutdown. -// This is typically used by services to perform cleanup. -func (eng *Engine) OnShutdown(h func()) { - eng.l.Lock() - eng.onShutdown = append(eng.onShutdown, h) - eng.shutdownWait.Add(1) - eng.l.Unlock() -} - -// Shutdown permanently shuts down eng as follows: -// - It refuses all new jobs, permanently. -// - It waits for all active jobs to complete (with no timeout) -// - It calls all shutdown handlers concurrently (if any) -// - It returns when all handlers complete, or after 15 seconds, -// whichever happens first. -func (eng *Engine) Shutdown() { - eng.l.Lock() - if eng.shutdown { - eng.l.Unlock() - eng.shutdownWait.Wait() - return - } - eng.shutdown = true - eng.l.Unlock() - // We don't need to protect the rest with a lock, to allow - // for other calls to immediately fail with "shutdown" instead - // of hanging for 15 seconds. - // This requires all concurrent calls to check for shutdown, otherwise - // it might cause a race. - - // Wait for all jobs to complete. - // Timeout after 5 seconds. - tasksDone := make(chan struct{}) - go func() { - eng.tasks.Wait() - close(tasksDone) - }() - select { - case <-time.After(time.Second * 5): - case <-tasksDone: - } - - // Call shutdown handlers, if any. - // Timeout after 10 seconds. - for _, h := range eng.onShutdown { - go func(h func()) { - h() - eng.shutdownWait.Done() - }(h) - } - done := make(chan struct{}) - go func() { - eng.shutdownWait.Wait() - close(done) - }() - select { - case <-time.After(time.Second * 10): - case <-done: - } - return -} - -// IsShutdown returns true if the engine is in the process -// of shutting down, or already shut down. -// Otherwise it returns false. -func (eng *Engine) IsShutdown() bool { - eng.l.RLock() - defer eng.l.RUnlock() - return eng.shutdown -} - -// ParseJob creates a new job from a text description using a shell-like syntax. -// -// The following syntax is used to parse `input`: -// -// * Words are separated using standard whitespaces as separators. -// * Quotes and backslashes are not interpreted. -// * Words of the form 'KEY=[VALUE]' are added to the job environment. -// * All other words are added to the job arguments. -// -// For example: -// -// job, _ := eng.ParseJob("VERBOSE=1 echo hello TEST=true world") -// -// The resulting job will have: -// job.Args={"echo", "hello", "world"} -// job.Env={"VERBOSE":"1", "TEST":"true"} -// -func (eng *Engine) ParseJob(input string) (*Job, error) { - // FIXME: use a full-featured command parser - scanner := bufio.NewScanner(strings.NewReader(input)) - scanner.Split(bufio.ScanWords) - var ( - cmd []string - env Env - ) - for scanner.Scan() { - word := scanner.Text() - kv := strings.SplitN(word, "=", 2) - if len(kv) == 2 { - env.Set(kv[0], kv[1]) - } else { - cmd = append(cmd, word) - } - } - if len(cmd) == 0 { - return nil, fmt.Errorf("empty command: '%s'", input) - } - job := eng.Job(cmd[0], cmd[1:]...) - job.Env().Init(&env) - return job, nil -} diff --git a/engine/engine_test.go b/engine/engine_test.go deleted file mode 100644 index a6ff62c8be..0000000000 --- a/engine/engine_test.go +++ /dev/null @@ -1,236 +0,0 @@ -package engine - -import ( - "bytes" - "strings" - "testing" - - "github.com/docker/docker/pkg/ioutils" -) - -func TestRegister(t *testing.T) { - if err := Register("dummy1", nil); err != nil { - t.Fatal(err) - } - - if err := Register("dummy1", nil); err == nil { - t.Fatalf("Expecting error, got none") - } - // Register is global so let's cleanup to avoid conflicts - defer unregister("dummy1") - - eng := New() - - //Should fail because global handlers are copied - //at the engine creation - if err := eng.Register("dummy1", nil); err == nil { - t.Fatalf("Expecting error, got none") - } - - if err := eng.Register("dummy2", nil); err != nil { - t.Fatal(err) - } - - if err := eng.Register("dummy2", nil); err == nil { - t.Fatalf("Expecting error, got none") - } - defer unregister("dummy2") -} - -func TestJob(t *testing.T) { - eng := New() - job1 := eng.Job("dummy1", "--level=awesome") - - if job1.handler != nil { - t.Fatalf("job1.handler should be empty") - } - - h := func(j *Job) error { - j.Printf("%s\n", j.Name) - return nil - } - - eng.Register("dummy2", h) - defer unregister("dummy2") - job2 := eng.Job("dummy2", "--level=awesome") - - if job2.handler == nil { - t.Fatalf("job2.handler shouldn't be nil") - } - - if job2.handler(job2) != nil { - t.Fatalf("handler dummy2 was not found in job2") - } -} - -func TestEngineShutdown(t *testing.T) { - eng := New() - if eng.IsShutdown() { - t.Fatalf("Engine should not show as shutdown") - } - eng.Shutdown() - if !eng.IsShutdown() { - t.Fatalf("Engine should show as shutdown") - } -} - -func TestEngineCommands(t *testing.T) { - eng := New() - handler := func(job *Job) error { return nil } - eng.Register("foo", handler) - eng.Register("bar", handler) - eng.Register("echo", handler) - eng.Register("die", handler) - var output bytes.Buffer - commands := eng.Job("commands") - commands.Stdout.Add(&output) - commands.Run() - expected := "bar\ncommands\ndie\necho\nfoo\n" - if result := output.String(); result != expected { - t.Fatalf("Unexpected output:\nExpected = %v\nResult = %v\n", expected, result) - } -} - -func TestEngineString(t *testing.T) { - eng1 := New() - eng2 := New() - s1 := eng1.String() - s2 := eng2.String() - if eng1 == eng2 { - t.Fatalf("Different engines should have different names (%v == %v)", s1, s2) - } -} - -func TestParseJob(t *testing.T) { - eng := New() - // Verify that the resulting job calls to the right place - var called bool - eng.Register("echo", func(job *Job) error { - called = true - return nil - }) - input := "echo DEBUG=1 hello world VERBOSITY=42" - job, err := eng.ParseJob(input) - if err != nil { - t.Fatal(err) - } - if job.Name != "echo" { - t.Fatalf("Invalid job name: %v", job.Name) - } - if strings.Join(job.Args, ":::") != "hello:::world" { - t.Fatalf("Invalid job args: %v", job.Args) - } - if job.Env().Get("DEBUG") != "1" { - t.Fatalf("Invalid job env: %v", job.Env) - } - if job.Env().Get("VERBOSITY") != "42" { - t.Fatalf("Invalid job env: %v", job.Env) - } - if len(job.Env().Map()) != 2 { - t.Fatalf("Invalid job env: %v", job.Env) - } - if err := job.Run(); err != nil { - t.Fatal(err) - } - if !called { - t.Fatalf("Job was not called") - } -} - -func TestCatchallEmptyName(t *testing.T) { - eng := New() - var called bool - eng.RegisterCatchall(func(job *Job) error { - called = true - return nil - }) - err := eng.Job("").Run() - if err == nil { - t.Fatalf("Engine.Job(\"\").Run() should return an error") - } - if called { - t.Fatalf("Engine.Job(\"\").Run() should return an error") - } -} - -// Ensure that a job within a job both using the same underlying standard -// output writer does not close the output of the outer job when the inner -// job's stdout is wrapped with a NopCloser. When not wrapped, it should -// close the outer job's output. -func TestNestedJobSharedOutput(t *testing.T) { - var ( - outerHandler Handler - innerHandler Handler - wrapOutput bool - ) - - outerHandler = func(job *Job) error { - job.Stdout.Write([]byte("outer1")) - - innerJob := job.Eng.Job("innerJob") - - if wrapOutput { - innerJob.Stdout.Add(ioutils.NopWriteCloser(job.Stdout)) - } else { - innerJob.Stdout.Add(job.Stdout) - } - - if err := innerJob.Run(); err != nil { - t.Fatal(err) - } - - // If wrapOutput was *false* this write will do nothing. - // FIXME (jlhawn): It should cause an error to write to - // closed output. - job.Stdout.Write([]byte(" outer2")) - - return nil - } - - innerHandler = func(job *Job) error { - job.Stdout.Write([]byte(" inner")) - - return nil - } - - eng := New() - eng.Register("outerJob", outerHandler) - eng.Register("innerJob", innerHandler) - - // wrapOutput starts *false* so the expected - // output of running the outer job will be: - // - // "outer1 inner" - // - outBuf := new(bytes.Buffer) - outerJob := eng.Job("outerJob") - outerJob.Stdout.Add(outBuf) - - if err := outerJob.Run(); err != nil { - t.Fatal(err) - } - - expectedOutput := "outer1 inner" - if outBuf.String() != expectedOutput { - t.Fatalf("expected job output to be %q, got %q", expectedOutput, outBuf.String()) - } - - // Set wrapOutput to true so that the expected - // output of running the outer job will be: - // - // "outer1 inner outer2" - // - wrapOutput = true - outBuf.Reset() - outerJob = eng.Job("outerJob") - outerJob.Stdout.Add(outBuf) - - if err := outerJob.Run(); err != nil { - t.Fatal(err) - } - - expectedOutput = "outer1 inner outer2" - if outBuf.String() != expectedOutput { - t.Fatalf("expected job output to be %q, got %q", expectedOutput, outBuf.String()) - } -} diff --git a/engine/env.go b/engine/env.go deleted file mode 100644 index 107ae4a0d9..0000000000 --- a/engine/env.go +++ /dev/null @@ -1,313 +0,0 @@ -package engine - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - "time" - - "github.com/docker/docker/pkg/ioutils" -) - -type Env []string - -// Get returns the last value associated with the given key. If there are no -// values associated with the key, Get returns the empty string. -func (env *Env) Get(key string) (value string) { - // not using Map() because of the extra allocations https://github.com/docker/docker/pull/7488#issuecomment-51638315 - for _, kv := range *env { - if strings.Index(kv, "=") == -1 { - continue - } - parts := strings.SplitN(kv, "=", 2) - if parts[0] != key { - continue - } - if len(parts) < 2 { - value = "" - } else { - value = parts[1] - } - } - return -} - -func (env *Env) Exists(key string) bool { - _, exists := env.Map()[key] - return exists -} - -// Len returns the number of keys in the environment. -// Note that len(env) might be different from env.Len(), -// because the same key might be set multiple times. -func (env *Env) Len() int { - return len(env.Map()) -} - -func (env *Env) Init(src *Env) { - (*env) = make([]string, 0, len(*src)) - for _, val := range *src { - (*env) = append((*env), val) - } -} - -func (env *Env) GetBool(key string) (value bool) { - s := strings.ToLower(strings.Trim(env.Get(key), " \t")) - if s == "" || s == "0" || s == "no" || s == "false" || s == "none" { - return false - } - return true -} - -func (env *Env) SetBool(key string, value bool) { - if value { - env.Set(key, "1") - } else { - env.Set(key, "0") - } -} - -func (env *Env) GetTime(key string) (time.Time, error) { - t, err := time.Parse(time.RFC3339Nano, env.Get(key)) - return t, err -} - -func (env *Env) SetTime(key string, t time.Time) { - env.Set(key, t.Format(time.RFC3339Nano)) -} - -func (env *Env) GetInt(key string) int { - return int(env.GetInt64(key)) -} - -func (env *Env) GetInt64(key string) int64 { - s := strings.Trim(env.Get(key), " \t") - val, err := strconv.ParseInt(s, 10, 64) - if err != nil { - return 0 - } - return val -} - -func (env *Env) SetInt(key string, value int) { - env.Set(key, fmt.Sprintf("%d", value)) -} - -func (env *Env) SetInt64(key string, value int64) { - env.Set(key, fmt.Sprintf("%d", value)) -} - -// Returns nil if key not found -func (env *Env) GetList(key string) []string { - sval := env.Get(key) - if sval == "" { - return nil - } - l := make([]string, 0, 1) - if err := json.Unmarshal([]byte(sval), &l); err != nil { - l = append(l, sval) - } - return l -} - -func (env *Env) GetSubEnv(key string) *Env { - sval := env.Get(key) - if sval == "" { - return nil - } - buf := bytes.NewBufferString(sval) - var sub Env - if err := sub.Decode(buf); err != nil { - return nil - } - return &sub -} - -func (env *Env) SetSubEnv(key string, sub *Env) error { - var buf bytes.Buffer - if err := sub.Encode(&buf); err != nil { - return err - } - env.Set(key, string(buf.Bytes())) - return nil -} - -func (env *Env) GetJson(key string, iface interface{}) error { - sval := env.Get(key) - if sval == "" { - return nil - } - return json.Unmarshal([]byte(sval), iface) -} - -func (env *Env) SetJson(key string, value interface{}) error { - sval, err := json.Marshal(value) - if err != nil { - return err - } - env.Set(key, string(sval)) - return nil -} - -func (env *Env) SetList(key string, value []string) error { - return env.SetJson(key, value) -} - -func (env *Env) Set(key, value string) { - *env = append(*env, key+"="+value) -} - -func NewDecoder(src io.Reader) *Decoder { - return &Decoder{ - json.NewDecoder(src), - } -} - -type Decoder struct { - *json.Decoder -} - -func (decoder *Decoder) Decode() (*Env, error) { - m := make(map[string]interface{}) - if err := decoder.Decoder.Decode(&m); err != nil { - return nil, err - } - env := &Env{} - for key, value := range m { - env.SetAuto(key, value) - } - return env, nil -} - -// DecodeEnv decodes `src` as a json dictionary, and adds -// each decoded key-value pair to the environment. -// -// If `src` cannot be decoded as a json dictionary, an error -// is returned. -func (env *Env) Decode(src io.Reader) error { - m := make(map[string]interface{}) - d := json.NewDecoder(src) - // We need this or we'll lose data when we decode int64 in json - d.UseNumber() - if err := d.Decode(&m); err != nil { - return err - } - for k, v := range m { - env.SetAuto(k, v) - } - return nil -} - -func (env *Env) SetAuto(k string, v interface{}) { - // Issue 7941 - if the value in the incoming JSON is null then treat it - // as if they never specified the property at all. - if v == nil { - return - } - - // FIXME: we fix-convert float values to int, because - // encoding/json decodes integers to float64, but cannot encode them back. - // (See https://golang.org/src/pkg/encoding/json/decode.go#L46) - if fval, ok := v.(float64); ok { - env.SetInt64(k, int64(fval)) - } else if sval, ok := v.(string); ok { - env.Set(k, sval) - } else if val, err := json.Marshal(v); err == nil { - env.Set(k, string(val)) - } else { - env.Set(k, fmt.Sprintf("%v", v)) - } -} - -func changeFloats(v interface{}) interface{} { - switch v := v.(type) { - case float64: - return int(v) - case map[string]interface{}: - for key, val := range v { - v[key] = changeFloats(val) - } - case []interface{}: - for idx, val := range v { - v[idx] = changeFloats(val) - } - } - return v -} - -func (env *Env) Encode(dst io.Writer) error { - m := make(map[string]interface{}) - for k, v := range env.Map() { - var val interface{} - if err := json.Unmarshal([]byte(v), &val); err == nil { - // FIXME: we fix-convert float values to int, because - // encoding/json decodes integers to float64, but cannot encode them back. - // (See https://golang.org/src/pkg/encoding/json/decode.go#L46) - m[k] = changeFloats(val) - } else { - m[k] = v - } - } - if err := json.NewEncoder(dst).Encode(&m); err != nil { - return err - } - return nil -} - -func (env *Env) WriteTo(dst io.Writer) (int64, error) { - wc := ioutils.NewWriteCounter(dst) - err := env.Encode(wc) - return wc.Count, err -} - -func (env *Env) Import(src interface{}) (err error) { - defer func() { - if err != nil { - err = fmt.Errorf("ImportEnv: %s", err) - } - }() - var buf bytes.Buffer - if err := json.NewEncoder(&buf).Encode(src); err != nil { - return err - } - if err := env.Decode(&buf); err != nil { - return err - } - return nil -} - -func (env *Env) Map() map[string]string { - m := make(map[string]string) - for _, kv := range *env { - parts := strings.SplitN(kv, "=", 2) - m[parts[0]] = parts[1] - } - return m -} - -// MultiMap returns a representation of env as a -// map of string arrays, keyed by string. -// This is the same structure as http headers for example, -// which allow each key to have multiple values. -func (env *Env) MultiMap() map[string][]string { - m := make(map[string][]string) - for _, kv := range *env { - parts := strings.SplitN(kv, "=", 2) - m[parts[0]] = append(m[parts[0]], parts[1]) - } - return m -} - -// InitMultiMap removes all values in env, then initializes -// new values from the contents of m. -func (env *Env) InitMultiMap(m map[string][]string) { - (*env) = make([]string, 0, len(m)) - for k, vals := range m { - for _, v := range vals { - env.Set(k, v) - } - } -} diff --git a/engine/env_test.go b/engine/env_test.go deleted file mode 100644 index 1398275b2b..0000000000 --- a/engine/env_test.go +++ /dev/null @@ -1,366 +0,0 @@ -package engine - -import ( - "bytes" - "encoding/json" - "testing" - "time" - - "github.com/docker/docker/pkg/stringutils" -) - -func TestEnvLenZero(t *testing.T) { - env := &Env{} - if env.Len() != 0 { - t.Fatalf("%d", env.Len()) - } -} - -func TestEnvLenNotZero(t *testing.T) { - env := &Env{} - env.Set("foo", "bar") - env.Set("ga", "bu") - if env.Len() != 2 { - t.Fatalf("%d", env.Len()) - } -} - -func TestEnvLenDup(t *testing.T) { - env := &Env{ - "foo=bar", - "foo=baz", - "a=b", - } - // len(env) != env.Len() - if env.Len() != 2 { - t.Fatalf("%d", env.Len()) - } -} - -func TestEnvGetDup(t *testing.T) { - env := &Env{ - "foo=bar", - "foo=baz", - "foo=bif", - } - expected := "bif" - if v := env.Get("foo"); v != expected { - t.Fatalf("expect %q, got %q", expected, v) - } -} - -func TestNewJob(t *testing.T) { - job := mkJob(t, "dummy", "--level=awesome") - if job.Name != "dummy" { - t.Fatalf("Wrong job name: %s", job.Name) - } - if len(job.Args) != 1 { - t.Fatalf("Wrong number of job arguments: %d", len(job.Args)) - } - if job.Args[0] != "--level=awesome" { - t.Fatalf("Wrong job arguments: %s", job.Args[0]) - } -} - -func TestSetenv(t *testing.T) { - job := mkJob(t, "dummy") - job.Setenv("foo", "bar") - if val := job.Getenv("foo"); val != "bar" { - t.Fatalf("Getenv returns incorrect value: %s", val) - } - - job.Setenv("bar", "") - if val := job.Getenv("bar"); val != "" { - t.Fatalf("Getenv returns incorrect value: %s", val) - } - if val := job.Getenv("nonexistent"); val != "" { - t.Fatalf("Getenv returns incorrect value: %s", val) - } -} - -func TestDecodeEnv(t *testing.T) { - job := mkJob(t, "dummy") - type tmp struct { - Id1 int64 - Id2 int64 - } - body := []byte("{\"tags\":{\"Id1\":123, \"Id2\":1234567}}") - if err := job.DecodeEnv(bytes.NewBuffer(body)); err != nil { - t.Fatalf("DecodeEnv failed: %v", err) - } - mytag := tmp{} - if val := job.GetenvJson("tags", &mytag); val != nil { - t.Fatalf("GetenvJson returns incorrect value: %s", val) - } - - if mytag.Id1 != 123 || mytag.Id2 != 1234567 { - t.Fatal("Get wrong values set by job.DecodeEnv") - } -} - -func TestSetenvBool(t *testing.T) { - job := mkJob(t, "dummy") - job.SetenvBool("foo", true) - if val := job.GetenvBool("foo"); !val { - t.Fatalf("GetenvBool returns incorrect value: %t", val) - } - - job.SetenvBool("bar", false) - if val := job.GetenvBool("bar"); val { - t.Fatalf("GetenvBool returns incorrect value: %t", val) - } - - if val := job.GetenvBool("nonexistent"); val { - t.Fatalf("GetenvBool returns incorrect value: %t", val) - } -} - -func TestSetenvTime(t *testing.T) { - job := mkJob(t, "dummy") - - now := time.Now() - job.SetenvTime("foo", now) - if val, err := job.GetenvTime("foo"); err != nil { - t.Fatalf("GetenvTime failed to parse: %v", err) - } else { - nowStr := now.Format(time.RFC3339) - valStr := val.Format(time.RFC3339) - if nowStr != valStr { - t.Fatalf("GetenvTime returns incorrect value: %s, Expected: %s", valStr, nowStr) - } - } - - job.Setenv("bar", "Obviously I'm not a date") - if val, err := job.GetenvTime("bar"); err == nil { - t.Fatalf("GetenvTime was supposed to fail, instead returned: %s", val) - } -} - -func TestSetenvInt(t *testing.T) { - job := mkJob(t, "dummy") - - job.SetenvInt("foo", -42) - if val := job.GetenvInt("foo"); val != -42 { - t.Fatalf("GetenvInt returns incorrect value: %d", val) - } - - job.SetenvInt("bar", 42) - if val := job.GetenvInt("bar"); val != 42 { - t.Fatalf("GetenvInt returns incorrect value: %d", val) - } - if val := job.GetenvInt("nonexistent"); val != 0 { - t.Fatalf("GetenvInt returns incorrect value: %d", val) - } -} - -func TestSetenvList(t *testing.T) { - job := mkJob(t, "dummy") - - job.SetenvList("foo", []string{"bar"}) - if val := job.GetenvList("foo"); len(val) != 1 || val[0] != "bar" { - t.Fatalf("GetenvList returns incorrect value: %v", val) - } - - job.SetenvList("bar", nil) - if val := job.GetenvList("bar"); val != nil { - t.Fatalf("GetenvList returns incorrect value: %v", val) - } - if val := job.GetenvList("nonexistent"); val != nil { - t.Fatalf("GetenvList returns incorrect value: %v", val) - } -} - -func TestEnviron(t *testing.T) { - job := mkJob(t, "dummy") - job.Setenv("foo", "bar") - val, exists := job.Environ()["foo"] - if !exists { - t.Fatalf("foo not found in the environ") - } - if val != "bar" { - t.Fatalf("bar not found in the environ") - } -} - -func TestMultiMap(t *testing.T) { - e := &Env{} - e.Set("foo", "bar") - e.Set("bar", "baz") - e.Set("hello", "world") - m := e.MultiMap() - e2 := &Env{} - e2.Set("old_key", "something something something") - e2.InitMultiMap(m) - if v := e2.Get("old_key"); v != "" { - t.Fatalf("%#v", v) - } - if v := e2.Get("bar"); v != "baz" { - t.Fatalf("%#v", v) - } - if v := e2.Get("hello"); v != "world" { - t.Fatalf("%#v", v) - } -} - -func testMap(l int) [][2]string { - res := make([][2]string, l) - for i := 0; i < l; i++ { - t := [2]string{stringutils.GenerateRandomAsciiString(5), stringutils.GenerateRandomAsciiString(20)} - res[i] = t - } - return res -} - -func BenchmarkSet(b *testing.B) { - fix := testMap(100) - b.ResetTimer() - for i := 0; i < b.N; i++ { - env := &Env{} - for _, kv := range fix { - env.Set(kv[0], kv[1]) - } - } -} - -func BenchmarkSetJson(b *testing.B) { - fix := testMap(100) - type X struct { - f string - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - env := &Env{} - for _, kv := range fix { - if err := env.SetJson(kv[0], X{kv[1]}); err != nil { - b.Fatal(err) - } - } - } -} - -func BenchmarkGet(b *testing.B) { - fix := testMap(100) - env := &Env{} - for _, kv := range fix { - env.Set(kv[0], kv[1]) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - for _, kv := range fix { - env.Get(kv[0]) - } - } -} - -func BenchmarkGetJson(b *testing.B) { - fix := testMap(100) - env := &Env{} - type X struct { - f string - } - for _, kv := range fix { - env.SetJson(kv[0], X{kv[1]}) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - for _, kv := range fix { - if err := env.GetJson(kv[0], &X{}); err != nil { - b.Fatal(err) - } - } - } -} - -func BenchmarkEncode(b *testing.B) { - fix := testMap(100) - env := &Env{} - type X struct { - f string - } - // half a json - for i, kv := range fix { - if i%2 != 0 { - if err := env.SetJson(kv[0], X{kv[1]}); err != nil { - b.Fatal(err) - } - continue - } - env.Set(kv[0], kv[1]) - } - var writer bytes.Buffer - b.ResetTimer() - for i := 0; i < b.N; i++ { - env.Encode(&writer) - writer.Reset() - } -} - -func BenchmarkDecode(b *testing.B) { - fix := testMap(100) - env := &Env{} - type X struct { - f string - } - // half a json - for i, kv := range fix { - if i%2 != 0 { - if err := env.SetJson(kv[0], X{kv[1]}); err != nil { - b.Fatal(err) - } - continue - } - env.Set(kv[0], kv[1]) - } - var writer bytes.Buffer - env.Encode(&writer) - denv := &Env{} - reader := bytes.NewReader(writer.Bytes()) - b.ResetTimer() - for i := 0; i < b.N; i++ { - err := denv.Decode(reader) - if err != nil { - b.Fatal(err) - } - reader.Seek(0, 0) - } -} - -func TestLongNumbers(t *testing.T) { - type T struct { - TestNum int64 - } - v := T{67108864} - var buf bytes.Buffer - e := &Env{} - e.SetJson("Test", v) - if err := e.Encode(&buf); err != nil { - t.Fatal(err) - } - res := make(map[string]T) - if err := json.Unmarshal(buf.Bytes(), &res); err != nil { - t.Fatal(err) - } - if res["Test"].TestNum != v.TestNum { - t.Fatalf("TestNum %d, expected %d", res["Test"].TestNum, v.TestNum) - } -} - -func TestLongNumbersArray(t *testing.T) { - type T struct { - TestNum []int64 - } - v := T{[]int64{67108864}} - var buf bytes.Buffer - e := &Env{} - e.SetJson("Test", v) - if err := e.Encode(&buf); err != nil { - t.Fatal(err) - } - res := make(map[string]T) - if err := json.Unmarshal(buf.Bytes(), &res); err != nil { - t.Fatal(err) - } - if res["Test"].TestNum[0] != v.TestNum[0] { - t.Fatalf("TestNum %d, expected %d", res["Test"].TestNum, v.TestNum) - } -} diff --git a/engine/hack.go b/engine/hack.go deleted file mode 100644 index 10595ce2b1..0000000000 --- a/engine/hack.go +++ /dev/null @@ -1,21 +0,0 @@ -package engine - -type Hack map[string]interface{} - -func (eng *Engine) HackGetGlobalVar(key string) interface{} { - if eng.hack == nil { - return nil - } - val, exists := eng.hack[key] - if !exists { - return nil - } - return val -} - -func (eng *Engine) HackSetGlobalVar(key string, val interface{}) { - if eng.hack == nil { - eng.hack = make(Hack) - } - eng.hack[key] = val -} diff --git a/engine/helpers_test.go b/engine/helpers_test.go deleted file mode 100644 index cfa11da7cd..0000000000 --- a/engine/helpers_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package engine - -import ( - "testing" -) - -var globalTestID string - -func mkJob(t *testing.T, name string, args ...string) *Job { - return New().Job(name, args...) -} diff --git a/engine/http.go b/engine/http.go deleted file mode 100644 index 7e4dcd7bb4..0000000000 --- a/engine/http.go +++ /dev/null @@ -1,42 +0,0 @@ -package engine - -import ( - "net/http" - "path" -) - -// ServeHTTP executes a job as specified by the http request `r`, and sends the -// result as an http response. -// This method allows an Engine instance to be passed as a standard http.Handler interface. -// -// Note that the protocol used in this method is a convenience wrapper and is not the canonical -// implementation of remote job execution. This is because HTTP/1 does not handle stream multiplexing, -// and so cannot differentiate stdout from stderr. Additionally, headers cannot be added to a response -// once data has been written to the body, which makes it inconvenient to return metadata such -// as the exit status. -// -func (eng *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) { - var ( - jobName = path.Base(r.URL.Path) - jobArgs, exists = r.URL.Query()["a"] - ) - if !exists { - jobArgs = []string{} - } - w.Header().Set("Job-Name", jobName) - for _, arg := range jobArgs { - w.Header().Add("Job-Args", arg) - } - job := eng.Job(jobName, jobArgs...) - job.Stdout.Add(w) - job.Stderr.Add(w) - // FIXME: distinguish job status from engine error in Run() - // The former should be passed as a special header, the former - // should cause a 500 status - w.WriteHeader(http.StatusOK) - // The exit status cannot be sent reliably with HTTP1, because headers - // can only be sent before the body. - // (we could possibly use http footers via chunked encoding, but I couldn't find - // how to use them in net/http) - job.Run() -} diff --git a/engine/job.go b/engine/job.go deleted file mode 100644 index 12acdc9334..0000000000 --- a/engine/job.go +++ /dev/null @@ -1,222 +0,0 @@ -package engine - -import ( - "bytes" - "fmt" - "io" - "strings" - "sync" - "time" - - "github.com/Sirupsen/logrus" -) - -// A job is the fundamental unit of work in the docker engine. -// Everything docker can do should eventually be exposed as a job. -// For example: execute a process in a container, create a new container, -// download an archive from the internet, serve the http api, etc. -// -// The job API is designed after unix processes: a job has a name, arguments, -// environment variables, standard streams for input, output and error. -type Job struct { - Eng *Engine - Name string - Args []string - env *Env - Stdout *Output - Stderr *Output - Stdin *Input - handler Handler - end time.Time - closeIO bool - - // When closed, the job has been cancelled. - // Note: not all jobs implement cancellation. - // See Job.Cancel() and Job.WaitCancelled() - cancelled chan struct{} - cancelOnce sync.Once -} - -// Run executes the job and blocks until the job completes. -// If the job fails it returns an error -func (job *Job) Run() (err error) { - defer func() { - // Wait for all background tasks to complete - if job.closeIO { - if err := job.Stdout.Close(); err != nil { - logrus.Error(err) - } - if err := job.Stderr.Close(); err != nil { - logrus.Error(err) - } - if err := job.Stdin.Close(); err != nil { - logrus.Error(err) - } - } - }() - - if job.Eng.IsShutdown() && !job.GetenvBool("overrideShutdown") { - return fmt.Errorf("engine is shutdown") - } - // FIXME: this is a temporary workaround to avoid Engine.Shutdown - // waiting 5 seconds for server/api.ServeApi to complete (which it never will) - // everytime the daemon is cleanly restarted. - // The permanent fix is to implement Job.Stop and Job.OnStop so that - // ServeApi can cooperate and terminate cleanly. - if job.Name != "serveapi" { - job.Eng.l.Lock() - job.Eng.tasks.Add(1) - job.Eng.l.Unlock() - defer job.Eng.tasks.Done() - } - // FIXME: make this thread-safe - // FIXME: implement wait - if !job.end.IsZero() { - return fmt.Errorf("%s: job has already completed", job.Name) - } - // Log beginning and end of the job - if job.Eng.Logging { - logrus.Infof("+job %s", job.CallString()) - defer func() { - okerr := "OK" - if err != nil { - okerr = fmt.Sprintf("ERR: %s", err) - } - logrus.Infof("-job %s %s", job.CallString(), okerr) - }() - } - - if job.handler == nil { - return fmt.Errorf("%s: command not found", job.Name) - } - - var errorMessage = bytes.NewBuffer(nil) - job.Stderr.Add(errorMessage) - - err = job.handler(job) - job.end = time.Now() - - return -} - -func (job *Job) CallString() string { - return fmt.Sprintf("%s(%s)", job.Name, strings.Join(job.Args, ", ")) -} - -func (job *Job) Env() *Env { - return job.env -} - -func (job *Job) EnvExists(key string) (value bool) { - return job.env.Exists(key) -} - -func (job *Job) Getenv(key string) (value string) { - return job.env.Get(key) -} - -func (job *Job) GetenvBool(key string) (value bool) { - return job.env.GetBool(key) -} - -func (job *Job) SetenvBool(key string, value bool) { - job.env.SetBool(key, value) -} - -func (job *Job) GetenvTime(key string) (value time.Time, err error) { - return job.env.GetTime(key) -} - -func (job *Job) SetenvTime(key string, value time.Time) { - job.env.SetTime(key, value) -} - -func (job *Job) GetenvSubEnv(key string) *Env { - return job.env.GetSubEnv(key) -} - -func (job *Job) SetenvSubEnv(key string, value *Env) error { - return job.env.SetSubEnv(key, value) -} - -func (job *Job) GetenvInt64(key string) int64 { - return job.env.GetInt64(key) -} - -func (job *Job) GetenvInt(key string) int { - return job.env.GetInt(key) -} - -func (job *Job) SetenvInt64(key string, value int64) { - job.env.SetInt64(key, value) -} - -func (job *Job) SetenvInt(key string, value int) { - job.env.SetInt(key, value) -} - -// Returns nil if key not found -func (job *Job) GetenvList(key string) []string { - return job.env.GetList(key) -} - -func (job *Job) GetenvJson(key string, iface interface{}) error { - return job.env.GetJson(key, iface) -} - -func (job *Job) SetenvJson(key string, value interface{}) error { - return job.env.SetJson(key, value) -} - -func (job *Job) SetenvList(key string, value []string) error { - return job.env.SetJson(key, value) -} - -func (job *Job) Setenv(key, value string) { - job.env.Set(key, value) -} - -// DecodeEnv decodes `src` as a json dictionary, and adds -// each decoded key-value pair to the environment. -// -// If `src` cannot be decoded as a json dictionary, an error -// is returned. -func (job *Job) DecodeEnv(src io.Reader) error { - return job.env.Decode(src) -} - -func (job *Job) EncodeEnv(dst io.Writer) error { - return job.env.Encode(dst) -} - -func (job *Job) ImportEnv(src interface{}) (err error) { - return job.env.Import(src) -} - -func (job *Job) Environ() map[string]string { - return job.env.Map() -} - -func (job *Job) Printf(format string, args ...interface{}) (n int, err error) { - return fmt.Fprintf(job.Stdout, format, args...) -} - -func (job *Job) Errorf(format string, args ...interface{}) (n int, err error) { - return fmt.Fprintf(job.Stderr, format, args...) -} - -func (job *Job) SetCloseIO(val bool) { - job.closeIO = val -} - -// When called, causes the Job.WaitCancelled channel to unblock. -func (job *Job) Cancel() { - job.cancelOnce.Do(func() { - close(job.cancelled) - }) -} - -// Returns a channel which is closed ("never blocks") when the job is cancelled. -func (job *Job) WaitCancelled() <-chan struct{} { - return job.cancelled -} diff --git a/engine/job_test.go b/engine/job_test.go deleted file mode 100644 index 76135e6e67..0000000000 --- a/engine/job_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package engine - -import ( - "bytes" - "errors" - "fmt" - "testing" -) - -func TestJobOK(t *testing.T) { - eng := New() - eng.Register("return_ok", func(job *Job) error { return nil }) - err := eng.Job("return_ok").Run() - if err != nil { - t.Fatalf("Expected: err=%v\nReceived: err=%v", nil, err) - } -} - -func TestJobErr(t *testing.T) { - eng := New() - eng.Register("return_err", func(job *Job) error { return errors.New("return_err") }) - err := eng.Job("return_err").Run() - if err == nil { - t.Fatalf("When a job returns error, Run() should return an error") - } -} - -func TestJobStdoutString(t *testing.T) { - eng := New() - // FIXME: test multiple combinations of output and status - eng.Register("say_something_in_stdout", func(job *Job) error { - job.Printf("Hello world\n") - return nil - }) - - job := eng.Job("say_something_in_stdout") - var outputBuffer = bytes.NewBuffer(nil) - job.Stdout.Add(outputBuffer) - if err := job.Run(); err != nil { - t.Fatal(err) - } - fmt.Println(outputBuffer) - var output = Tail(outputBuffer, 1) - if expectedOutput := "Hello world"; output != expectedOutput { - t.Fatalf("Stdout last line:\nExpected: %v\nReceived: %v", expectedOutput, output) - } -} diff --git a/engine/shutdown_test.go b/engine/shutdown_test.go deleted file mode 100644 index d2ef0339de..0000000000 --- a/engine/shutdown_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package engine - -import ( - "testing" - "time" -) - -func TestShutdownEmpty(t *testing.T) { - eng := New() - if eng.IsShutdown() { - t.Fatalf("IsShutdown should be false") - } - eng.Shutdown() - if !eng.IsShutdown() { - t.Fatalf("IsShutdown should be true") - } -} - -func TestShutdownAfterRun(t *testing.T) { - eng := New() - eng.Register("foo", func(job *Job) error { - return nil - }) - if err := eng.Job("foo").Run(); err != nil { - t.Fatal(err) - } - eng.Shutdown() - if err := eng.Job("foo").Run(); err == nil { - t.Fatalf("%#v", *eng) - } -} - -// An approximate and racy, but better-than-nothing test that -// -func TestShutdownDuringRun(t *testing.T) { - var ( - jobDelay time.Duration = 500 * time.Millisecond - jobDelayLow time.Duration = 100 * time.Millisecond - jobDelayHigh time.Duration = 700 * time.Millisecond - ) - eng := New() - var completed bool - eng.Register("foo", func(job *Job) error { - time.Sleep(jobDelay) - completed = true - return nil - }) - go eng.Job("foo").Run() - time.Sleep(50 * time.Millisecond) - done := make(chan struct{}) - var startShutdown time.Time - go func() { - startShutdown = time.Now() - eng.Shutdown() - close(done) - }() - time.Sleep(50 * time.Millisecond) - if err := eng.Job("foo").Run(); err == nil { - t.Fatalf("run on shutdown should fail: %#v", *eng) - } - <-done - // Verify that Shutdown() blocks for roughly 500ms, instead - // of returning almost instantly. - // - // We use >100ms to leave ample margin for race conditions between - // goroutines. It's possible (but unlikely in reasonable testing - // conditions), that this test will cause a false positive or false - // negative. But it's probably better than not having any test - // for the 99.999% of time where testing conditions are reasonable. - if d := time.Since(startShutdown); d.Nanoseconds() < jobDelayLow.Nanoseconds() { - t.Fatalf("shutdown did not block long enough: %v", d) - } else if d.Nanoseconds() > jobDelayHigh.Nanoseconds() { - t.Fatalf("shutdown blocked too long: %v", d) - } - if !completed { - t.Fatalf("job did not complete") - } -} diff --git a/engine/streams.go b/engine/streams.go deleted file mode 100644 index 2863e94487..0000000000 --- a/engine/streams.go +++ /dev/null @@ -1,188 +0,0 @@ -package engine - -import ( - "bytes" - "fmt" - "io" - "strings" - "sync" - "unicode" -) - -type Output struct { - sync.Mutex - dests []io.Writer - tasks sync.WaitGroup - used bool -} - -// Tail returns the n last lines of a buffer -// stripped out of trailing white spaces, if any. -// -// if n <= 0, returns an empty string -func Tail(buffer *bytes.Buffer, n int) string { - if n <= 0 { - return "" - } - s := strings.TrimRightFunc(buffer.String(), unicode.IsSpace) - i := len(s) - 1 - for ; i >= 0 && n > 0; i-- { - if s[i] == '\n' { - n-- - if n == 0 { - break - } - } - } - // when i == -1, return the whole string which is s[0:] - return s[i+1:] -} - -// NewOutput returns a new Output object with no destinations attached. -// Writing to an empty Output will cause the written data to be discarded. -func NewOutput() *Output { - return &Output{} -} - -// Return true if something was written on this output -func (o *Output) Used() bool { - o.Lock() - defer o.Unlock() - return o.used -} - -// Add attaches a new destination to the Output. Any data subsequently written -// to the output will be written to the new destination in addition to all the others. -// This method is thread-safe. -func (o *Output) Add(dst io.Writer) { - o.Lock() - defer o.Unlock() - o.dests = append(o.dests, dst) -} - -// Set closes and remove existing destination and then attaches a new destination to -// the Output. Any data subsequently written to the output will be written to the new -// destination in addition to all the others. This method is thread-safe. -func (o *Output) Set(dst io.Writer) { - o.Close() - o.Lock() - defer o.Unlock() - o.dests = []io.Writer{dst} -} - -// AddPipe creates an in-memory pipe with io.Pipe(), adds its writing end as a destination, -// and returns its reading end for consumption by the caller. -// This is a rough equivalent similar to Cmd.StdoutPipe() in the standard os/exec package. -// This method is thread-safe. -func (o *Output) AddPipe() (io.Reader, error) { - r, w := io.Pipe() - o.Add(w) - return r, nil -} - -// Write writes the same data to all registered destinations. -// This method is thread-safe. -func (o *Output) Write(p []byte) (n int, err error) { - o.Lock() - defer o.Unlock() - o.used = true - var firstErr error - for _, dst := range o.dests { - _, err := dst.Write(p) - if err != nil && firstErr == nil { - firstErr = err - } - } - return len(p), firstErr -} - -// Close unregisters all destinations and waits for all background -// AddTail and AddString tasks to complete. -// The Close method of each destination is called if it exists. -func (o *Output) Close() error { - o.Lock() - defer o.Unlock() - var firstErr error - for _, dst := range o.dests { - if closer, ok := dst.(io.Closer); ok { - err := closer.Close() - if err != nil && firstErr == nil { - firstErr = err - } - } - } - o.tasks.Wait() - o.dests = nil - return firstErr -} - -type Input struct { - src io.Reader - sync.Mutex -} - -// NewInput returns a new Input object with no source attached. -// Reading to an empty Input will return io.EOF. -func NewInput() *Input { - return &Input{} -} - -// Read reads from the input in a thread-safe way. -func (i *Input) Read(p []byte) (n int, err error) { - i.Mutex.Lock() - defer i.Mutex.Unlock() - if i.src == nil { - return 0, io.EOF - } - return i.src.Read(p) -} - -// Closes the src -// Not thread safe on purpose -func (i *Input) Close() error { - if i.src != nil { - if closer, ok := i.src.(io.Closer); ok { - return closer.Close() - } - } - return nil -} - -// Add attaches a new source to the input. -// Add can only be called once per input. Subsequent calls will -// return an error. -func (i *Input) Add(src io.Reader) error { - i.Mutex.Lock() - defer i.Mutex.Unlock() - if i.src != nil { - return fmt.Errorf("Maximum number of sources reached: 1") - } - i.src = src - return nil -} - -// AddEnv starts a new goroutine which will decode all subsequent data -// as a stream of json-encoded objects, and point `dst` to the last -// decoded object. -// The result `env` can be queried using the type-neutral Env interface. -// It is not safe to query `env` until the Output is closed. -func (o *Output) AddEnv() (dst *Env, err error) { - src, err := o.AddPipe() - if err != nil { - return nil, err - } - dst = &Env{} - o.tasks.Add(1) - go func() { - defer o.tasks.Done() - decoder := NewDecoder(src) - for { - env, err := decoder.Decode() - if err != nil { - return - } - *dst = *env - } - }() - return dst, nil -} diff --git a/engine/streams_test.go b/engine/streams_test.go deleted file mode 100644 index c22338a32e..0000000000 --- a/engine/streams_test.go +++ /dev/null @@ -1,215 +0,0 @@ -package engine - -import ( - "bufio" - "bytes" - "fmt" - "io" - "io/ioutil" - "strings" - "testing" -) - -type sentinelWriteCloser struct { - calledWrite bool - calledClose bool -} - -func (w *sentinelWriteCloser) Write(p []byte) (int, error) { - w.calledWrite = true - return len(p), nil -} - -func (w *sentinelWriteCloser) Close() error { - w.calledClose = true - return nil -} - -func TestOutputAddEnv(t *testing.T) { - input := "{\"foo\": \"bar\", \"answer_to_life_the_universe_and_everything\": 42}" - o := NewOutput() - result, err := o.AddEnv() - if err != nil { - t.Fatal(err) - } - o.Write([]byte(input)) - o.Close() - if v := result.Get("foo"); v != "bar" { - t.Errorf("Expected %v, got %v", "bar", v) - } - if v := result.GetInt("answer_to_life_the_universe_and_everything"); v != 42 { - t.Errorf("Expected %v, got %v", 42, v) - } - if v := result.Get("this-value-doesnt-exist"); v != "" { - t.Errorf("Expected %v, got %v", "", v) - } -} - -func TestOutputAddClose(t *testing.T) { - o := NewOutput() - var s sentinelWriteCloser - o.Add(&s) - if err := o.Close(); err != nil { - t.Fatal(err) - } - // Write data after the output is closed. - // Write should succeed, but no destination should receive it. - if _, err := o.Write([]byte("foo bar")); err != nil { - t.Fatal(err) - } - if !s.calledClose { - t.Fatal("Output.Close() didn't close the destination") - } -} - -func TestOutputAddPipe(t *testing.T) { - var testInputs = []string{ - "hello, world!", - "One\nTwo\nThree", - "", - "A line\nThen another nl-terminated line\n", - "A line followed by an empty line\n\n", - } - for _, input := range testInputs { - expectedOutput := input - o := NewOutput() - r, err := o.AddPipe() - if err != nil { - t.Fatal(err) - } - go func(o *Output) { - if n, err := o.Write([]byte(input)); err != nil { - t.Error(err) - } else if n != len(input) { - t.Errorf("Expected %d, got %d", len(input), n) - } - if err := o.Close(); err != nil { - t.Error(err) - } - }(o) - output, err := ioutil.ReadAll(r) - if err != nil { - t.Fatal(err) - } - if string(output) != expectedOutput { - t.Errorf("Last line is not stored as return string.\nExpected: '%s'\nGot: '%s'", expectedOutput, output) - } - } -} - -func TestTail(t *testing.T) { - var tests = make(map[string][]string) - tests["hello, world!"] = []string{ - "", - "hello, world!", - "hello, world!", - "hello, world!", - } - tests["One\nTwo\nThree"] = []string{ - "", - "Three", - "Two\nThree", - "One\nTwo\nThree", - } - tests["One\nTwo\n\n\n"] = []string{ - "", - "Two", - "One\nTwo", - } - for input, outputs := range tests { - for n, expectedOutput := range outputs { - output := Tail(bytes.NewBufferString(input), n) - if output != expectedOutput { - t.Errorf("Tail n=%d returned wrong result.\nExpected: '%s'\nGot : '%s'", n, expectedOutput, output) - } - } - } -} - -func lastLine(txt string) string { - scanner := bufio.NewScanner(strings.NewReader(txt)) - var lastLine string - for scanner.Scan() { - lastLine = scanner.Text() - } - return lastLine -} - -func TestOutputAdd(t *testing.T) { - o := NewOutput() - b := &bytes.Buffer{} - o.Add(b) - input := "hello, world!" - if n, err := o.Write([]byte(input)); err != nil { - t.Fatal(err) - } else if n != len(input) { - t.Fatalf("Expected %d, got %d", len(input), n) - } - if output := b.String(); output != input { - t.Fatalf("Received wrong data from Add.\nExpected: '%s'\nGot: '%s'", input, output) - } -} - -func TestOutputWriteError(t *testing.T) { - o := NewOutput() - buf := &bytes.Buffer{} - o.Add(buf) - r, w := io.Pipe() - input := "Hello there" - expectedErr := fmt.Errorf("This is an error") - r.CloseWithError(expectedErr) - o.Add(w) - n, err := o.Write([]byte(input)) - if err != expectedErr { - t.Fatalf("Output.Write() should return the first error encountered, if any") - } - if buf.String() != input { - t.Fatalf("Output.Write() should attempt write on all destinations, even after encountering an error") - } - if n != len(input) { - t.Fatalf("Output.Write() should return the size of the input if it successfully writes to at least one destination") - } -} - -func TestInputAddEmpty(t *testing.T) { - i := NewInput() - var b bytes.Buffer - if err := i.Add(&b); err != nil { - t.Fatal(err) - } - data, err := ioutil.ReadAll(i) - if err != nil { - t.Fatal(err) - } - if len(data) > 0 { - t.Fatalf("Read from empty input should yield no data") - } -} - -func TestInputAddTwo(t *testing.T) { - i := NewInput() - var b1 bytes.Buffer - // First add should succeed - if err := i.Add(&b1); err != nil { - t.Fatal(err) - } - var b2 bytes.Buffer - // Second add should fail - if err := i.Add(&b2); err == nil { - t.Fatalf("Adding a second source should return an error") - } -} - -func TestInputAddNotEmpty(t *testing.T) { - i := NewInput() - b := bytes.NewBufferString("hello world\nabc") - expectedResult := b.String() - i.Add(b) - result, err := ioutil.ReadAll(i) - if err != nil { - t.Fatal(err) - } - if string(result) != expectedResult { - t.Fatalf("Expected: %v\nReceived: %v", expectedResult, result) - } -} diff --git a/hack/make.sh b/hack/make.sh index 31e08cd376..cfa71eb546 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -57,7 +57,6 @@ DEFAULT_BUNDLES=( test-docker-py dynbinary - test-integration cover cross @@ -216,7 +215,6 @@ find_dirs() { find . -not \( \ \( \ -path './vendor/*' \ - -o -path './integration/*' \ -o -path './integration-cli/*' \ -o -path './contrib/*' \ -o -path './pkg/mflag/example/*' \ diff --git a/hack/make/test-integration b/hack/make/test-integration deleted file mode 100644 index 206e37abf0..0000000000 --- a/hack/make/test-integration +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -set -e - -DEST=$1 - -INIT=$DEST/../dynbinary/dockerinit-$VERSION -[ -x "$INIT" ] || { - source "${MAKEDIR}/.dockerinit" - INIT="$DEST/dockerinit" -} -export TEST_DOCKERINIT_PATH="$INIT" - -bundle_test_integration() { - LDFLAGS=" - $LDFLAGS - -X $DOCKER_PKG/dockerversion.INITSHA1 \"$DOCKER_INITSHA1\" - " go_test_dir ./integration \ - "-coverpkg $(find_dirs '*.go' | sed 's,^\.,'$DOCKER_PKG',g' | paste -d, -s)" -} - -# this "grep" hides some really irritating warnings that "go test -coverpkg" -# spews when it is given packages that aren't used -bundle_test_integration 2>&1 \ - | grep --line-buffered -v '^warning: no packages being tested depend on ' \ - | tee -a "$DEST/test.log" diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 1fec3912e6..e43daed8f0 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -858,3 +858,185 @@ func (s *DockerSuite) TestContainerApiRename(c *check.C) { c.Fatalf("Failed to rename container, expected %v, got %v. Container rename API failed", newName, name) } } + +func (s *DockerSuite) TestContainerApiKill(c *check.C) { + name := "test-api-kill" + runCmd := exec.Command(dockerBinary, "run", "-di", "--name", name, "busybox", "top") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + c.Fatalf("Error on container creation: %v, output: %q", err, out) + } + + status, _, err := sockRequest("POST", "/containers/"+name+"/kill", nil) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) + + state, err := inspectField(name, "State.Running") + if err != nil { + c.Fatal(err) + } + if state != "false" { + c.Fatalf("got wrong State from container %s: %q", name, state) + } +} + +func (s *DockerSuite) TestContainerApiRestart(c *check.C) { + name := "test-api-restart" + runCmd := exec.Command(dockerBinary, "run", "-di", "--name", name, "busybox", "top") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + c.Fatalf("Error on container creation: %v, output: %q", err, out) + } + + status, _, err := sockRequest("POST", "/containers/"+name+"/restart?t=1", nil) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) + + if err := waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 5); err != nil { + c.Fatal(err) + } +} + +func (s *DockerSuite) TestContainerApiStart(c *check.C) { + name := "testing-start" + config := map[string]interface{}{ + "Image": "busybox", + "Cmd": []string{"/bin/sh", "-c", "/bin/top"}, + "OpenStdin": true, + } + + status, _, err := sockRequest("POST", "/containers/create?name="+name, config) + c.Assert(status, check.Equals, http.StatusCreated) + c.Assert(err, check.IsNil) + + conf := make(map[string]interface{}) + status, _, err = sockRequest("POST", "/containers/"+name+"/start", conf) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) + + // second call to start should give 304 + status, _, err = sockRequest("POST", "/containers/"+name+"/start", conf) + c.Assert(status, check.Equals, http.StatusNotModified) + c.Assert(err, check.IsNil) +} + +func (s *DockerSuite) TestContainerApiStop(c *check.C) { + name := "test-api-stop" + runCmd := exec.Command(dockerBinary, "run", "-di", "--name", name, "busybox", "top") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + c.Fatalf("Error on container creation: %v, output: %q", err, out) + } + + status, _, err := sockRequest("POST", "/containers/"+name+"/stop?t=1", nil) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) + + if err := waitInspect(name, "{{ .State.Running }}", "false", 5); err != nil { + c.Fatal(err) + } + + // second call to start should give 304 + status, _, err = sockRequest("POST", "/containers/"+name+"/stop?t=1", nil) + c.Assert(status, check.Equals, http.StatusNotModified) + c.Assert(err, check.IsNil) +} + +func (s *DockerSuite) TestContainerApiWait(c *check.C) { + name := "test-api-wait" + runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "sleep", "5") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + c.Fatalf("Error on container creation: %v, output: %q", err, out) + } + + status, body, err := sockRequest("POST", "/containers/"+name+"/wait", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + + if err := waitInspect(name, "{{ .State.Running }}", "false", 5); err != nil { + c.Fatal(err) + } + + var waitres types.ContainerWaitResponse + if err := json.Unmarshal(body, &waitres); err != nil { + c.Fatalf("unable to unmarshal response body: %v", err) + } + + if waitres.StatusCode != 0 { + c.Fatalf("Expected wait response StatusCode to be 0, got %d", waitres.StatusCode) + } +} + +func (s *DockerSuite) TestContainerApiCopy(c *check.C) { + name := "test-container-api-copy" + runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "touch", "/test.txt") + _, err := runCommand(runCmd) + c.Assert(err, check.IsNil) + + postData := types.CopyConfig{ + Resource: "/test.txt", + } + + status, body, err := sockRequest("POST", "/containers/"+name+"/copy", postData) + c.Assert(err, check.IsNil) + c.Assert(status, check.Equals, http.StatusOK) + + found := false + for tarReader := tar.NewReader(bytes.NewReader(body)); ; { + h, err := tarReader.Next() + if err != nil { + if err == io.EOF { + break + } + c.Fatal(err) + } + if h.Name == "test.txt" { + found = true + break + } + } + c.Assert(found, check.Equals, true) +} + +func (s *DockerSuite) TestContainerApiCopyResourcePathEmpty(c *check.C) { + name := "test-container-api-copy-resource-empty" + runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "touch", "/test.txt") + _, err := runCommand(runCmd) + c.Assert(err, check.IsNil) + + postData := types.CopyConfig{ + Resource: "", + } + + status, body, err := sockRequest("POST", "/containers/"+name+"/copy", postData) + c.Assert(err, check.IsNil) + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(string(body), check.Matches, "Path cannot be empty\n") +} + +func (s *DockerSuite) TestContainerApiCopyResourcePathNotFound(c *check.C) { + name := "test-container-api-copy-resource-not-found" + runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox") + _, err := runCommand(runCmd) + c.Assert(err, check.IsNil) + + postData := types.CopyConfig{ + Resource: "/notexist", + } + + status, body, err := sockRequest("POST", "/containers/"+name+"/copy", postData) + c.Assert(err, check.IsNil) + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(string(body), check.Matches, "Could not find the file /notexist in container "+name+"\n") +} + +func (s *DockerSuite) TestContainerApiCopyContainerNotFound(c *check.C) { + postData := types.CopyConfig{ + Resource: "/something", + } + + status, _, err := sockRequest("POST", "/containers/notexists/copy", postData) + c.Assert(err, check.IsNil) + c.Assert(status, check.Equals, http.StatusNotFound) +} diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index e88fbaeaad..15484715b6 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -35,7 +35,7 @@ func (s *DockerSuite) TestApiImagesFilter(c *check.C) { c.Fatal(err, out) } } - type image struct{ RepoTags []string } + type image types.Image getImages := func(filter string) []image { v := url.Values{} v.Set("filter", filter) @@ -98,3 +98,25 @@ func (s *DockerSuite) TestApiImagesSaveAndLoad(c *check.C) { c.Fatal("load did not work properly") } } + +func (s *DockerSuite) TestApiImagesDelete(c *check.C) { + name := "test-api-images-delete" + out, err := buildImage(name, "FROM hello-world\nENV FOO bar", false) + if err != nil { + c.Fatal(err) + } + defer deleteImages(name) + id := strings.TrimSpace(out) + + if out, err := exec.Command(dockerBinary, "tag", name, "test:tag1").CombinedOutput(); err != nil { + c.Fatal(err, out) + } + + status, _, err := sockRequest("DELETE", "/images/"+id, nil) + c.Assert(status, check.Equals, http.StatusConflict) + c.Assert(err, check.IsNil) + + status, _, err = sockRequest("DELETE", "/images/test:tag1", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) +} diff --git a/integration-cli/docker_api_test.go b/integration-cli/docker_api_test.go new file mode 100644 index 0000000000..e9ca0d5929 --- /dev/null +++ b/integration-cli/docker_api_test.go @@ -0,0 +1,25 @@ +package main + +import ( + "net/http" + + "github.com/go-check/check" +) + +func (s *DockerSuite) TestApiOptionsRoute(c *check.C) { + status, _, err := sockRequest("OPTIONS", "/", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) +} + +func (s *DockerSuite) TestApiGetEnabledCors(c *check.C) { + res, body, err := sockRequestRaw("GET", "/version", nil, "") + body.Close() + c.Assert(err, check.IsNil) + c.Assert(res.StatusCode, check.Equals, http.StatusOK) + // TODO: @runcom incomplete tests, why old integration tests had this headers + // and here none of the headers below are in the response? + //c.Log(res.Header) + //c.Assert(res.Header.Get("Access-Control-Allow-Origin"), check.Equals, "*") + //c.Assert(res.Header.Get("Access-Control-Allow-Headers"), check.Equals, "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth") +} diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 17141ddef0..de24558393 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -980,7 +980,7 @@ func (s *DockerDaemonSuite) TestDaemonUnixSockCleanedUp(c *check.C) { } } -func (s *DockerDaemonSuite) TestDaemonwithwrongkey(c *check.C) { +func (s *DockerDaemonSuite) TestDaemonWithWrongkey(c *check.C) { type Config struct { Crv string `json:"crv"` D string `json:"d"` diff --git a/integration/README.md b/integration/README.md deleted file mode 100644 index 41f43a4ba7..0000000000 --- a/integration/README.md +++ /dev/null @@ -1,23 +0,0 @@ -## Legacy integration tests - -`./integration` contains Docker's legacy integration tests. -It is DEPRECATED and will eventually be removed. - -### If you are a *CONTRIBUTOR* and want to add a test: - -* Consider mocking out side effects and contributing a *unit test* in the subsystem -you're modifying. For example, the remote API has unit tests in `./api/server/server_unit_tests.go`. -The events subsystem has unit tests in `./events/events_test.go`. And so on. - -* For end-to-end integration tests, please contribute to `./integration-cli`. - - -### If you are a *MAINTAINER* - -Please don't allow patches adding new tests to `./integration`. - -### If you are *LOOKING FOR A WAY TO HELP* - -Please consider porting tests away from `./integration` and into either unit tests or CLI tests. - -Any help will be greatly appreciated! diff --git a/integration/api_test.go b/integration/api_test.go deleted file mode 100644 index e45fa97e82..0000000000 --- a/integration/api_test.go +++ /dev/null @@ -1,680 +0,0 @@ -package docker - -import ( - "bufio" - "bytes" - "encoding/json" - "io" - "io/ioutil" - "net" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/docker/docker/api" - "github.com/docker/docker/api/server" - "github.com/docker/docker/api/types" - "github.com/docker/docker/engine" - "github.com/docker/docker/runconfig" - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" -) - -func TestPostContainersKill(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - containerID := createTestContainer(eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("/bin/cat"), - OpenStdin: true, - }, - t, - ) - - startContainer(eng, containerID, t) - - // Give some time to the process to start - containerWaitTimeout(eng, containerID, t) - - if !containerRunning(eng, containerID, t) { - t.Errorf("Container should be running") - } - - r := httptest.NewRecorder() - req, err := http.NewRequest("POST", "/containers/"+containerID+"/kill", bytes.NewReader([]byte{})) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - if r.Code != http.StatusNoContent { - t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) - } - if containerRunning(eng, containerID, t) { - t.Fatalf("The container hasn't been killed") - } -} - -func TestPostContainersRestart(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - containerID := createTestContainer(eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("/bin/top"), - OpenStdin: true, - }, - t, - ) - - startContainer(eng, containerID, t) - - // Give some time to the process to start - containerWaitTimeout(eng, containerID, t) - - if !containerRunning(eng, containerID, t) { - t.Errorf("Container should be running") - } - - req, err := http.NewRequest("POST", "/containers/"+containerID+"/restart?t=1", bytes.NewReader([]byte{})) - if err != nil { - t.Fatal(err) - } - r := httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - if r.Code != http.StatusNoContent { - t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) - } - - // Give some time to the process to restart - containerWaitTimeout(eng, containerID, t) - - if !containerRunning(eng, containerID, t) { - t.Fatalf("Container should be running") - } - - containerKill(eng, containerID, t) -} - -func TestPostContainersStart(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - containerID := createTestContainer( - eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("/bin/cat"), - OpenStdin: true, - }, - t, - ) - - hostConfigJSON, err := json.Marshal(&runconfig.HostConfig{}) - - req, err := http.NewRequest("POST", "/containers/"+containerID+"/start", bytes.NewReader(hostConfigJSON)) - if err != nil { - t.Fatal(err) - } - - req.Header.Set("Content-Type", "application/json") - - r := httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - if r.Code != http.StatusNoContent { - t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) - } - - containerAssertExists(eng, containerID, t) - - req, err = http.NewRequest("POST", "/containers/"+containerID+"/start", bytes.NewReader(hostConfigJSON)) - if err != nil { - t.Fatal(err) - } - - req.Header.Set("Content-Type", "application/json") - - r = httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r, req) - - // Starting an already started container should return a 304 - assertHttpNotError(r, t) - if r.Code != http.StatusNotModified { - t.Fatalf("%d NOT MODIFIER expected, received %d\n", http.StatusNotModified, r.Code) - } - containerAssertExists(eng, containerID, t) - containerKill(eng, containerID, t) -} - -func TestPostContainersStop(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - containerID := createTestContainer(eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("/bin/top"), - OpenStdin: true, - }, - t, - ) - - startContainer(eng, containerID, t) - - // Give some time to the process to start - containerWaitTimeout(eng, containerID, t) - - if !containerRunning(eng, containerID, t) { - t.Errorf("Container should be running") - } - - // Note: as it is a POST request, it requires a body. - req, err := http.NewRequest("POST", "/containers/"+containerID+"/stop?t=1", bytes.NewReader([]byte{})) - if err != nil { - t.Fatal(err) - } - r := httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - if r.Code != http.StatusNoContent { - t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) - } - if containerRunning(eng, containerID, t) { - t.Fatalf("The container hasn't been stopped") - } - - req, err = http.NewRequest("POST", "/containers/"+containerID+"/stop?t=1", bytes.NewReader([]byte{})) - if err != nil { - t.Fatal(err) - } - - r = httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r, req) - - // Stopping an already stopper container should return a 304 - assertHttpNotError(r, t) - if r.Code != http.StatusNotModified { - t.Fatalf("%d NOT MODIFIER expected, received %d\n", http.StatusNotModified, r.Code) - } -} - -func TestPostContainersWait(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - containerID := createTestContainer(eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("/bin/sleep", "1"), - OpenStdin: true, - }, - t, - ) - startContainer(eng, containerID, t) - - setTimeout(t, "Wait timed out", 3*time.Second, func() { - r := httptest.NewRecorder() - req, err := http.NewRequest("POST", "/containers/"+containerID+"/wait", bytes.NewReader([]byte{})) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - var apiWait engine.Env - if err := apiWait.Decode(r.Body); err != nil { - t.Fatal(err) - } - if apiWait.GetInt("StatusCode") != 0 { - t.Fatalf("Non zero exit code for sleep: %d\n", apiWait.GetInt("StatusCode")) - } - }) - - if containerRunning(eng, containerID, t) { - t.Fatalf("The container should be stopped after wait") - } -} - -func TestPostContainersAttach(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - containerID := createTestContainer(eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("/bin/cat"), - OpenStdin: true, - }, - t, - ) - // Start the process - startContainer(eng, containerID, t) - - stdin, stdinPipe := io.Pipe() - stdout, stdoutPipe := io.Pipe() - - // Try to avoid the timeout in destroy. Best effort, don't check error - defer func() { - closeWrap(stdin, stdinPipe, stdout, stdoutPipe) - containerKill(eng, containerID, t) - }() - - // Attach to it - c1 := make(chan struct{}) - go func() { - defer close(c1) - - r := &hijackTester{ - ResponseRecorder: httptest.NewRecorder(), - in: stdin, - out: stdoutPipe, - } - - req, err := http.NewRequest("POST", "/containers/"+containerID+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{})) - if err != nil { - t.Fatal(err) - } - - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r.ResponseRecorder, t) - }() - - // Acknowledge hijack - setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() { - stdout.Read([]byte{}) - stdout.Read(make([]byte, 4096)) - }) - - setTimeout(t, "read/write assertion timed out", 2*time.Second, func() { - if err := assertPipe("hello\n", string([]byte{1, 0, 0, 0, 0, 0, 0, 6})+"hello", stdout, stdinPipe, 150); err != nil { - t.Fatal(err) - } - }) - - // Close pipes (client disconnects) - if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { - t.Fatal(err) - } - - // Wait for attach to finish, the client disconnected, therefore, Attach finished his job - setTimeout(t, "Waiting for CmdAttach timed out", 10*time.Second, func() { - <-c1 - }) - - // We closed stdin, expect /bin/cat to still be running - // Wait a little bit to make sure container.monitor() did his thing - containerWaitTimeout(eng, containerID, t) - - // Try to avoid the timeout in destroy. Best effort, don't check error - cStdin, _ := containerAttach(eng, containerID, t) - cStdin.Close() - containerWait(eng, containerID, t) -} - -func TestPostContainersAttachStderr(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - containerID := createTestContainer(eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("/bin/sh", "-c", "/bin/cat >&2"), - OpenStdin: true, - }, - t, - ) - // Start the process - startContainer(eng, containerID, t) - - stdin, stdinPipe := io.Pipe() - stdout, stdoutPipe := io.Pipe() - - // Try to avoid the timeout in destroy. Best effort, don't check error - defer func() { - closeWrap(stdin, stdinPipe, stdout, stdoutPipe) - containerKill(eng, containerID, t) - }() - - // Attach to it - c1 := make(chan struct{}) - go func() { - defer close(c1) - - r := &hijackTester{ - ResponseRecorder: httptest.NewRecorder(), - in: stdin, - out: stdoutPipe, - } - - req, err := http.NewRequest("POST", "/containers/"+containerID+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{})) - if err != nil { - t.Fatal(err) - } - - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r.ResponseRecorder, t) - }() - - // Acknowledge hijack - setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() { - stdout.Read([]byte{}) - stdout.Read(make([]byte, 4096)) - }) - - setTimeout(t, "read/write assertion timed out", 2*time.Second, func() { - if err := assertPipe("hello\n", string([]byte{2, 0, 0, 0, 0, 0, 0, 6})+"hello", stdout, stdinPipe, 150); err != nil { - t.Fatal(err) - } - }) - - // Close pipes (client disconnects) - if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { - t.Fatal(err) - } - - // Wait for attach to finish, the client disconnected, therefore, Attach finished his job - setTimeout(t, "Waiting for CmdAttach timed out", 10*time.Second, func() { - <-c1 - }) - - // We closed stdin, expect /bin/cat to still be running - // Wait a little bit to make sure container.monitor() did his thing - containerWaitTimeout(eng, containerID, t) - - // Try to avoid the timeout in destroy. Best effort, don't check error - cStdin, _ := containerAttach(eng, containerID, t) - cStdin.Close() - containerWait(eng, containerID, t) -} - -func TestOptionsRoute(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - r := httptest.NewRecorder() - req, err := http.NewRequest("OPTIONS", "/", nil) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - if r.Code != http.StatusOK { - t.Errorf("Expected response for OPTIONS request to be \"200\", %v found.", r.Code) - } -} - -func TestGetEnabledCors(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - r := httptest.NewRecorder() - - req, err := http.NewRequest("GET", "/version", nil) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - if r.Code != http.StatusOK { - t.Errorf("Expected response for OPTIONS request to be \"200\", %v found.", r.Code) - } - - allowOrigin := r.Header().Get("Access-Control-Allow-Origin") - allowHeaders := r.Header().Get("Access-Control-Allow-Headers") - allowMethods := r.Header().Get("Access-Control-Allow-Methods") - - if allowOrigin != "*" { - t.Errorf("Expected header Access-Control-Allow-Origin to be \"*\", %s found.", allowOrigin) - } - if allowHeaders != "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth" { - t.Errorf("Expected header Access-Control-Allow-Headers to be \"Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth\", %s found.", allowHeaders) - } - if allowMethods != "GET, POST, DELETE, PUT, OPTIONS" { - t.Errorf("Expected header Access-Control-Allow-Methods to be \"GET, POST, DELETE, PUT, OPTIONS\", %s found.", allowMethods) - } -} - -func TestDeleteImages(t *testing.T) { - eng := NewTestEngine(t) - //we expect errors, so we disable stderr - eng.Stderr = ioutil.Discard - defer mkDaemonFromEngine(eng, t).Nuke() - - initialImages := getImages(eng, t, true, "") - - d := getDaemon(eng) - if err := d.Repositories().Tag("test", "test", unitTestImageName, true); err != nil { - t.Fatal(err) - } - - images := getImages(eng, t, true, "") - - if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+1 { - t.Errorf("Expected %d images, %d found", len(initialImages[0].RepoTags)+1, len(images[0].RepoTags)) - } - - req, err := http.NewRequest("DELETE", "/images/"+unitTestImageID, nil) - if err != nil { - t.Fatal(err) - } - - r := httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r, req) - if r.Code != http.StatusConflict { - t.Fatalf("Expected http status 409-conflict, got %v", r.Code) - } - - req2, err := http.NewRequest("DELETE", "/images/test:test", nil) - if err != nil { - t.Fatal(err) - } - - r2 := httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r2, req2) - assertHttpNotError(r2, t) - if r2.Code != http.StatusOK { - t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) - } - - delImages := []types.ImageDelete{} - err = json.Unmarshal(r2.Body.Bytes(), &delImages) - if err != nil { - t.Fatal(err) - } - - if len(delImages) != 1 { - t.Fatalf("Expected %d event (untagged), got %d", 1, len(delImages)) - } - images = getImages(eng, t, false, "") - - if len(images) != len(initialImages) { - t.Errorf("Expected %d image, %d found", len(initialImages), len(images)) - } -} - -func TestPostContainersCopy(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - // Create a container and remove a file - containerID := createTestContainer(eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("touch", "/test.txt"), - }, - t, - ) - containerRun(eng, containerID, t) - - r := httptest.NewRecorder() - - var copyData engine.Env - copyData.Set("Resource", "/test.txt") - copyData.Set("HostPath", ".") - - jsonData := bytes.NewBuffer(nil) - if err := copyData.Encode(jsonData); err != nil { - t.Fatal(err) - } - - req, err := http.NewRequest("POST", "/containers/"+containerID+"/copy", jsonData) - if err != nil { - t.Fatal(err) - } - req.Header.Add("Content-Type", "application/json") - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - - if r.Code != http.StatusOK { - t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) - } - - found := false - for tarReader := tar.NewReader(r.Body); ; { - h, err := tarReader.Next() - if err != nil { - if err == io.EOF { - break - } - t.Fatal(err) - } - if h.Name == "test.txt" { - found = true - break - } - } - if !found { - t.Fatalf("The created test file has not been found in the copied output") - } -} - -func TestPostContainersCopyWhenContainerNotFound(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - r := httptest.NewRecorder() - - var copyData engine.Env - copyData.Set("Resource", "/test.txt") - copyData.Set("HostPath", ".") - - jsonData := bytes.NewBuffer(nil) - if err := copyData.Encode(jsonData); err != nil { - t.Fatal(err) - } - - req, err := http.NewRequest("POST", "/containers/id_not_found/copy", jsonData) - if err != nil { - t.Fatal(err) - } - req.Header.Add("Content-Type", "application/json") - server.ServeRequest(eng, api.APIVERSION, r, req) - if r.Code != http.StatusNotFound { - t.Fatalf("404 expected for id_not_found Container, received %v", r.Code) - } -} - -// Regression test for https://github.com/docker/docker/issues/6231 -func TestConstainersStartChunkedEncodingHostConfig(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - r := httptest.NewRecorder() - - var testData engine.Env - testData.Set("Image", "docker-test-image") - testData.SetAuto("Volumes", map[string]struct{}{"/foo": {}}) - testData.Set("Cmd", "true") - jsonData := bytes.NewBuffer(nil) - if err := testData.Encode(jsonData); err != nil { - t.Fatal(err) - } - - req, err := http.NewRequest("POST", "/containers/create?name=chunk_test", jsonData) - if err != nil { - t.Fatal(err) - } - - req.Header.Add("Content-Type", "application/json") - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - - var testData2 engine.Env - testData2.SetAuto("Binds", []string{"/tmp:/foo"}) - jsonData = bytes.NewBuffer(nil) - if err := testData2.Encode(jsonData); err != nil { - t.Fatal(err) - } - - req, err = http.NewRequest("POST", "/containers/chunk_test/start", jsonData) - if err != nil { - t.Fatal(err) - } - - req.Header.Add("Content-Type", "application/json") - // This is a cheat to make the http request do chunked encoding - // Otherwise (just setting the Content-Encoding to chunked) net/http will overwrite - // https://golang.org/src/pkg/net/http/request.go?s=11980:12172 - req.ContentLength = -1 - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - - type config struct { - HostConfig struct { - Binds []string - } - } - - req, err = http.NewRequest("GET", "/containers/chunk_test/json", nil) - if err != nil { - t.Fatal(err) - } - - r2 := httptest.NewRecorder() - req.Header.Add("Content-Type", "application/json") - server.ServeRequest(eng, api.APIVERSION, r2, req) - assertHttpNotError(r, t) - - c := config{} - - json.Unmarshal(r2.Body.Bytes(), &c) - - if len(c.HostConfig.Binds) == 0 { - t.Fatal("Chunked Encoding not handled") - } - - if c.HostConfig.Binds[0] != "/tmp:/foo" { - t.Fatal("Chunked encoding not properly handled, expected binds to be /tmp:/foo, got:", c.HostConfig.Binds[0]) - } -} - -// Mocked types for tests -type NopConn struct { - io.ReadCloser - io.Writer -} - -func (c *NopConn) LocalAddr() net.Addr { return nil } -func (c *NopConn) RemoteAddr() net.Addr { return nil } -func (c *NopConn) SetDeadline(t time.Time) error { return nil } -func (c *NopConn) SetReadDeadline(t time.Time) error { return nil } -func (c *NopConn) SetWriteDeadline(t time.Time) error { return nil } - -type hijackTester struct { - *httptest.ResponseRecorder - in io.ReadCloser - out io.Writer -} - -func (t *hijackTester) Hijack() (net.Conn, *bufio.ReadWriter, error) { - bufrw := bufio.NewReadWriter(bufio.NewReader(t.in), bufio.NewWriter(t.out)) - conn := &NopConn{ - ReadCloser: t.in, - Writer: t.out, - } - return conn, bufrw, nil -} diff --git a/integration/container_test.go b/integration/container_test.go deleted file mode 100644 index 9256e9997f..0000000000 --- a/integration/container_test.go +++ /dev/null @@ -1,235 +0,0 @@ -package docker - -import ( - "io" - "io/ioutil" - "testing" - "time" - - "github.com/docker/docker/runconfig" -) - -func TestRestartStdin(t *testing.T) { - daemon := mkDaemon(t) - defer nuke(daemon) - container, _, err := daemon.Create(&runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("cat"), - - OpenStdin: true, - }, - &runconfig.HostConfig{}, - "", - ) - if err != nil { - t.Fatal(err) - } - defer daemon.Rm(container) - - stdin := container.StdinPipe() - stdout := container.StdoutPipe() - if err := container.Start(); err != nil { - t.Fatal(err) - } - if _, err := io.WriteString(stdin, "hello world"); err != nil { - t.Fatal(err) - } - if err := stdin.Close(); err != nil { - t.Fatal(err) - } - container.WaitStop(-1 * time.Second) - output, err := ioutil.ReadAll(stdout) - if err != nil { - t.Fatal(err) - } - if err := stdout.Close(); err != nil { - t.Fatal(err) - } - if string(output) != "hello world" { - t.Fatalf("Unexpected output. Expected %s, received: %s", "hello world", string(output)) - } - - // Restart and try again - stdin = container.StdinPipe() - stdout = container.StdoutPipe() - if err := container.Start(); err != nil { - t.Fatal(err) - } - if _, err := io.WriteString(stdin, "hello world #2"); err != nil { - t.Fatal(err) - } - if err := stdin.Close(); err != nil { - t.Fatal(err) - } - container.WaitStop(-1 * time.Second) - output, err = ioutil.ReadAll(stdout) - if err != nil { - t.Fatal(err) - } - if err := stdout.Close(); err != nil { - t.Fatal(err) - } - if string(output) != "hello world #2" { - t.Fatalf("Unexpected output. Expected %s, received: %s", "hello world #2", string(output)) - } -} - -func TestStdin(t *testing.T) { - daemon := mkDaemon(t) - defer nuke(daemon) - container, _, err := daemon.Create(&runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("cat"), - - OpenStdin: true, - }, - &runconfig.HostConfig{}, - "", - ) - if err != nil { - t.Fatal(err) - } - defer daemon.Rm(container) - - stdin := container.StdinPipe() - stdout := container.StdoutPipe() - if err := container.Start(); err != nil { - t.Fatal(err) - } - defer stdin.Close() - defer stdout.Close() - if _, err := io.WriteString(stdin, "hello world"); err != nil { - t.Fatal(err) - } - if err := stdin.Close(); err != nil { - t.Fatal(err) - } - container.WaitStop(-1 * time.Second) - output, err := ioutil.ReadAll(stdout) - if err != nil { - t.Fatal(err) - } - if string(output) != "hello world" { - t.Fatalf("Unexpected output. Expected %s, received: %s", "hello world", string(output)) - } -} - -func TestTty(t *testing.T) { - daemon := mkDaemon(t) - defer nuke(daemon) - container, _, err := daemon.Create(&runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("cat"), - - OpenStdin: true, - }, - &runconfig.HostConfig{}, - "", - ) - if err != nil { - t.Fatal(err) - } - defer daemon.Rm(container) - - stdin := container.StdinPipe() - stdout := container.StdoutPipe() - if err := container.Start(); err != nil { - t.Fatal(err) - } - defer stdin.Close() - defer stdout.Close() - if _, err := io.WriteString(stdin, "hello world"); err != nil { - t.Fatal(err) - } - if err := stdin.Close(); err != nil { - t.Fatal(err) - } - container.WaitStop(-1 * time.Second) - output, err := ioutil.ReadAll(stdout) - if err != nil { - t.Fatal(err) - } - if string(output) != "hello world" { - t.Fatalf("Unexpected output. Expected %s, received: %s", "hello world", string(output)) - } -} - -func BenchmarkRunSequential(b *testing.B) { - daemon := mkDaemon(b) - defer nuke(daemon) - for i := 0; i < b.N; i++ { - container, _, err := daemon.Create(&runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("echo", "-n", "foo"), - }, - &runconfig.HostConfig{}, - "", - ) - if err != nil { - b.Fatal(err) - } - defer daemon.Rm(container) - output, err := container.Output() - if err != nil { - b.Fatal(err) - } - if string(output) != "foo" { - b.Fatalf("Unexpected output: %s", output) - } - if err := daemon.Rm(container); err != nil { - b.Fatal(err) - } - } -} - -func BenchmarkRunParallel(b *testing.B) { - daemon := mkDaemon(b) - defer nuke(daemon) - - var tasks []chan error - - for i := 0; i < b.N; i++ { - complete := make(chan error) - tasks = append(tasks, complete) - go func(i int, complete chan error) { - container, _, err := daemon.Create(&runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("echo", "-n", "foo"), - }, - &runconfig.HostConfig{}, - "", - ) - if err != nil { - complete <- err - return - } - defer daemon.Rm(container) - if err := container.Start(); err != nil { - complete <- err - return - } - if _, err := container.WaitStop(15 * time.Second); err != nil { - complete <- err - return - } - // if string(output) != "foo" { - // complete <- fmt.Errorf("Unexpected output: %v", string(output)) - // } - if err := daemon.Rm(container); err != nil { - complete <- err - return - } - complete <- nil - }(i, complete) - } - var errors []error - for _, task := range tasks { - err := <-task - if err != nil { - errors = append(errors, err) - } - } - if len(errors) > 0 { - b.Fatal(errors) - } -} diff --git a/integration/runtime_test.go b/integration/runtime_test.go deleted file mode 100644 index a2f22072c3..0000000000 --- a/integration/runtime_test.go +++ /dev/null @@ -1,847 +0,0 @@ -package docker - -import ( - "bytes" - "fmt" - "io" - std_log "log" - "net" - "net/url" - "os" - "path/filepath" - "runtime" - "strconv" - "strings" - "syscall" - "testing" - "time" - - "github.com/Sirupsen/logrus" - apiserver "github.com/docker/docker/api/server" - "github.com/docker/docker/cliconfig" - "github.com/docker/docker/daemon" - "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/engine" - "github.com/docker/docker/graph" - "github.com/docker/docker/image" - "github.com/docker/docker/nat" - "github.com/docker/docker/pkg/fileutils" - "github.com/docker/docker/pkg/ioutils" - "github.com/docker/docker/pkg/reexec" - "github.com/docker/docker/pkg/stringid" - "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" -) - -const ( - unitTestImageName = "docker-test-image" - unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 - unitTestImageIDShort = "83599e29c455" - unitTestNetworkBridge = "testdockbr0" - unitTestStoreBase = "/var/lib/docker/unit-tests" - unitTestDockerTmpdir = "/var/lib/docker/tmp" - testDaemonAddr = "127.0.0.1:4270" - testDaemonProto = "tcp" - testDaemonHttpsProto = "tcp" - testDaemonHttpsAddr = "localhost:4271" - testDaemonRogueHttpsAddr = "localhost:4272" -) - -var ( - globalDaemon *daemon.Daemon - globalHttpsEngine *engine.Engine - globalRogueHttpsEngine *engine.Engine - startFds int - startGoroutines int -) - -// FIXME: nuke() is deprecated by Daemon.Nuke() -func nuke(daemon *daemon.Daemon) error { - return daemon.Nuke() -} - -// FIXME: cleanup and nuke are redundant. -func cleanup(eng *engine.Engine, t *testing.T) error { - daemon := mkDaemonFromEngine(eng, t) - for _, container := range daemon.List() { - container.Kill() - daemon.Rm(container) - } - images, err := daemon.Repositories().Images(&graph.ImagesConfig{}) - if err != nil { - t.Fatal(err) - } - for _, image := range images { - if image.ID != unitTestImageID { - eng.Job("image_delete", image.ID).Run() - } - } - return nil -} - -func init() { - // Always use the same driver (vfs) for all integration tests. - // To test other drivers, we need a dedicated driver validation suite. - os.Setenv("DOCKER_DRIVER", "vfs") - os.Setenv("TEST", "1") - os.Setenv("DOCKER_TMPDIR", unitTestDockerTmpdir) - - // Hack to run sys init during unit testing - if reexec.Init() { - return - } - - if uid := syscall.Geteuid(); uid != 0 { - logrus.Fatalf("docker tests need to be run as root") - } - - // Copy dockerinit into our current testing directory, if provided (so we can test a separate dockerinit binary) - if dockerinit := os.Getenv("TEST_DOCKERINIT_PATH"); dockerinit != "" { - src, err := os.Open(dockerinit) - if err != nil { - logrus.Fatalf("Unable to open TEST_DOCKERINIT_PATH: %s", err) - } - defer src.Close() - dst, err := os.OpenFile(filepath.Join(filepath.Dir(utils.SelfPath()), "dockerinit"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0555) - if err != nil { - logrus.Fatalf("Unable to create dockerinit in test directory: %s", err) - } - defer dst.Close() - if _, err := io.Copy(dst, src); err != nil { - logrus.Fatalf("Unable to copy dockerinit to TEST_DOCKERINIT_PATH: %s", err) - } - dst.Close() - src.Close() - } - - // Setup the base daemon, which will be duplicated for each test. - // (no tests are run directly in the base) - setupBaseImage() - - // Create the "global daemon" with a long-running daemons for integration tests - spawnGlobalDaemon() - startFds, startGoroutines = fileutils.GetTotalUsedFds(), runtime.NumGoroutine() -} - -func setupBaseImage() { - eng := newTestEngine(std_log.New(os.Stderr, "", 0), false, unitTestStoreBase) - d := getDaemon(eng) - - _, err := d.Repositories().Lookup(unitTestImageName) - // If the unit test is not found, try to download it. - if err != nil { - // seems like we can just ignore the error here... - // there was a check of imgId from job stdout against unittestid but - // if there was an error how could the imgid from the job - // be compared?! it's obvious it's different, am I totally wrong? - - // Retrieve the Image - imagePullConfig := &graph.ImagePullConfig{ - Parallel: true, - OutStream: ioutils.NopWriteCloser(os.Stdout), - AuthConfig: &cliconfig.AuthConfig{}, - } - if err := d.Repositories().Pull(unitTestImageName, "", imagePullConfig); err != nil { - logrus.Fatalf("Unable to pull the test image: %s", err) - } - } -} - -func spawnGlobalDaemon() { - if globalDaemon != nil { - logrus.Debugf("Global daemon already exists. Skipping.") - return - } - t := std_log.New(os.Stderr, "", 0) - eng := NewTestEngine(t) - globalDaemon = mkDaemonFromEngine(eng, t) - - serverConfig := &apiserver.ServerConfig{Logging: true} - api := apiserver.New(serverConfig, eng) - // Spawn a Daemon - go func() { - logrus.Debugf("Spawning global daemon for integration tests") - listenURL := &url.URL{ - Scheme: testDaemonProto, - Host: testDaemonAddr, - } - - if err := api.ServeApi([]string{listenURL.String()}); err != nil { - logrus.Fatalf("Unable to spawn the test daemon: %s", err) - } - }() - - // Give some time to ListenAndServer to actually start - // FIXME: use inmem transports instead of tcp - time.Sleep(time.Second) - - api.AcceptConnections(getDaemon(eng)) -} - -// FIXME: test that ImagePull(json=true) send correct json output - -func GetTestImage(daemon *daemon.Daemon) *image.Image { - imgs, err := daemon.Graph().Map() - if err != nil { - logrus.Fatalf("Unable to get the test image: %s", err) - } - for _, image := range imgs { - if image.ID == unitTestImageID { - return image - } - } - logrus.Fatalf("Test image %v not found in %s: %s", unitTestImageID, daemon.Graph().Root, imgs) - return nil -} - -func TestDaemonCreate(t *testing.T) { - daemon := mkDaemon(t) - defer nuke(daemon) - - // Make sure we start we 0 containers - if len(daemon.List()) != 0 { - t.Errorf("Expected 0 containers, %v found", len(daemon.List())) - } - - container, _, err := daemon.Create(&runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("ls", "-al"), - }, - &runconfig.HostConfig{}, - "", - ) - if err != nil { - t.Fatal(err) - } - - defer func() { - if err := daemon.Rm(container); err != nil { - t.Error(err) - } - }() - - // Make sure we can find the newly created container with List() - if len(daemon.List()) != 1 { - t.Errorf("Expected 1 container, %v found", len(daemon.List())) - } - - // Make sure the container List() returns is the right one - if daemon.List()[0].ID != container.ID { - t.Errorf("Unexpected container %v returned by List", daemon.List()[0]) - } - - // Make sure we can get the container with Get() - if _, err := daemon.Get(container.ID); err != nil { - t.Errorf("Unable to get newly created container") - } - - // Make sure it is the right container - if c, _ := daemon.Get(container.ID); c != container { - t.Errorf("Get() returned the wrong container") - } - - // Make sure Exists returns it as existing - if !daemon.Exists(container.ID) { - t.Errorf("Exists() returned false for a newly created container") - } - - // Test that conflict error displays correct details - cmd := runconfig.NewCommand("ls", "-al") - testContainer, _, _ := daemon.Create( - &runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: cmd, - }, - &runconfig.HostConfig{}, - "conflictname", - ) - if _, _, err := daemon.Create(&runconfig.Config{Image: GetTestImage(daemon).ID, Cmd: cmd}, &runconfig.HostConfig{}, testContainer.Name); err == nil || !strings.Contains(err.Error(), stringid.TruncateID(testContainer.ID)) { - t.Fatalf("Name conflict error doesn't include the correct short id. Message was: %v", err) - } - - // Make sure create with bad parameters returns an error - if _, _, err = daemon.Create(&runconfig.Config{Image: GetTestImage(daemon).ID}, &runconfig.HostConfig{}, ""); err == nil { - t.Fatal("Builder.Create should throw an error when Cmd is missing") - } - - if _, _, err := daemon.Create( - &runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand(), - }, - &runconfig.HostConfig{}, - "", - ); err == nil { - t.Fatal("Builder.Create should throw an error when Cmd is empty") - } - - config := &runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("/bin/ls"), - PortSpecs: []string{"80"}, - } - container, _, err = daemon.Create(config, &runconfig.HostConfig{}, "") - - _, err = daemon.Commit(container, "testrepo", "testtag", "", "", true, config) - if err != nil { - t.Error(err) - } - - // test expose 80:8000 - container, warnings, err := daemon.Create(&runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("ls", "-al"), - PortSpecs: []string{"80:8000"}, - }, - &runconfig.HostConfig{}, - "", - ) - if err != nil { - t.Fatal(err) - } - if warnings == nil || len(warnings) != 1 { - t.Error("Expected a warning, got none") - } -} - -func TestDestroy(t *testing.T) { - daemon := mkDaemon(t) - defer nuke(daemon) - - container, _, err := daemon.Create(&runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("ls", "-al"), - }, - &runconfig.HostConfig{}, - "") - if err != nil { - t.Fatal(err) - } - // Destroy - if err := daemon.Rm(container); err != nil { - t.Error(err) - } - - // Make sure daemon.Exists() behaves correctly - if daemon.Exists("test_destroy") { - t.Errorf("Exists() returned true") - } - - // Make sure daemon.List() doesn't list the destroyed container - if len(daemon.List()) != 0 { - t.Errorf("Expected 0 container, %v found", len(daemon.List())) - } - - // Make sure daemon.Get() refuses to return the unexisting container - if c, _ := daemon.Get(container.ID); c != nil { - t.Errorf("Got a container that should not exist") - } - - // Test double destroy - if err := daemon.Rm(container); err == nil { - // It should have failed - t.Errorf("Double destroy did not fail") - } -} - -func TestGet(t *testing.T) { - daemon := mkDaemon(t) - defer nuke(daemon) - - container1, _, _ := mkContainer(daemon, []string{"_", "ls", "-al"}, t) - defer daemon.Rm(container1) - - container2, _, _ := mkContainer(daemon, []string{"_", "ls", "-al"}, t) - defer daemon.Rm(container2) - - container3, _, _ := mkContainer(daemon, []string{"_", "ls", "-al"}, t) - defer daemon.Rm(container3) - - if c, _ := daemon.Get(container1.ID); c != container1 { - t.Errorf("Get(test1) returned %v while expecting %v", c, container1) - } - - if c, _ := daemon.Get(container2.ID); c != container2 { - t.Errorf("Get(test2) returned %v while expecting %v", c, container2) - } - - if c, _ := daemon.Get(container3.ID); c != container3 { - t.Errorf("Get(test3) returned %v while expecting %v", c, container3) - } - -} - -func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daemon.Container, string) { - var ( - err error - id string - strPort string - eng = NewTestEngine(t) - daemon = mkDaemonFromEngine(eng, t) - port = 5554 - p nat.Port - ) - defer func() { - if err != nil { - daemon.Nuke() - } - }() - - for { - port += 1 - strPort = strconv.Itoa(port) - var cmd string - if proto == "tcp" { - cmd = "socat TCP-LISTEN:" + strPort + ",reuseaddr,fork EXEC:/bin/cat" - } else if proto == "udp" { - cmd = "socat UDP-RECVFROM:" + strPort + ",fork EXEC:/bin/cat" - } else { - t.Fatal(fmt.Errorf("Unknown protocol %v", proto)) - } - ep := make(map[nat.Port]struct{}, 1) - p = nat.Port(fmt.Sprintf("%s/%s", strPort, proto)) - ep[p] = struct{}{} - - c := &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("sh", "-c", cmd), - PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)}, - ExposedPorts: ep, - } - - id, _, err = daemon.ContainerCreate(unitTestImageID, c, &runconfig.HostConfig{}) - // FIXME: this relies on the undocumented behavior of daemon.Create - // which will return a nil error AND container if the exposed ports - // are invalid. That behavior should be fixed! - if id != "" { - break - } - t.Logf("Port %v already in use, trying another one", strPort) - - } - - if err := daemon.ContainerStart(id, &runconfig.HostConfig{}); err != nil { - t.Fatal(err) - } - - container, err := daemon.Get(id) - if err != nil { - t.Fatal(err) - } - - setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() { - for !container.IsRunning() { - time.Sleep(10 * time.Millisecond) - } - }) - - // Even if the state is running, lets give some time to lxc to spawn the process - container.WaitStop(500 * time.Millisecond) - - strPort = container.NetworkSettings.Ports[p][0].HostPort - return daemon, container, strPort -} - -// Run a container with a TCP port allocated, and test that it can receive connections on localhost -func TestAllocateTCPPortLocalhost(t *testing.T) { - daemon, container, port := startEchoServerContainer(t, "tcp") - defer nuke(daemon) - defer container.Kill() - - for i := 0; i != 10; i++ { - conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%v", port)) - if err != nil { - t.Fatal(err) - } - defer conn.Close() - - input := bytes.NewBufferString("well hello there\n") - _, err = conn.Write(input.Bytes()) - if err != nil { - t.Fatal(err) - } - buf := make([]byte, 16) - read := 0 - conn.SetReadDeadline(time.Now().Add(3 * time.Second)) - read, err = conn.Read(buf) - if err != nil { - if err, ok := err.(*net.OpError); ok { - if err.Err == syscall.ECONNRESET { - t.Logf("Connection reset by the proxy, socat is probably not listening yet, trying again in a sec") - conn.Close() - time.Sleep(time.Second) - continue - } - if err.Timeout() { - t.Log("Timeout, trying again") - conn.Close() - continue - } - } - t.Fatal(err) - } - output := string(buf[:read]) - if !strings.Contains(output, "well hello there") { - t.Fatal(fmt.Errorf("[%v] doesn't contain [well hello there]", output)) - } else { - return - } - } - - t.Fatal("No reply from the container") -} - -// Run a container with an UDP port allocated, and test that it can receive connections on localhost -func TestAllocateUDPPortLocalhost(t *testing.T) { - daemon, container, port := startEchoServerContainer(t, "udp") - defer nuke(daemon) - defer container.Kill() - - conn, err := net.Dial("udp", fmt.Sprintf("localhost:%v", port)) - if err != nil { - t.Fatal(err) - } - defer conn.Close() - - input := bytes.NewBufferString("well hello there\n") - buf := make([]byte, 16) - // Try for a minute, for some reason the select in socat may take ages - // to return even though everything on the path seems fine (i.e: the - // UDPProxy forwards the traffic correctly and you can see the packets - // on the interface from within the container). - for i := 0; i != 120; i++ { - _, err := conn.Write(input.Bytes()) - if err != nil { - t.Fatal(err) - } - conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond)) - read, err := conn.Read(buf) - if err == nil { - output := string(buf[:read]) - if strings.Contains(output, "well hello there") { - return - } - } - } - - t.Fatal("No reply from the container") -} - -func TestRestore(t *testing.T) { - eng := NewTestEngine(t) - daemon1 := mkDaemonFromEngine(eng, t) - defer daemon1.Nuke() - // Create a container with one instance of docker - container1, _, _ := mkContainer(daemon1, []string{"_", "ls", "-al"}, t) - defer daemon1.Rm(container1) - - // Create a second container meant to be killed - container2, _, _ := mkContainer(daemon1, []string{"-i", "_", "/bin/cat"}, t) - defer daemon1.Rm(container2) - - // Start the container non blocking - if err := container2.Start(); err != nil { - t.Fatal(err) - } - - if !container2.IsRunning() { - t.Fatalf("Container %v should appear as running but isn't", container2.ID) - } - - // Simulate a crash/manual quit of dockerd: process dies, states stays 'Running' - cStdin := container2.StdinPipe() - cStdin.Close() - if _, err := container2.WaitStop(2 * time.Second); err != nil { - t.Fatal(err) - } - container2.SetRunning(42) - container2.ToDisk() - - if len(daemon1.List()) != 2 { - t.Errorf("Expected 2 container, %v found", len(daemon1.List())) - } - if err := container1.Run(); err != nil { - t.Fatal(err) - } - - if !container2.IsRunning() { - t.Fatalf("Container %v should appear as running but isn't", container2.ID) - } - - // Here are are simulating a docker restart - that is, reloading all containers - // from scratch - eng = newTestEngine(t, false, daemon1.Config().Root) - daemon2 := mkDaemonFromEngine(eng, t) - if len(daemon2.List()) != 2 { - t.Errorf("Expected 2 container, %v found", len(daemon2.List())) - } - runningCount := 0 - for _, c := range daemon2.List() { - if c.IsRunning() { - t.Errorf("Running container found: %v (%v)", c.ID, c.Path) - runningCount++ - } - } - if runningCount != 0 { - t.Fatalf("Expected 0 container alive, %d found", runningCount) - } - container3, err := daemon2.Get(container1.ID) - if err != nil { - t.Fatal("Unable to Get container") - } - if err := container3.Run(); err != nil { - t.Fatal(err) - } - container2.SetStopped(&execdriver.ExitStatus{ExitCode: 0}) -} - -func TestDefaultContainerName(t *testing.T) { - eng := NewTestEngine(t) - daemon := mkDaemonFromEngine(eng, t) - defer nuke(daemon) - - config, _, _, err := parseRun([]string{unitTestImageID, "echo test"}) - if err != nil { - t.Fatal(err) - } - - container, err := daemon.Get(createNamedTestContainer(eng, config, t, "some_name")) - if err != nil { - t.Fatal(err) - } - containerID := container.ID - - if container.Name != "/some_name" { - t.Fatalf("Expect /some_name got %s", container.Name) - } - - c, err := daemon.Get("/some_name") - if err != nil { - t.Fatalf("Couldn't retrieve test container as /some_name") - } - if c.ID != containerID { - t.Fatalf("Container /some_name has ID %s instead of %s", c.ID, containerID) - } -} - -func TestRandomContainerName(t *testing.T) { - eng := NewTestEngine(t) - daemon := mkDaemonFromEngine(eng, t) - defer nuke(daemon) - - config, _, _, err := parseRun([]string{GetTestImage(daemon).ID, "echo test"}) - if err != nil { - t.Fatal(err) - } - - container, err := daemon.Get(createTestContainer(eng, config, t)) - if err != nil { - t.Fatal(err) - } - containerID := container.ID - - if container.Name == "" { - t.Fatalf("Expected not empty container name") - } - - if c, err := daemon.Get(container.Name); err != nil { - logrus.Fatalf("Could not lookup container %s by its name", container.Name) - } else if c.ID != containerID { - logrus.Fatalf("Looking up container name %s returned id %s instead of %s", container.Name, c.ID, containerID) - } -} - -func TestContainerNameValidation(t *testing.T) { - eng := NewTestEngine(t) - daemon := mkDaemonFromEngine(eng, t) - defer nuke(daemon) - - for _, test := range []struct { - Name string - Valid bool - }{ - {"abc-123_AAA.1", true}, - {"\000asdf", false}, - } { - config, _, _, err := parseRun([]string{unitTestImageID, "echo test"}) - if err != nil { - if !test.Valid { - continue - } - t.Fatal(err) - } - - containerId, _, err := daemon.ContainerCreate(test.Name, config, &runconfig.HostConfig{}) - if err != nil { - if !test.Valid { - continue - } - t.Fatal(err) - } - - container, err := daemon.Get(containerId) - if err != nil { - t.Fatal(err) - } - - if container.Name != "/"+test.Name { - t.Fatalf("Expect /%s got %s", test.Name, container.Name) - } - - if c, err := daemon.Get("/" + test.Name); err != nil { - t.Fatalf("Couldn't retrieve test container as /%s", test.Name) - } else if c.ID != container.ID { - t.Fatalf("Container /%s has ID %s instead of %s", test.Name, c.ID, container.ID) - } - } -} - -func TestLinkChildContainer(t *testing.T) { - eng := NewTestEngine(t) - daemon := mkDaemonFromEngine(eng, t) - defer nuke(daemon) - - config, _, _, err := parseRun([]string{unitTestImageID, "echo test"}) - if err != nil { - t.Fatal(err) - } - - container, err := daemon.Get(createNamedTestContainer(eng, config, t, "/webapp")) - if err != nil { - t.Fatal(err) - } - - webapp, err := daemon.GetByName("/webapp") - if err != nil { - t.Fatal(err) - } - - if webapp.ID != container.ID { - t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID) - } - - config, _, _, err = parseRun([]string{GetTestImage(daemon).ID, "echo test"}) - if err != nil { - t.Fatal(err) - } - - childContainer, err := daemon.Get(createTestContainer(eng, config, t)) - if err != nil { - t.Fatal(err) - } - - if err := daemon.RegisterLink(webapp, childContainer, "db"); err != nil { - t.Fatal(err) - } - - // Get the child by it's new name - db, err := daemon.GetByName("/webapp/db") - if err != nil { - t.Fatal(err) - } - if db.ID != childContainer.ID { - t.Fatalf("Expect db id to match container id: %s != %s", db.ID, childContainer.ID) - } -} - -func TestGetAllChildren(t *testing.T) { - eng := NewTestEngine(t) - daemon := mkDaemonFromEngine(eng, t) - defer nuke(daemon) - - config, _, _, err := parseRun([]string{unitTestImageID, "echo test"}) - if err != nil { - t.Fatal(err) - } - - container, err := daemon.Get(createNamedTestContainer(eng, config, t, "/webapp")) - if err != nil { - t.Fatal(err) - } - - webapp, err := daemon.GetByName("/webapp") - if err != nil { - t.Fatal(err) - } - - if webapp.ID != container.ID { - t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID) - } - - config, _, _, err = parseRun([]string{unitTestImageID, "echo test"}) - if err != nil { - t.Fatal(err) - } - - childContainer, err := daemon.Get(createTestContainer(eng, config, t)) - if err != nil { - t.Fatal(err) - } - - if err := daemon.RegisterLink(webapp, childContainer, "db"); err != nil { - t.Fatal(err) - } - - children, err := daemon.Children("/webapp") - if err != nil { - t.Fatal(err) - } - - if children == nil { - t.Fatal("Children should not be nil") - } - if len(children) == 0 { - t.Fatal("Children should not be empty") - } - - for key, value := range children { - if key != "/webapp/db" { - t.Fatalf("Expected /webapp/db got %s", key) - } - if value.ID != childContainer.ID { - t.Fatalf("Expected id %s got %s", childContainer.ID, value.ID) - } - } -} - -func TestDestroyWithInitLayer(t *testing.T) { - daemon := mkDaemon(t) - defer nuke(daemon) - - container, _, err := daemon.Create(&runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("ls", "-al"), - }, - &runconfig.HostConfig{}, - "") - - if err != nil { - t.Fatal(err) - } - // Destroy - if err := daemon.Rm(container); err != nil { - t.Fatal(err) - } - - // Make sure daemon.Exists() behaves correctly - if daemon.Exists("test_destroy") { - t.Fatalf("Exists() returned true") - } - - // Make sure daemon.List() doesn't list the destroyed container - if len(daemon.List()) != 0 { - t.Fatalf("Expected 0 container, %v found", len(daemon.List())) - } - - driver := daemon.Graph().Driver() - - // Make sure that the container does not exist in the driver - if _, err := driver.Get(container.ID, ""); err == nil { - t.Fatal("Container should not exist in the driver") - } - - // Make sure that the init layer is removed from the driver - if _, err := driver.Get(fmt.Sprintf("%s-init", container.ID), ""); err == nil { - t.Fatal("Container's init layer should not exist in the driver") - } -} diff --git a/integration/utils.go b/integration/utils.go deleted file mode 100644 index 62e02e9bb1..0000000000 --- a/integration/utils.go +++ /dev/null @@ -1,88 +0,0 @@ -package docker - -import ( - "bufio" - "fmt" - "io" - "strings" - "testing" - "time" - - "github.com/docker/docker/daemon" -) - -func closeWrap(args ...io.Closer) error { - e := false - ret := fmt.Errorf("Error closing elements") - for _, c := range args { - if err := c.Close(); err != nil { - e = true - ret = fmt.Errorf("%s\n%s", ret, err) - } - } - if e { - return ret - } - return nil -} - -func waitContainerStart(t *testing.T, timeout time.Duration) *daemon.Container { - var container *daemon.Container - - setTimeout(t, "Waiting for the container to be started timed out", timeout, func() { - for { - l := globalDaemon.List() - if len(l) == 1 && l[0].IsRunning() { - container = l[0] - break - } - time.Sleep(10 * time.Millisecond) - } - }) - - if container == nil { - t.Fatal("An error occurred while waiting for the container to start") - } - - return container -} - -func setTimeout(t *testing.T, msg string, d time.Duration, f func()) { - c := make(chan bool) - - // Make sure we are not too long - go func() { - time.Sleep(d) - c <- true - }() - go func() { - f() - c <- false - }() - if <-c && msg != "" { - t.Fatal(msg) - } -} - -func expectPipe(expected string, r io.Reader) error { - o, err := bufio.NewReader(r).ReadString('\n') - if err != nil { - return err - } - if strings.Trim(o, " \r\n") != expected { - return fmt.Errorf("Unexpected output. Expected [%s], received [%s]", expected, o) - } - return nil -} - -func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error { - for i := 0; i < count; i++ { - if _, err := w.Write([]byte(input)); err != nil { - return err - } - if err := expectPipe(output, r); err != nil { - return err - } - } - return nil -} diff --git a/integration/utils_test.go b/integration/utils_test.go deleted file mode 100644 index 9479d4296c..0000000000 --- a/integration/utils_test.go +++ /dev/null @@ -1,348 +0,0 @@ -package docker - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/http/httptest" - "os" - "path" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/daemon" - "github.com/docker/docker/daemon/networkdriver/bridge" - "github.com/docker/docker/engine" - "github.com/docker/docker/graph" - flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/registry" - "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" -) - -type Fataler interface { - Fatal(...interface{}) -} - -// This file contains utility functions for docker's unit test suite. -// It has to be named XXX_test.go, apparently, in other to access private functions -// from other XXX_test.go functions. - -// Create a temporary daemon suitable for unit testing. -// Call t.Fatal() at the first error. -func mkDaemon(f Fataler) *daemon.Daemon { - eng := newTestEngine(f, false, "") - return mkDaemonFromEngine(eng, f) -} - -func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler, name string) (shortId string) { - containerId, _, err := getDaemon(eng).ContainerCreate(name, config, &runconfig.HostConfig{}) - if err != nil { - f.Fatal(err) - } - return containerId -} - -func createTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler) (shortId string) { - return createNamedTestContainer(eng, config, f, "") -} - -func startContainer(eng *engine.Engine, id string, t Fataler) { - if err := getDaemon(eng).ContainerStart(id, &runconfig.HostConfig{}); err != nil { - t.Fatal(err) - } -} - -func containerRun(eng *engine.Engine, id string, t Fataler) { - startContainer(eng, id, t) - containerWait(eng, id, t) -} - -func containerFileExists(eng *engine.Engine, id, dir string, t Fataler) bool { - c := getContainer(eng, id, t) - if err := c.Mount(); err != nil { - t.Fatal(err) - } - defer c.Unmount() - if _, err := os.Stat(path.Join(c.RootfsPath(), dir)); err != nil { - if os.IsNotExist(err) { - return false - } - t.Fatal(err) - } - return true -} - -func containerAttach(eng *engine.Engine, id string, t Fataler) (io.WriteCloser, io.ReadCloser) { - c := getContainer(eng, id, t) - i := c.StdinPipe() - o := c.StdoutPipe() - return i, o -} - -func containerWait(eng *engine.Engine, id string, t Fataler) int { - ex, _ := getContainer(eng, id, t).WaitStop(-1 * time.Second) - return ex -} - -func containerWaitTimeout(eng *engine.Engine, id string, t Fataler) error { - _, err := getContainer(eng, id, t).WaitStop(500 * time.Millisecond) - return err -} - -func containerKill(eng *engine.Engine, id string, t Fataler) { - if err := getDaemon(eng).ContainerKill(id, 0); err != nil { - t.Fatal(err) - } -} - -func containerRunning(eng *engine.Engine, id string, t Fataler) bool { - return getContainer(eng, id, t).IsRunning() -} - -func containerAssertExists(eng *engine.Engine, id string, t Fataler) { - getContainer(eng, id, t) -} - -func containerAssertNotExists(eng *engine.Engine, id string, t Fataler) { - daemon := mkDaemonFromEngine(eng, t) - if c, _ := daemon.Get(id); c != nil { - t.Fatal(fmt.Errorf("Container %s should not exist", id)) - } -} - -// assertHttpNotError expect the given response to not have an error. -// Otherwise the it causes the test to fail. -func assertHttpNotError(r *httptest.ResponseRecorder, t Fataler) { - // Non-error http status are [200, 400) - if r.Code < http.StatusOK || r.Code >= http.StatusBadRequest { - t.Fatal(fmt.Errorf("Unexpected http error: %v", r.Code)) - } -} - -// assertHttpError expect the given response to have an error. -// Otherwise the it causes the test to fail. -func assertHttpError(r *httptest.ResponseRecorder, t Fataler) { - // Non-error http status are [200, 400) - if !(r.Code < http.StatusOK || r.Code >= http.StatusBadRequest) { - t.Fatal(fmt.Errorf("Unexpected http success code: %v", r.Code)) - } -} - -func getContainer(eng *engine.Engine, id string, t Fataler) *daemon.Container { - daemon := mkDaemonFromEngine(eng, t) - c, err := daemon.Get(id) - if err != nil { - t.Fatal(err) - } - return c -} - -func mkDaemonFromEngine(eng *engine.Engine, t Fataler) *daemon.Daemon { - iDaemon := eng.HackGetGlobalVar("httpapi.daemon") - if iDaemon == nil { - panic("Legacy daemon field not set in engine") - } - daemon, ok := iDaemon.(*daemon.Daemon) - if !ok { - panic("Legacy daemon field in engine does not cast to *daemon.Daemon") - } - return daemon -} - -func newTestEngine(t Fataler, autorestart bool, root string) *engine.Engine { - if root == "" { - if dir, err := newTestDirectory(unitTestStoreBase); err != nil { - t.Fatal(err) - } else { - root = dir - } - } - os.MkdirAll(root, 0700) - - eng := engine.New() - eng.Logging = false - - // (This is manually copied and modified from main() until we have a more generic plugin system) - cfg := &daemon.Config{ - Root: root, - AutoRestart: autorestart, - ExecDriver: "native", - // Either InterContainerCommunication or EnableIptables must be set, - // otherwise NewDaemon will fail because of conflicting settings. - Bridge: bridge.Config{ - InterContainerCommunication: true, - }, - TrustKeyPath: filepath.Join(root, "key.json"), - LogConfig: runconfig.LogConfig{Type: "json-file"}, - } - d, err := daemon.NewDaemon(cfg, eng, registry.NewService(nil)) - if err != nil { - t.Fatal(err) - } - if err := d.Install(eng); err != nil { - t.Fatal(err) - } - return eng -} - -func NewTestEngine(t Fataler) *engine.Engine { - return newTestEngine(t, false, "") -} - -func newTestDirectory(templateDir string) (dir string, err error) { - return utils.TestDirectory(templateDir) -} - -func getCallerName(depth int) string { - return utils.GetCallerName(depth) -} - -// Write `content` to the file at path `dst`, creating it if necessary, -// as well as any missing directories. -// The file is truncated if it already exists. -// Call t.Fatal() at the first error. -func writeFile(dst, content string, t *testing.T) { - // Create subdirectories if necessary - if err := os.MkdirAll(path.Dir(dst), 0700); err != nil && !os.IsExist(err) { - t.Fatal(err) - } - f, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700) - if err != nil { - t.Fatal(err) - } - // Write content (truncate if it exists) - if _, err := io.Copy(f, strings.NewReader(content)); err != nil { - t.Fatal(err) - } -} - -// Return the contents of file at path `src`. -// Call t.Fatal() at the first error (including if the file doesn't exist) -func readFile(src string, t *testing.T) (content string) { - f, err := os.Open(src) - if err != nil { - t.Fatal(err) - } - data, err := ioutil.ReadAll(f) - if err != nil { - t.Fatal(err) - } - return string(data) -} - -// Create a test container from the given daemon `r` and run arguments `args`. -// If the image name is "_", (eg. []string{"-i", "-t", "_", "bash"}, it is -// dynamically replaced by the current test image. -// The caller is responsible for destroying the container. -// Call t.Fatal() at the first error. -func mkContainer(r *daemon.Daemon, args []string, t *testing.T) (*daemon.Container, *runconfig.HostConfig, error) { - config, hc, _, err := parseRun(args) - defer func() { - if err != nil && t != nil { - t.Fatal(err) - } - }() - if err != nil { - return nil, nil, err - } - if config.Image == "_" { - config.Image = GetTestImage(r).ID - } - c, _, err := r.Create(config, nil, "") - if err != nil { - return nil, nil, err - } - // NOTE: hostConfig is ignored. - // If `args` specify privileged mode, custom lxc conf, external mount binds, - // port redirects etc. they will be ignored. - // This is because the correct way to set these things is to pass environment - // to the `start` job. - // FIXME: this helper function should be deprecated in favor of calling - // `create` and `start` jobs directly. - return c, hc, nil -} - -// Create a test container, start it, wait for it to complete, destroy it, -// and return its standard output as a string. -// The image name (eg. the XXX in []string{"-i", "-t", "XXX", "bash"}, is dynamically replaced by the current test image. -// If t is not nil, call t.Fatal() at the first error. Otherwise return errors normally. -func runContainer(eng *engine.Engine, r *daemon.Daemon, args []string, t *testing.T) (output string, err error) { - defer func() { - if err != nil && t != nil { - t.Fatal(err) - } - }() - container, hc, err := mkContainer(r, args, t) - if err != nil { - return "", err - } - defer r.Rm(container) - stdout := container.StdoutPipe() - defer stdout.Close() - - job := eng.Job("start", container.ID) - if err := job.ImportEnv(hc); err != nil { - return "", err - } - if err := job.Run(); err != nil { - return "", err - } - - container.WaitStop(-1 * time.Second) - data, err := ioutil.ReadAll(stdout) - if err != nil { - return "", err - } - output = string(data) - return -} - -// FIXME: this is duplicated from graph_test.go in the docker package. -func fakeTar() (io.ReadCloser, error) { - content := []byte("Hello world!\n") - buf := new(bytes.Buffer) - tw := tar.NewWriter(buf) - for _, name := range []string{"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres/postgres.conf"} { - hdr := new(tar.Header) - hdr.Size = int64(len(content)) - hdr.Name = name - if err := tw.WriteHeader(hdr); err != nil { - return nil, err - } - tw.Write([]byte(content)) - } - tw.Close() - return ioutil.NopCloser(buf), nil -} - -func getImages(eng *engine.Engine, t *testing.T, all bool, filter string) []*types.Image { - config := graph.ImagesConfig{ - Filter: filter, - All: all, - } - images, err := getDaemon(eng).Repositories().Images(&config) - if err != nil { - t.Fatal(err) - } - - return images -} - -func parseRun(args []string) (*runconfig.Config, *runconfig.HostConfig, *flag.FlagSet, error) { - cmd := flag.NewFlagSet("run", flag.ContinueOnError) - cmd.SetOutput(ioutil.Discard) - cmd.Usage = nil - return runconfig.Parse(cmd, args) -} - -func getDaemon(eng *engine.Engine) *daemon.Daemon { - return eng.HackGetGlobalVar("httpapi.daemon").(*daemon.Daemon) -} diff --git a/integration/z_final_test.go b/integration/z_final_test.go deleted file mode 100644 index d6ef2884f2..0000000000 --- a/integration/z_final_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package docker - -import ( - "runtime" - "testing" - - "github.com/docker/docker/pkg/fileutils" -) - -func displayFdGoroutines(t *testing.T) { - t.Logf("File Descriptors: %d, Goroutines: %d", fileutils.GetTotalUsedFds(), runtime.NumGoroutine()) -} - -func TestFinal(t *testing.T) { - nuke(globalDaemon) - t.Logf("Start File Descriptors: %d, Start Goroutines: %d", startFds, startGoroutines) - displayFdGoroutines(t) -} diff --git a/pkg/pidfile/pidfile.go b/pkg/pidfile/pidfile.go index 21a5438799..7cc6b964a7 100644 --- a/pkg/pidfile/pidfile.go +++ b/pkg/pidfile/pidfile.go @@ -24,15 +24,15 @@ func checkPidFileAlreadyExists(path string) error { return nil } -func New(path string) (file *PidFile, err error) { +func New(path string) (*PidFile, error) { if err := checkPidFileAlreadyExists(path); err != nil { return nil, err } + if err := ioutil.WriteFile(path, []byte(fmt.Sprintf("%d", os.Getpid())), 0644); err != nil { + return nil, err + } - file = &PidFile{path: path} - err = ioutil.WriteFile(path, []byte(fmt.Sprintf("%d", os.Getpid())), 0644) - - return file, err + return &PidFile{path: path}, nil } func (file PidFile) Remove() error {