Переглянути джерело

Engine: 'start' starts the specified container

Solomon Hykes 11 роки тому
батько
коміт
958b4a8757
9 змінених файлів з 184 додано та 58 видалено
  1. 7 13
      api.go
  2. 3 3
      api_test.go
  3. 6 4
      engine/engine.go
  4. 23 0
      engine/hack.go
  5. 59 1
      engine/job.go
  6. 2 2
      engine/utils.go
  7. 35 19
      server.go
  8. 36 16
      server_test.go
  9. 13 0
      utils_test.go

+ 7 - 13
api.go

@@ -639,26 +639,20 @@ func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.R
 }
 
 func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
-	var hostConfig *HostConfig
+	if vars == nil {
+		return fmt.Errorf("Missing parameter")
+	}
+	name := vars["name"]
+	job := srv.Eng.Job("start", name)
 	// allow a nil body for backwards compatibility
 	if r.Body != nil {
 		if matchesContentType(r.Header.Get("Content-Type"), "application/json") {
-			hostConfig = &HostConfig{}
-			if err := json.NewDecoder(r.Body).Decode(hostConfig); err != nil {
+			if err := job.DecodeEnv(r.Body); err != nil {
 				return err
 			}
 		}
 	}
-
-	if vars == nil {
-		return fmt.Errorf("Missing parameter")
-	}
-	name := vars["name"]
-	// Register any links from the host config before starting the container
-	if err := srv.RegisterLinks(name, hostConfig); err != nil {
-		return err
-	}
-	if err := srv.ContainerStart(name, hostConfig); err != nil {
+	if err := job.Run(); err != nil {
 		return err
 	}
 	w.WriteHeader(http.StatusNoContent)

+ 3 - 3
api_test.go

@@ -781,11 +781,11 @@ func TestPostContainersRestart(t *testing.T) {
 }
 
 func TestPostContainersStart(t *testing.T) {
-	runtime := mkRuntime(t)
+	eng := NewTestEngine(t)
+	srv := mkServerFromEngine(eng, t)
+	runtime := srv.runtime
 	defer nuke(runtime)
 
-	srv := &Server{runtime: runtime}
-
 	container, _, err := runtime.Create(
 		&Config{
 			Image:     GetTestImage(runtime).ID,

+ 6 - 4
engine/engine.go

@@ -13,10 +13,11 @@ type Handler func(*Job) string
 
 var globalHandlers map[string]Handler
 
+func init() {
+	globalHandlers = make(map[string]Handler)
+}
+
 func Register(name string, handler Handler) error {
-	if globalHandlers == nil {
-		globalHandlers = make(map[string]Handler)
-	}
 	globalHandlers[name] = handler
 	return nil
 }
@@ -27,6 +28,7 @@ func Register(name string, handler Handler) error {
 type Engine struct {
 	root		string
 	handlers	map[string]Handler
+	hack		Hack	// data for temporary hackery (see hack.go)
 }
 
 // New initializes a new engine managing the directory specified at `root`.
@@ -66,7 +68,7 @@ func New(root string) (*Engine, error) {
 // This function mimics `Command` from the standard os/exec package.
 func (eng *Engine) Job(name string, args ...string) *Job {
 	job := &Job{
-		eng:		eng,
+		Eng:		eng,
 		Name:		name,
 		Args:		args,
 		Stdin:		os.Stdin,

+ 23 - 0
engine/hack.go

@@ -0,0 +1,23 @@
+package engine
+
+
+type Hack map[string]interface{}
+
+
+func (eng *Engine) Hack_GetGlobalVar(key string) interface{} {
+	if eng.hack == nil {
+		return nil
+	}
+	val, exists := eng.hack[key]
+	if !exists {
+		return nil
+	}
+	return val
+}
+
+func (eng *Engine) Hack_SetGlobalVar(key string, val interface{}) {
+	if eng.hack == nil {
+		eng.hack = make(Hack)
+	}
+	eng.hack[key] = val
+}

+ 59 - 1
engine/job.go

@@ -1,6 +1,7 @@
 package engine
 
 import (
+	"bytes"
 	"io"
 	"strings"
 	"fmt"
@@ -22,7 +23,7 @@ import (
 // This allows for richer error reporting.
 // 
 type Job struct {
-	eng	*Engine
+	Eng	*Engine
 	Name	string
 	Args	[]string
 	env	[]string
@@ -111,3 +112,60 @@ func (job *Job) SetenvList(key string, value []string) error {
 func (job *Job) Setenv(key, value string) {
 	job.env = append(job.env, key + "=" + value)
 }
+
+// DecodeEnv decodes `src` as a json dictionary, and adds
+// each decoded key-value pair to the environment.
+//
+// If `text` cannot be decoded as a json dictionary, an error
+// is returned.
+func (job *Job) DecodeEnv(src io.Reader) error {
+	m := make(map[string]interface{})
+	if err := json.NewDecoder(src).Decode(&m); err != nil {
+		return err
+	}
+	for k, v := range m {
+		if sval, ok := v.(string); ok {
+			job.Setenv(k, sval)
+		} else	if val, err := json.Marshal(v); err == nil {
+			job.Setenv(k, string(val))
+		} else {
+			job.Setenv(k, fmt.Sprintf("%v", v))
+		}
+	}
+	return nil
+}
+
+func (job *Job) EncodeEnv(dst io.Writer) error {
+	return json.NewEncoder(dst).Encode(job.Environ())
+}
+
+func (job *Job) ExportEnv(dst interface{}) error {
+	var buf bytes.Buffer
+	if err := job.EncodeEnv(&buf); err != nil {
+		return err
+	}
+	if err := json.NewDecoder(&buf).Decode(dst); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (job *Job) ImportEnv(src interface{}) error {
+	var buf bytes.Buffer
+	if err := json.NewEncoder(&buf).Encode(src); err != nil {
+		return err
+	}
+	if err := job.DecodeEnv(&buf); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (job *Job) Environ() map[string]string {
+	m := make(map[string]string)
+	for _, kv := range job.env {
+		parts := strings.SplitN(kv, "=", 2)
+		m[parts[0]] = parts[1]
+	}
+	return m
+}

+ 2 - 2
engine/init_test.go → engine/utils.go

@@ -15,7 +15,7 @@ func init() {
 	Register("dummy", func(job *Job) string { return ""; })
 }
 
-func mkEngine(t *testing.T) *Engine {
+func NewTestEngine(t *testing.T) *Engine {
 	// Use the caller function name as a prefix.
 	// This helps trace temp directories back to their test.
 	pc, _, _, _ := runtime.Caller(1)
@@ -38,5 +38,5 @@ func mkEngine(t *testing.T) *Engine {
 }
 
 func mkJob(t *testing.T, name string, args ...string) *Job {
-	return mkEngine(t).Job(name, args...)
+	return NewTestEngine(t).Job(name, args...)
 }

+ 35 - 19
server.go

@@ -40,7 +40,7 @@ func init() {
 // Only one api server can run at the same time - this is enforced by a pidfile.
 // The signals SIGINT, SIGKILL and SIGTERM are intercepted for cleanup.
 func jobInitApi(job *engine.Job) string {
-	srv, err := NewServer(ConfigFromJob(job))
+	srv, err := NewServer(job.Eng, ConfigFromJob(job))
 	if err != nil {
 		return err.Error()
 	}
@@ -56,17 +56,19 @@ func jobInitApi(job *engine.Job) string {
 		srv.Close()
 		os.Exit(0)
 	}()
-	err = engine.Register("serveapi", func(job *engine.Job) string {
-		return srv.ListenAndServe(job.Args...).Error()
-	})
-	if err != nil {
+	job.Eng.Hack_SetGlobalVar("httpapi.server", srv)
+	if err := engine.Register("start", srv.ContainerStart); err != nil {
+		return err.Error()
+	}
+	if err := engine.Register("serveapi", srv.ListenAndServe); err != nil {
 		return err.Error()
 	}
 	return "0"
 }
 
 
-func (srv *Server) ListenAndServe(protoAddrs ...string) error {
+func (srv *Server) ListenAndServe(job *engine.Job) string {
+	protoAddrs := job.Args
 	chErrors := make(chan error, len(protoAddrs))
 	for _, protoAddr := range protoAddrs {
 		protoAddrParts := strings.SplitN(protoAddr, "://", 2)
@@ -80,7 +82,7 @@ func (srv *Server) ListenAndServe(protoAddrs ...string) error {
 				log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
 			}
 		default:
-			return fmt.Errorf("Invalid protocol format.")
+			return "Invalid protocol format."
 		}
 		go func() {
 			chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, true)
@@ -89,10 +91,10 @@ func (srv *Server) ListenAndServe(protoAddrs ...string) error {
 	for i := 0; i < len(protoAddrs); i += 1 {
 		err := <-chErrors
 		if err != nil {
-			return err
+			return err.Error()
 		}
 	}
-	return nil
+	return "0"
 }
 
 func (srv *Server) DockerVersion() APIVersion {
@@ -1282,8 +1284,7 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error {
 		return fmt.Errorf("No such container: %s", name)
 	}
 
-	// Register links
-	if hostConfig != nil && hostConfig.Links != nil {
+	if hostConfig.Links != nil {
 		for _, l := range hostConfig.Links {
 			parts, err := parseLink(l)
 			if err != nil {
@@ -1296,7 +1297,6 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error {
 			if child == nil {
 				return fmt.Errorf("Could not get container for %s", parts["name"])
 			}
-
 			if err := runtime.RegisterLink(container, child, parts["alias"]); err != nil {
 				return err
 			}
@@ -1312,22 +1312,36 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error {
 	return nil
 }
 
-func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error {
+func (srv *Server) ContainerStart(job *engine.Job) string {
+	if len(job.Args) < 1 {
+		return fmt.Sprintf("Usage: %s container_id", job.Name)
+	}
+	name := job.Args[0]
 	runtime := srv.runtime
 	container := runtime.Get(name)
 	if container == nil {
-		return fmt.Errorf("No such container: %s", name)
+		return fmt.Sprintf("No such container: %s", name)
 	}
-	if hostConfig != nil {
-		container.hostConfig = hostConfig
+	// If no environment was set, then no hostconfig was passed.
+	if len(job.Environ()) > 0 {
+		var hostConfig HostConfig
+		if err := job.ExportEnv(&hostConfig); err != nil {
+			return err.Error()
+		}
+		// Register any links from the host config before starting the container
+		// FIXME: we could just pass the container here, no need to lookup by name again.
+		if err := srv.RegisterLinks(name, &hostConfig); err != nil {
+			return err.Error()
+		}
+		container.hostConfig = &hostConfig
 		container.ToDisk()
 	}
 	if err := container.Start(); err != nil {
-		return fmt.Errorf("Cannot start container %s: %s", name, err)
+		return fmt.Sprintf("Cannot start container %s: %s", name, err)
 	}
 	srv.LogEvent("start", container.ShortID(), runtime.repositories.ImageName(container.Image))
 
-	return nil
+	return "0"
 }
 
 func (srv *Server) ContainerStop(name string, t int) error {
@@ -1478,12 +1492,13 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er
 
 }
 
-func NewServer(config *DaemonConfig) (*Server, error) {
+func NewServer(eng *engine.Engine, config *DaemonConfig) (*Server, error) {
 	runtime, err := NewRuntime(config)
 	if err != nil {
 		return nil, err
 	}
 	srv := &Server{
+		Eng:         eng,
 		runtime:     runtime,
 		pullingPool: make(map[string]struct{}),
 		pushingPool: make(map[string]struct{}),
@@ -1527,4 +1542,5 @@ type Server struct {
 	events      []utils.JSONMessage
 	listeners   map[string]chan utils.JSONMessage
 	reqFactory  *utils.HTTPRequestFactory
+	Eng         *engine.Engine
 }

+ 36 - 16
server_test.go

@@ -1,6 +1,7 @@
 package docker
 
 import (
+	"github.com/dotcloud/docker/engine"
 	"github.com/dotcloud/docker/utils"
 	"strings"
 	"testing"
@@ -109,10 +110,11 @@ func TestCreateRm(t *testing.T) {
 }
 
 func TestCreateRmVolumes(t *testing.T) {
-	runtime := mkRuntime(t)
-	defer nuke(runtime)
+	eng := engine.NewTestEngine(t)
 
-	srv := &Server{runtime: runtime}
+	srv := mkServerFromEngine(eng, t)
+	runtime := srv.runtime
+	defer nuke(runtime)
 
 	config, hostConfig, _, err := ParseRun([]string{"-v", "/srv", GetTestImage(runtime).ID, "echo test"}, nil)
 	if err != nil {
@@ -128,8 +130,11 @@ func TestCreateRmVolumes(t *testing.T) {
 		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
 	}
 
-	err = srv.ContainerStart(id, hostConfig)
-	if err != nil {
+	job := eng.Job("start", id)
+	if err := job.ImportEnv(hostConfig); err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
 		t.Fatal(err)
 	}
 
@@ -169,11 +174,11 @@ func TestCommit(t *testing.T) {
 }
 
 func TestCreateStartRestartStopStartKillRm(t *testing.T) {
-	runtime := mkRuntime(t)
+	eng := engine.NewTestEngine(t)
+	srv := mkServerFromEngine(eng, t)
+	runtime := srv.runtime
 	defer nuke(runtime)
 
-	srv := &Server{runtime: runtime}
-
 	config, hostConfig, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
 	if err != nil {
 		t.Fatal(err)
@@ -188,7 +193,11 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
 		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
 	}
 
-	if err := srv.ContainerStart(id, hostConfig); err != nil {
+	job := eng.Job("start", id)
+	if err := job.ImportEnv(hostConfig); err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
 		t.Fatal(err)
 	}
 
@@ -200,7 +209,11 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if err := srv.ContainerStart(id, hostConfig); err != nil {
+	job = eng.Job("start", id)
+	if err := job.ImportEnv(hostConfig); err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
 		t.Fatal(err)
 	}
 
@@ -384,9 +397,10 @@ func TestLogEvent(t *testing.T) {
 }
 
 func TestRmi(t *testing.T) {
-	runtime := mkRuntime(t)
+	eng := engine.NewTestEngine(t)
+	srv := mkServerFromEngine(eng, t)
+	runtime := srv.runtime
 	defer nuke(runtime)
-	srv := &Server{runtime: runtime}
 
 	initialImages, err := srv.Images(false, "")
 	if err != nil {
@@ -404,8 +418,11 @@ func TestRmi(t *testing.T) {
 	}
 
 	//To remove
-	err = srv.ContainerStart(containerID, hostConfig)
-	if err != nil {
+	job := eng.Job("start", containerID)
+	if err := job.ImportEnv(hostConfig); err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
 		t.Fatal(err)
 	}
 
@@ -425,8 +442,11 @@ func TestRmi(t *testing.T) {
 	}
 
 	//To remove
-	err = srv.ContainerStart(containerID, hostConfig)
-	if err != nil {
+	job = eng.Job("start", containerID)
+	if err := job.ImportEnv(hostConfig); err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
 		t.Fatal(err)
 	}
 

+ 13 - 0
utils_test.go

@@ -2,6 +2,7 @@ package docker
 
 import (
 	"fmt"
+	"github.com/dotcloud/docker/engine"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io/ioutil"
@@ -40,6 +41,18 @@ func mkRuntime(f Fataler) *Runtime {
 	return runtime
 }
 
+func mkServerFromEngine(eng *engine.Engine, t Fataler) *Server {
+	iSrv := eng.Hack_GetGlobalVar("httpapi.server")
+	if iSrv == nil {
+		t.Fatal("Legacy server field not set in engine")
+	}
+	srv, ok := iSrv.(*Server)
+	if !ok {
+		t.Fatal("Legacy server field in engine does not cast to *Server")
+	}
+	return srv
+}
+
 // A common interface to access the Fatal method of
 // both testing.B and testing.T.
 type Fataler interface {