Browse code

Merge branch 'job-create-start-clean' into engine-patch-2

Conflicts:
engine/engine.go
engine/job.go
server.go
utils_test.go

Solomon Hykes authored on 2013/11/13 09:36:20
Showing 15 changed files
... ...
@@ -541,43 +541,36 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
541 541
 	if err := parseForm(r); err != nil {
542 542
 		return nil
543 543
 	}
544
-	config := &Config{}
545 544
 	out := &APIRun{}
546
-	name := r.Form.Get("name")
547
-
548
-	if err := json.NewDecoder(r.Body).Decode(config); err != nil {
545
+	job := srv.Eng.Job("create", r.Form.Get("name"))
546
+	if err := job.DecodeEnv(r.Body); err != nil {
549 547
 		return err
550 548
 	}
551
-
552 549
 	resolvConf, err := utils.GetResolvConf()
553 550
 	if err != nil {
554 551
 		return err
555 552
 	}
556
-
557
-	if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
553
+	if !job.GetenvBool("NetworkDisabled") && len(job.Getenv("Dns")) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
558 554
 		out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns))
559
-		config.Dns = defaultDns
555
+		job.SetenvList("Dns", defaultDns)
560 556
 	}
561
-
562
-	id, warnings, err := srv.ContainerCreate(config, name)
563
-	if err != nil {
557
+	// Read container ID from the first line of stdout
558
+	job.StdoutParseString(&out.ID)
559
+	// Read warnings from stderr
560
+	job.StderrParseLines(&out.Warnings, 0)
561
+	if err := job.Run(); err != nil {
564 562
 		return err
565 563
 	}
566
-	out.ID = id
567
-	for _, warning := range warnings {
568
-		out.Warnings = append(out.Warnings, warning)
569
-	}
570
-
571
-	if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
564
+	if job.GetenvInt("Memory") > 0 && !srv.runtime.capabilities.MemoryLimit {
572 565
 		log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
573 566
 		out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.")
574 567
 	}
575
-	if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit {
568
+	if job.GetenvInt("Memory") > 0 && !srv.runtime.capabilities.SwapLimit {
576 569
 		log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.")
577 570
 		out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.")
578 571
 	}
579 572
 
580
-	if !config.NetworkDisabled && srv.runtime.capabilities.IPv4ForwardingDisabled {
573
+	if !job.GetenvBool("NetworkDisabled") && srv.runtime.capabilities.IPv4ForwardingDisabled {
581 574
 		log.Println("Warning: IPv4 forwarding is disabled.")
582 575
 		out.Warnings = append(out.Warnings, "IPv4 forwarding is disabled.")
583 576
 	}
... ...
@@ -654,26 +647,23 @@ func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.R
654 654
 }
655 655
 
656 656
 func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
657
-	var hostConfig *HostConfig
657
+	if vars == nil {
658
+		return fmt.Errorf("Missing parameter")
659
+	}
660
+	name := vars["name"]
661
+	job := srv.Eng.Job("start", name)
662
+	if err := job.ImportEnv(HostConfig{}); err != nil {
663
+		return fmt.Errorf("Couldn't initialize host configuration")
664
+	}
658 665
 	// allow a nil body for backwards compatibility
659 666
 	if r.Body != nil {
660 667
 		if matchesContentType(r.Header.Get("Content-Type"), "application/json") {
661
-			hostConfig = &HostConfig{}
662
-			if err := json.NewDecoder(r.Body).Decode(hostConfig); err != nil {
668
+			if err := job.DecodeEnv(r.Body); err != nil {
663 669
 				return err
664 670
 			}
665 671
 		}
666 672
 	}
667
-
668
-	if vars == nil {
669
-		return fmt.Errorf("Missing parameter")
670
-	}
671
-	name := vars["name"]
672
-	// Register any links from the host config before starting the container
673
-	if err := srv.RegisterLinks(name, hostConfig); err != nil {
674
-		return err
675
-	}
676
-	if err := srv.ContainerStart(name, hostConfig); err != nil {
673
+	if err := job.Run(); err != nil {
677 674
 		return err
678 675
 	}
679 676
 	w.WriteHeader(http.StatusNoContent)
... ...
@@ -609,11 +609,11 @@ func TestPostCommit(t *testing.T) {
609 609
 }
610 610
 
611 611
 func TestPostContainersCreate(t *testing.T) {
612
-	runtime := mkRuntime(t)
612
+	eng := NewTestEngine(t)
613
+	srv := mkServerFromEngine(eng, t)
614
+	runtime := srv.runtime
613 615
 	defer nuke(runtime)
614 616
 
615
-	srv := &Server{runtime: runtime}
616
-
617 617
 	configJSON, err := json.Marshal(&Config{
618 618
 		Image:  GetTestImage(runtime).ID,
619 619
 		Memory: 33554432,
... ...
@@ -756,27 +756,23 @@ func TestPostContainersRestart(t *testing.T) {
756 756
 }
757 757
 
758 758
 func TestPostContainersStart(t *testing.T) {
759
-	runtime := mkRuntime(t)
759
+	eng := NewTestEngine(t)
760
+	srv := mkServerFromEngine(eng, t)
761
+	runtime := srv.runtime
760 762
 	defer nuke(runtime)
761 763
 
762
-	srv := &Server{runtime: runtime}
763
-
764
-	container, _, err := runtime.Create(
764
+	id := createTestContainer(
765
+		eng,
765 766
 		&Config{
766 767
 			Image:     GetTestImage(runtime).ID,
767 768
 			Cmd:       []string{"/bin/cat"},
768 769
 			OpenStdin: true,
769 770
 		},
770
-		"",
771
-	)
772
-	if err != nil {
773
-		t.Fatal(err)
774
-	}
775
-	defer runtime.Destroy(container)
771
+		t)
776 772
 
777 773
 	hostConfigJSON, err := json.Marshal(&HostConfig{})
778 774
 
779
-	req, err := http.NewRequest("POST", "/containers/"+container.ID+"/start", bytes.NewReader(hostConfigJSON))
775
+	req, err := http.NewRequest("POST", "/containers/"+id+"/start", bytes.NewReader(hostConfigJSON))
780 776
 	if err != nil {
781 777
 		t.Fatal(err)
782 778
 	}
... ...
@@ -784,22 +780,26 @@ func TestPostContainersStart(t *testing.T) {
784 784
 	req.Header.Set("Content-Type", "application/json")
785 785
 
786 786
 	r := httptest.NewRecorder()
787
-	if err := postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
787
+	if err := postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": id}); err != nil {
788 788
 		t.Fatal(err)
789 789
 	}
790 790
 	if r.Code != http.StatusNoContent {
791 791
 		t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
792 792
 	}
793 793
 
794
+	container := runtime.Get(id)
795
+	if container == nil {
796
+		t.Fatalf("Container %s was not created", id)
797
+	}
794 798
 	// Give some time to the process to start
799
+	// FIXME: use Wait once it's available as a job
795 800
 	container.WaitTimeout(500 * time.Millisecond)
796
-
797 801
 	if !container.State.Running {
798 802
 		t.Errorf("Container should be running")
799 803
 	}
800 804
 
801 805
 	r = httptest.NewRecorder()
802
-	if err = postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err == nil {
806
+	if err = postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": id}); err == nil {
803 807
 		t.Fatalf("A running container should be able to be started")
804 808
 	}
805 809
 
... ...
@@ -544,10 +544,7 @@ func TestBuildADDFileNotFound(t *testing.T) {
544 544
 }
545 545
 
546 546
 func TestBuildInheritance(t *testing.T) {
547
-	runtime, err := newTestRuntime("")
548
-	if err != nil {
549
-		t.Fatal(err)
550
-	}
547
+	runtime := mkRuntime(t)
551 548
 	defer nuke(runtime)
552 549
 
553 550
 	srv := &Server{
... ...
@@ -9,7 +9,6 @@ import (
9 9
 type DaemonConfig struct {
10 10
 	Pidfile                     string
11 11
 	Root                        string
12
-	ProtoAddresses              []string
13 12
 	AutoRestart                 bool
14 13
 	EnableCors                  bool
15 14
 	Dns                         []string
... ...
@@ -36,7 +35,6 @@ func ConfigFromJob(job *engine.Job) *DaemonConfig {
36 36
 	} else {
37 37
 		config.BridgeIface = DefaultNetworkBridge
38 38
 	}
39
-	config.ProtoAddresses = job.GetenvList("ProtoAddresses")
40 39
 	config.DefaultIp = net.ParseIP(job.Getenv("DefaultIp"))
41 40
 	config.InterContainerCommunication = job.GetenvBool("InterContainerCommunication")
42 41
 	return &config
... ...
@@ -71,7 +71,8 @@ func main() {
71 71
 		if err != nil {
72 72
 			log.Fatal(err)
73 73
 		}
74
-		job := eng.Job("serveapi")
74
+		// Load plugin: httpapi
75
+		job := eng.Job("initapi")
75 76
 		job.Setenv("Pidfile", *pidfile)
76 77
 		job.Setenv("Root", *flRoot)
77 78
 		job.SetenvBool("AutoRestart", *flAutoRestart)
... ...
@@ -79,12 +80,17 @@ func main() {
79 79
 		job.Setenv("Dns", *flDns)
80 80
 		job.SetenvBool("EnableIptables", *flEnableIptables)
81 81
 		job.Setenv("BridgeIface", *bridgeName)
82
-		job.SetenvList("ProtoAddresses", flHosts)
83 82
 		job.Setenv("DefaultIp", *flDefaultIp)
84 83
 		job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
85 84
 		if err := job.Run(); err != nil {
86 85
 			log.Fatal(err)
87 86
 		}
87
+		// Serve api
88
+		job = eng.Job("serveapi", flHosts...)
89
+		job.SetenvBool("Logging", true)
90
+		if err := job.Run(); err != nil {
91
+			log.Fatal(err)
92
+		}
88 93
 	} else {
89 94
 		if len(flHosts) > 1 {
90 95
 			log.Fatal("Please specify only one -H")
... ...
@@ -6,15 +6,21 @@ import (
6 6
 	"log"
7 7
 	"os"
8 8
 	"runtime"
9
+	"strings"
9 10
 )
10 11
 
11 12
 type Handler func(*Job) string
12 13
 
13 14
 var globalHandlers map[string]Handler
14 15
 
16
+func init() {
17
+	globalHandlers = make(map[string]Handler)
18
+}
19
+
15 20
 func Register(name string, handler Handler) error {
16
-	if globalHandlers == nil {
17
-		globalHandlers = make(map[string]Handler)
21
+	_, exists := globalHandlers[name]
22
+	if exists {
23
+		return fmt.Errorf("Can't overwrite global handler for command %s", name)
18 24
 	}
19 25
 	globalHandlers[name] = handler
20 26
 	return nil
... ...
@@ -24,10 +30,27 @@ func Register(name string, handler Handler) error {
24 24
 // It acts as a store for *containers*, and allows manipulation of these
25 25
 // containers by executing *jobs*.
26 26
 type Engine struct {
27
-	root     string
28
-	handlers map[string]Handler
27
+	root		string
28
+	handlers	map[string]Handler
29
+	hack		Hack	// data for temporary hackery (see hack.go)
30
+	id		string
31
+}
32
+
33
+func (eng *Engine) Root() string {
34
+	return eng.root
35
+}
36
+
37
+func (eng *Engine) Register(name string, handler Handler) error {
38
+	eng.Logf("Register(%s) (handlers=%v)", name, eng.handlers)
39
+	_, exists := eng.handlers[name]
40
+	if exists {
41
+		return fmt.Errorf("Can't overwrite handler for command %s", name)
42
+	}
43
+	eng.handlers[name] = handler
44
+	return nil
29 45
 }
30 46
 
47
+
31 48
 // New initializes a new engine managing the directory specified at `root`.
32 49
 // `root` is used to store containers and any other state private to the engine.
33 50
 // Changing the contents of the root without executing a job will cause unspecified
... ...
@@ -55,22 +78,31 @@ func New(root string) (*Engine, error) {
55 55
 		return nil, err
56 56
 	}
57 57
 	eng := &Engine{
58
-		root:     root,
59
-		handlers: globalHandlers,
58
+		root:		root,
59
+		handlers:	make(map[string]Handler),
60
+		id:		utils.RandomString(),
61
+	}
62
+	// Copy existing global handlers
63
+	for k, v := range globalHandlers {
64
+		eng.handlers[k] = v
60 65
 	}
61 66
 	return eng, nil
62 67
 }
63 68
 
69
+func (eng *Engine) String() string {
70
+	return fmt.Sprintf("%s|%s", eng.Root(), eng.id[:8])
71
+}
72
+
64 73
 // Job creates a new job which can later be executed.
65 74
 // This function mimics `Command` from the standard os/exec package.
66 75
 func (eng *Engine) Job(name string, args ...string) *Job {
67 76
 	job := &Job{
68
-		eng:    eng,
69
-		Name:   name,
70
-		Args:   args,
71
-		Stdin:  os.Stdin,
72
-		Stdout: os.Stdout,
73
-		Stderr: os.Stderr,
77
+		Eng:		eng,
78
+		Name:		name,
79
+		Args:		args,
80
+		Stdin:		os.Stdin,
81
+		Stdout:		os.Stdout,
82
+		Stderr:		os.Stderr,
74 83
 	}
75 84
 	handler, exists := eng.handlers[name]
76 85
 	if exists {
... ...
@@ -78,3 +110,8 @@ func (eng *Engine) Job(name string, args ...string) *Job {
78 78
 	}
79 79
 	return job
80 80
 }
81
+
82
+func (eng *Engine) Logf(format string, args ...interface{}) (n int, err error) {
83
+	prefixedFormat := fmt.Sprintf("[%s] %s\n", eng, strings.TrimRight(format, "\n"))
84
+	return fmt.Fprintf(os.Stderr, prefixedFormat, args...)
85
+}
81 86
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+package engine
1
+
2
+
3
+type Hack map[string]interface{}
4
+
5
+
6
+func (eng *Engine) Hack_GetGlobalVar(key string) interface{} {
7
+	if eng.hack == nil {
8
+		return nil
9
+	}
10
+	val, exists := eng.hack[key]
11
+	if !exists {
12
+		return nil
13
+	}
14
+	return val
15
+}
16
+
17
+func (eng *Engine) Hack_SetGlobalVar(key string, val interface{}) {
18
+	if eng.hack == nil {
19
+		eng.hack = make(Hack)
20
+	}
21
+	eng.hack[key] = val
22
+}
0 23
deleted file mode 100644
... ...
@@ -1,42 +0,0 @@
1
-package engine
2
-
3
-import (
4
-	"fmt"
5
-	"github.com/dotcloud/docker/utils"
6
-	"io/ioutil"
7
-	"runtime"
8
-	"strings"
9
-	"testing"
10
-)
11
-
12
-var globalTestID string
13
-
14
-func init() {
15
-	Register("dummy", func(job *Job) string { return "" })
16
-}
17
-
18
-func mkEngine(t *testing.T) *Engine {
19
-	// Use the caller function name as a prefix.
20
-	// This helps trace temp directories back to their test.
21
-	pc, _, _, _ := runtime.Caller(1)
22
-	callerLongName := runtime.FuncForPC(pc).Name()
23
-	parts := strings.Split(callerLongName, ".")
24
-	callerShortName := parts[len(parts)-1]
25
-	if globalTestID == "" {
26
-		globalTestID = utils.RandomString()[:4]
27
-	}
28
-	prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, callerShortName)
29
-	root, err := ioutil.TempDir("", prefix)
30
-	if err != nil {
31
-		t.Fatal(err)
32
-	}
33
-	eng, err := New(root)
34
-	if err != nil {
35
-		t.Fatal(err)
36
-	}
37
-	return eng
38
-}
39
-
40
-func mkJob(t *testing.T, name string, args ...string) *Job {
41
-	return mkEngine(t).Job(name, args...)
42
-}
... ...
@@ -1,11 +1,16 @@
1 1
 package engine
2 2
 
3 3
 import (
4
-	"encoding/json"
5
-	"fmt"
6
-	"github.com/dotcloud/docker/utils"
4
+	"bufio"
5
+	"bytes"
7 6
 	"io"
7
+	"io/ioutil"
8
+	"strconv"
8 9
 	"strings"
10
+	"fmt"
11
+	"sync"
12
+	"encoding/json"
13
+	"os"
9 14
 )
10 15
 
11 16
 // A job is the fundamental unit of work in the docker engine.
... ...
@@ -22,24 +27,43 @@ import (
22 22
 // This allows for richer error reporting.
23 23
 //
24 24
 type Job struct {
25
-	eng     *Engine
26
-	Name    string
27
-	Args    []string
28
-	env     []string
29
-	Stdin   io.ReadCloser
30
-	Stdout  io.WriteCloser
31
-	Stderr  io.WriteCloser
32
-	handler func(*Job) string
33
-	status  string
25
+	Eng	*Engine
26
+	Name	string
27
+	Args	[]string
28
+	env	[]string
29
+	Stdin	io.Reader
30
+	Stdout	io.Writer
31
+	Stderr	io.Writer
32
+	handler	func(*Job) string
33
+	status	string
34
+	onExit	[]func()
34 35
 }
35 36
 
36 37
 // Run executes the job and blocks until the job completes.
37 38
 // If the job returns a failure status, an error is returned
38 39
 // which includes the status.
39 40
 func (job *Job) Run() error {
40
-	randId := utils.RandomString()[:4]
41
-	fmt.Printf("Job #%s: %s\n", randId, job)
42
-	defer fmt.Printf("Job #%s: %s = '%s'", randId, job, job.status)
41
+	defer func() {
42
+		var wg sync.WaitGroup
43
+		for _, f := range job.onExit {
44
+			wg.Add(1)
45
+			go func(f func()) {
46
+				f()
47
+				wg.Done()
48
+			}(f)
49
+		}
50
+		wg.Wait()
51
+	}()
52
+	if job.Stdout != nil && job.Stdout != os.Stdout {
53
+		job.Stdout = io.MultiWriter(job.Stdout, os.Stdout)
54
+	}
55
+	if job.Stderr != nil && job.Stderr != os.Stderr {
56
+		job.Stderr = io.MultiWriter(job.Stderr, os.Stderr)
57
+	}
58
+	job.Eng.Logf("+job %s", job.CallString())
59
+	defer func() {
60
+		job.Eng.Logf("-job %s%s", job.CallString(), job.StatusString())
61
+	}()
43 62
 	if job.handler == nil {
44 63
 		job.status = "command not found"
45 64
 	} else {
... ...
@@ -51,9 +75,84 @@ func (job *Job) Run() error {
51 51
 	return nil
52 52
 }
53 53
 
54
+func (job *Job) StdoutParseLines(dst *[]string, limit int) {
55
+	job.parseLines(job.StdoutPipe(), dst, limit)
56
+}
57
+
58
+func (job *Job) StderrParseLines(dst *[]string, limit int) {
59
+	job.parseLines(job.StderrPipe(), dst, limit)
60
+}
61
+
62
+func (job *Job) parseLines(src io.Reader, dst *[]string, limit int) {
63
+	var wg sync.WaitGroup
64
+	wg.Add(1)
65
+	go func() {
66
+		defer wg.Done()
67
+		scanner := bufio.NewScanner(src)
68
+		for scanner.Scan() {
69
+			// If the limit is reached, flush the rest of the source and return
70
+			if limit > 0 && len(*dst) >= limit {
71
+				io.Copy(ioutil.Discard, src)
72
+				return
73
+			}
74
+			line := scanner.Text()
75
+			// Append the line (with delimitor removed)
76
+			*dst = append(*dst, line)
77
+		}
78
+	}()
79
+	job.onExit = append(job.onExit, wg.Wait)
80
+}
81
+
82
+func (job *Job) StdoutParseString(dst *string) {
83
+	lines := make([]string, 0, 1)
84
+	job.StdoutParseLines(&lines, 1)
85
+	job.onExit = append(job.onExit, func() { if len(lines) >= 1 { *dst = lines[0] }})
86
+}
87
+
88
+func (job *Job) StderrParseString(dst *string) {
89
+	lines := make([]string, 0, 1)
90
+	job.StderrParseLines(&lines, 1)
91
+	job.onExit = append(job.onExit, func() { *dst = lines[0]; })
92
+}
93
+
94
+func (job *Job) StdoutPipe() io.ReadCloser {
95
+	r, w := io.Pipe()
96
+	job.Stdout = w
97
+	job.onExit = append(job.onExit, func(){ w.Close() })
98
+	return r
99
+}
100
+
101
+func (job *Job) StderrPipe() io.ReadCloser {
102
+	r, w := io.Pipe()
103
+	job.Stderr = w
104
+	job.onExit = append(job.onExit, func(){ w.Close() })
105
+	return r
106
+}
107
+
108
+
109
+func (job *Job) CallString() string {
110
+	return fmt.Sprintf("%s(%s)", job.Name, strings.Join(job.Args, ", "))
111
+}
112
+
113
+func (job *Job) StatusString() string {
114
+	// FIXME: if a job returns the empty string, it will be printed
115
+	// as not having returned.
116
+	// (this only affects String which is a convenience function).
117
+	if job.status != "" {
118
+		var okerr string
119
+		if job.status == "0" {
120
+			okerr = "OK"
121
+		} else {
122
+			okerr = "ERR"
123
+		}
124
+		return fmt.Sprintf(" = %s (%s)", okerr, job.status)
125
+	}
126
+	return ""
127
+}
128
+
54 129
 // String returns a human-readable description of `job`
55 130
 func (job *Job) String() string {
56
-	return strings.Join(append([]string{job.Name}, job.Args...), " ")
131
+	return fmt.Sprintf("%s.%s%s", job.Eng, job.CallString(), job.StatusString())
57 132
 }
58 133
 
59 134
 func (job *Job) Getenv(key string) (value string) {
... ...
@@ -90,6 +189,19 @@ func (job *Job) SetenvBool(key string, value bool) {
90 90
 	}
91 91
 }
92 92
 
93
+func (job *Job) GetenvInt(key string) int64 {
94
+	s := strings.Trim(job.Getenv(key), " \t")
95
+	val, err := strconv.ParseInt(s, 10, 64)
96
+	if err != nil {
97
+		return -1
98
+	}
99
+	return val
100
+}
101
+
102
+func (job *Job) SetenvInt(key string, value int64) {
103
+	job.Setenv(key, fmt.Sprintf("%d", value))
104
+}
105
+
93 106
 func (job *Job) GetenvList(key string) []string {
94 107
 	sval := job.Getenv(key)
95 108
 	l := make([]string, 0, 1)
... ...
@@ -111,3 +223,109 @@ func (job *Job) SetenvList(key string, value []string) error {
111 111
 func (job *Job) Setenv(key, value string) {
112 112
 	job.env = append(job.env, key+"="+value)
113 113
 }
114
+
115
+// DecodeEnv decodes `src` as a json dictionary, and adds
116
+// each decoded key-value pair to the environment.
117
+//
118
+// If `text` cannot be decoded as a json dictionary, an error
119
+// is returned.
120
+func (job *Job) DecodeEnv(src io.Reader) error {
121
+	m := make(map[string]interface{})
122
+	if err := json.NewDecoder(src).Decode(&m); err != nil {
123
+		return err
124
+	}
125
+	for k, v := range m {
126
+		// FIXME: we fix-convert float values to int, because
127
+		// encoding/json decodes integers to float64, but cannot encode them back.
128
+		// (See http://golang.org/src/pkg/encoding/json/decode.go#L46)
129
+		if fval, ok := v.(float64); ok {
130
+			job.SetenvInt(k, int64(fval))
131
+		} else if sval, ok := v.(string); ok {
132
+			job.Setenv(k, sval)
133
+		} else	if val, err := json.Marshal(v); err == nil {
134
+			job.Setenv(k, string(val))
135
+		} else {
136
+			job.Setenv(k, fmt.Sprintf("%v", v))
137
+		}
138
+	}
139
+	return nil
140
+}
141
+
142
+func (job *Job) EncodeEnv(dst io.Writer) error {
143
+	m := make(map[string]interface{})
144
+	for k, v := range job.Environ() {
145
+		var val interface{}
146
+		if err := json.Unmarshal([]byte(v), &val); err == nil {
147
+			// FIXME: we fix-convert float values to int, because
148
+			// encoding/json decodes integers to float64, but cannot encode them back.
149
+			// (See http://golang.org/src/pkg/encoding/json/decode.go#L46)
150
+			if fval, isFloat := val.(float64); isFloat {
151
+				val = int(fval)
152
+			}
153
+			m[k] = val
154
+		} else {
155
+			m[k] = v
156
+		}
157
+	}
158
+	if err := json.NewEncoder(dst).Encode(&m); err != nil {
159
+		return err
160
+	}
161
+	return nil
162
+}
163
+
164
+func (job *Job) ExportEnv(dst interface{}) (err error) {
165
+	defer func() {
166
+		if err != nil {
167
+			err = fmt.Errorf("ExportEnv %s", err)
168
+		}
169
+	}()
170
+	var buf bytes.Buffer
171
+	// step 1: encode/marshal the env to an intermediary json representation
172
+	if err := job.EncodeEnv(&buf); err != nil {
173
+		return err
174
+	}
175
+	// step 2: decode/unmarshal the intermediary json into the destination object
176
+	if err := json.NewDecoder(&buf).Decode(dst); err != nil {
177
+		return err
178
+	}
179
+	return nil
180
+}
181
+
182
+func (job *Job) ImportEnv(src interface{}) (err error) {
183
+	defer func() {
184
+		if err != nil {
185
+			err = fmt.Errorf("ImportEnv: %s", err)
186
+		}
187
+	}()
188
+	var buf bytes.Buffer
189
+	if err := json.NewEncoder(&buf).Encode(src); err != nil {
190
+		return err
191
+	}
192
+	if err := job.DecodeEnv(&buf); err != nil {
193
+		return err
194
+	}
195
+	return nil
196
+}
197
+
198
+func (job *Job) Environ() map[string]string {
199
+	m := make(map[string]string)
200
+	for _, kv := range job.env {
201
+		parts := strings.SplitN(kv, "=", 2)
202
+		m[parts[0]] = parts[1]
203
+	}
204
+	return m
205
+}
206
+
207
+func (job *Job) Logf(format string, args ...interface{}) (n int, err error) {
208
+	prefixedFormat := fmt.Sprintf("[%s] %s\n", job, strings.TrimRight(format, "\n"))
209
+	return fmt.Fprintf(job.Stderr, prefixedFormat, args...)
210
+}
211
+
212
+func (job *Job) Printf(format string, args ...interface{}) (n int, err error) {
213
+	return fmt.Fprintf(job.Stdout, format, args...)
214
+}
215
+
216
+func (job *Job) Errorf(format string, args ...interface{}) (n int, err error) {
217
+	return fmt.Fprintf(job.Stderr, format, args...)
218
+
219
+}
114 220
new file mode 100644
... ...
@@ -0,0 +1,42 @@
0
+package engine
1
+
2
+import (
3
+	"fmt"
4
+	"github.com/dotcloud/docker/utils"
5
+	"io/ioutil"
6
+	"runtime"
7
+	"strings"
8
+	"testing"
9
+)
10
+
11
+var globalTestID string
12
+
13
+func init() {
14
+	Register("dummy", func(job *Job) string { return "" })
15
+}
16
+
17
+func newTestEngine(t *testing.T) *Engine {
18
+	// Use the caller function name as a prefix.
19
+	// This helps trace temp directories back to their test.
20
+	pc, _, _, _ := runtime.Caller(1)
21
+	callerLongName := runtime.FuncForPC(pc).Name()
22
+	parts := strings.Split(callerLongName, ".")
23
+	callerShortName := parts[len(parts)-1]
24
+	if globalTestID == "" {
25
+		globalTestID = utils.RandomString()[:4]
26
+	}
27
+	prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, callerShortName)
28
+	root, err := ioutil.TempDir("", prefix)
29
+	if err != nil {
30
+		t.Fatal(err)
31
+	}
32
+	eng, err := New(root)
33
+	if err != nil {
34
+		t.Fatal(err)
35
+	}
36
+	return eng
37
+}
38
+
39
+func mkJob(t *testing.T, name string, args ...string) *Job {
40
+	return newTestEngine(t).Job(name, args...)
41
+}
... ...
@@ -3,6 +3,7 @@ package docker
3 3
 import (
4 4
 	"bytes"
5 5
 	"fmt"
6
+	"github.com/dotcloud/docker/engine"
6 7
 	"github.com/dotcloud/docker/sysinit"
7 8
 	"github.com/dotcloud/docker/utils"
8 9
 	"io"
... ...
@@ -17,6 +18,7 @@ import (
17 17
 	"syscall"
18 18
 	"testing"
19 19
 	"time"
20
+	"net/url"
20 21
 )
21 22
 
22 23
 const (
... ...
@@ -119,22 +121,19 @@ func init() {
119 119
 }
120 120
 
121 121
 func setupBaseImage() {
122
-	config := &DaemonConfig{
123
-		Root:        unitTestStoreBase,
124
-		AutoRestart: false,
125
-		BridgeIface: unitTestNetworkBridge,
126
-	}
127
-	runtime, err := NewRuntimeFromDirectory(config)
122
+	eng, err := engine.New(unitTestStoreBase)
128 123
 	if err != nil {
129
-		log.Fatalf("Unable to create a runtime for tests:", err)
124
+		log.Fatalf("Can't initialize engine at %s: %s", unitTestStoreBase, err)
130 125
 	}
131
-
132
-	// Create the "Server"
133
-	srv := &Server{
134
-		runtime:     runtime,
135
-		pullingPool: make(map[string]struct{}),
136
-		pushingPool: make(map[string]struct{}),
126
+	job := eng.Job("initapi")
127
+	job.Setenv("Root", unitTestStoreBase)
128
+	job.SetenvBool("Autorestart", false)
129
+	job.Setenv("BridgeIface", unitTestNetworkBridge)
130
+	if err := job.Run(); err != nil {
131
+		log.Fatalf("Unable to create a runtime for tests:", err)
137 132
 	}
133
+	srv := mkServerFromEngine(eng, log.New(os.Stderr, "", 0))
134
+	runtime := srv.runtime
138 135
 
139 136
 	// If the unit test is not found, try to download it.
140 137
 	if img, err := runtime.repositories.LookupImage(unitTestImageName); err != nil || img.ID != unitTestImageID {
... ...
@@ -150,18 +149,22 @@ func spawnGlobalDaemon() {
150 150
 		utils.Debugf("Global runtime already exists. Skipping.")
151 151
 		return
152 152
 	}
153
-	globalRuntime = mkRuntime(log.New(os.Stderr, "", 0))
154
-	srv := &Server{
155
-		runtime:     globalRuntime,
156
-		pullingPool: make(map[string]struct{}),
157
-		pushingPool: make(map[string]struct{}),
158
-	}
153
+	t := log.New(os.Stderr, "", 0)
154
+	eng := NewTestEngine(t)
155
+	srv := mkServerFromEngine(eng, t)
156
+	globalRuntime = srv.runtime
159 157
 
160 158
 	// Spawn a Daemon
161 159
 	go func() {
162 160
 		utils.Debugf("Spawning global daemon for integration tests")
163
-		if err := ListenAndServe(testDaemonProto, testDaemonAddr, srv, os.Getenv("DEBUG") != ""); err != nil {
164
-			log.Fatalf("Unable to spawn the test daemon:", err)
161
+		listenURL := &url.URL{
162
+			Scheme:	testDaemonProto,
163
+			Host:	testDaemonAddr,
164
+		}
165
+		job := eng.Job("serveapi", listenURL.String())
166
+		job.SetenvBool("Logging", os.Getenv("DEBUG") != "")
167
+		if err := job.Run(); err != nil {
168
+			log.Fatalf("Unable to spawn the test daemon: %s", err)
165 169
 		}
166 170
 	}()
167 171
 	// Give some time to ListenAndServer to actually start
... ...
@@ -181,7 +184,7 @@ func GetTestImage(runtime *Runtime) *Image {
181 181
 			return image
182 182
 		}
183 183
 	}
184
-	log.Fatalf("Test image %v not found", unitTestImageID)
184
+	log.Fatalf("Test image %v not found in %s: %s", unitTestImageID, runtime.graph.Root, imgs)
185 185
 	return nil
186 186
 }
187 187
 
... ...
@@ -643,20 +646,17 @@ func TestReloadContainerLinks(t *testing.T) {
643 643
 }
644 644
 
645 645
 func TestDefaultContainerName(t *testing.T) {
646
-	runtime := mkRuntime(t)
646
+	eng := NewTestEngine(t)
647
+	srv := mkServerFromEngine(eng, t)
648
+	runtime := srv.runtime
647 649
 	defer nuke(runtime)
648
-	srv := &Server{runtime: runtime}
649 650
 
650 651
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
651 652
 	if err != nil {
652 653
 		t.Fatal(err)
653 654
 	}
654 655
 
655
-	shortId, _, err := srv.ContainerCreate(config, "some_name")
656
-	if err != nil {
657
-		t.Fatal(err)
658
-	}
659
-	container := runtime.Get(shortId)
656
+	container := runtime.Get(createNamedTestContainer(eng, config, t, "some_name"))
660 657
 	containerID := container.ID
661 658
 
662 659
 	if container.Name != "/some_name" {
... ...
@@ -680,20 +680,17 @@ func TestDefaultContainerName(t *testing.T) {
680 680
 }
681 681
 
682 682
 func TestRandomContainerName(t *testing.T) {
683
-	runtime := mkRuntime(t)
683
+	eng := NewTestEngine(t)
684
+	srv := mkServerFromEngine(eng, t)
685
+	runtime := srv.runtime
684 686
 	defer nuke(runtime)
685
-	srv := &Server{runtime: runtime}
686 687
 
687 688
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
688 689
 	if err != nil {
689 690
 		t.Fatal(err)
690 691
 	}
691 692
 
692
-	shortId, _, err := srv.ContainerCreate(config, "")
693
-	if err != nil {
694
-		t.Fatal(err)
695
-	}
696
-	container := runtime.Get(shortId)
693
+	container := runtime.Get(createTestContainer(eng, config, t))
697 694
 	containerID := container.ID
698 695
 
699 696
 	if container.Name == "" {
... ...
@@ -717,20 +714,17 @@ func TestRandomContainerName(t *testing.T) {
717 717
 }
718 718
 
719 719
 func TestLinkChildContainer(t *testing.T) {
720
-	runtime := mkRuntime(t)
720
+	eng := NewTestEngine(t)
721
+	srv := mkServerFromEngine(eng, t)
722
+	runtime := srv.runtime
721 723
 	defer nuke(runtime)
722
-	srv := &Server{runtime: runtime}
723 724
 
724 725
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
725 726
 	if err != nil {
726 727
 		t.Fatal(err)
727 728
 	}
728 729
 
729
-	shortId, _, err := srv.ContainerCreate(config, "/webapp")
730
-	if err != nil {
731
-		t.Fatal(err)
732
-	}
733
-	container := runtime.Get(shortId)
730
+	container := runtime.Get(createNamedTestContainer(eng, config, t, "/webapp"))
734 731
 
735 732
 	webapp, err := runtime.GetByName("/webapp")
736 733
 	if err != nil {
... ...
@@ -746,12 +740,7 @@ func TestLinkChildContainer(t *testing.T) {
746 746
 		t.Fatal(err)
747 747
 	}
748 748
 
749
-	shortId, _, err = srv.ContainerCreate(config, "")
750
-	if err != nil {
751
-		t.Fatal(err)
752
-	}
753
-
754
-	childContainer := runtime.Get(shortId)
749
+	childContainer := runtime.Get(createTestContainer(eng, config, t))
755 750
 
756 751
 	if err := runtime.RegisterLink(webapp, childContainer, "db"); err != nil {
757 752
 		t.Fatal(err)
... ...
@@ -768,20 +757,17 @@ func TestLinkChildContainer(t *testing.T) {
768 768
 }
769 769
 
770 770
 func TestGetAllChildren(t *testing.T) {
771
-	runtime := mkRuntime(t)
771
+	eng := NewTestEngine(t)
772
+	srv := mkServerFromEngine(eng, t)
773
+	runtime := srv.runtime
772 774
 	defer nuke(runtime)
773
-	srv := &Server{runtime: runtime}
774 775
 
775 776
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
776 777
 	if err != nil {
777 778
 		t.Fatal(err)
778 779
 	}
779 780
 
780
-	shortId, _, err := srv.ContainerCreate(config, "/webapp")
781
-	if err != nil {
782
-		t.Fatal(err)
783
-	}
784
-	container := runtime.Get(shortId)
781
+	container := runtime.Get(createNamedTestContainer(eng, config, t, "/webapp"))
785 782
 
786 783
 	webapp, err := runtime.GetByName("/webapp")
787 784
 	if err != nil {
... ...
@@ -797,12 +783,7 @@ func TestGetAllChildren(t *testing.T) {
797 797
 		t.Fatal(err)
798 798
 	}
799 799
 
800
-	shortId, _, err = srv.ContainerCreate(config, "")
801
-	if err != nil {
802
-		t.Fatal(err)
803
-	}
804
-
805
-	childContainer := runtime.Get(shortId)
800
+	childContainer := runtime.Get(createTestContainer(eng, config, t))
806 801
 
807 802
 	if err := runtime.RegisterLink(webapp, childContainer, "db"); err != nil {
808 803
 		t.Fatal(err)
... ...
@@ -33,30 +33,25 @@ func (srv *Server) Close() error {
33 33
 }
34 34
 
35 35
 func init() {
36
-	engine.Register("serveapi", JobServeApi)
36
+	engine.Register("initapi", jobInitApi)
37 37
 }
38 38
 
39
-func JobServeApi(job *engine.Job) string {
40
-	srv, err := NewServer(ConfigFromJob(job))
39
+// jobInitApi runs the remote api server `srv` as a daemon,
40
+// Only one api server can run at the same time - this is enforced by a pidfile.
41
+// The signals SIGINT, SIGKILL and SIGTERM are intercepted for cleanup.
42
+func jobInitApi(job *engine.Job) string {
43
+	job.Logf("Creating server")
44
+	srv, err := NewServer(job.Eng, ConfigFromJob(job))
41 45
 	if err != nil {
42 46
 		return err.Error()
43 47
 	}
44
-	defer srv.Close()
45
-	if err := srv.Daemon(); err != nil {
46
-		return err.Error()
47
-	}
48
-	return "0"
49
-}
50
-
51
-// Daemon runs the remote api server `srv` as a daemon,
52
-// Only one api server can run at the same time - this is enforced by a pidfile.
53
-// The signals SIGINT, SIGKILL and SIGTERM are intercepted for cleanup.
54
-func (srv *Server) Daemon() error {
55
-	if err := utils.CreatePidFile(srv.runtime.config.Pidfile); err != nil {
56
-		log.Fatal(err)
48
+	if srv.runtime.config.Pidfile != "" {
49
+		job.Logf("Creating pidfile")
50
+		if err := utils.CreatePidFile(srv.runtime.config.Pidfile); err != nil {
51
+			log.Fatal(err)
52
+		}
57 53
 	}
58
-	defer utils.RemovePidFile(srv.runtime.config.Pidfile)
59
-
54
+	job.Logf("Setting up signal traps")
60 55
 	c := make(chan os.Signal, 1)
61 56
 	signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM))
62 57
 	go func() {
... ...
@@ -66,8 +61,22 @@ func (srv *Server) Daemon() error {
66 66
 		srv.Close()
67 67
 		os.Exit(0)
68 68
 	}()
69
+	job.Eng.Hack_SetGlobalVar("httpapi.server", srv)
70
+	if err := job.Eng.Register("create", srv.ContainerCreate); err != nil {
71
+		return err.Error()
72
+	}
73
+	if err := job.Eng.Register("start", srv.ContainerStart); err != nil {
74
+		return err.Error()
75
+	}
76
+	if err := job.Eng.Register("serveapi", srv.ListenAndServe); err != nil {
77
+		return err.Error()
78
+	}
79
+	return "0"
80
+}
81
+
69 82
 
70
-	protoAddrs := srv.runtime.config.ProtoAddresses
83
+func (srv *Server) ListenAndServe(job *engine.Job) string {
84
+	protoAddrs := job.Args
71 85
 	chErrors := make(chan error, len(protoAddrs))
72 86
 	for _, protoAddr := range protoAddrs {
73 87
 		protoAddrParts := strings.SplitN(protoAddr, "://", 2)
... ...
@@ -81,19 +90,20 @@ func (srv *Server) Daemon() error {
81 81
 				log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
82 82
 			}
83 83
 		default:
84
-			return fmt.Errorf("Invalid protocol format.")
84
+			return "Invalid protocol format."
85 85
 		}
86 86
 		go func() {
87
-			chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, true)
87
+			// FIXME: merge Server.ListenAndServe with ListenAndServe
88
+			chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, job.GetenvBool("Logging"))
88 89
 		}()
89 90
 	}
90 91
 	for i := 0; i < len(protoAddrs); i += 1 {
91 92
 		err := <-chErrors
92 93
 		if err != nil {
93
-			return err
94
+			return err.Error()
94 95
 		}
95 96
 	}
96
-	return nil
97
+	return "0"
97 98
 }
98 99
 
99 100
 func (srv *Server) DockerVersion() APIVersion {
... ...
@@ -1021,33 +1031,43 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
1021 1021
 	return nil
1022 1022
 }
1023 1023
 
1024
-func (srv *Server) ContainerCreate(config *Config, name string) (string, []string, error) {
1024
+func (srv *Server) ContainerCreate(job *engine.Job) string {
1025
+	var name string
1026
+	if len(job.Args) == 1 {
1027
+		name = job.Args[0]
1028
+	} else if len(job.Args) > 1 {
1029
+		return fmt.Sprintf("Usage: %s ", job.Name)
1030
+	}
1031
+	var config Config
1032
+	if err := job.ExportEnv(&config); err != nil {
1033
+		return err.Error()
1034
+	}
1025 1035
 	if config.Memory != 0 && config.Memory < 524288 {
1026
-		return "", nil, fmt.Errorf("Minimum memory limit allowed is 512k")
1036
+		return "Minimum memory limit allowed is 512k"
1027 1037
 	}
1028
-
1029 1038
 	if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
1030 1039
 		config.Memory = 0
1031 1040
 	}
1032
-
1033 1041
 	if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit {
1034 1042
 		config.MemorySwap = -1
1035 1043
 	}
1036
-	container, buildWarnings, err := srv.runtime.Create(config, name)
1044
+	container, buildWarnings, err := srv.runtime.Create(&config, name)
1037 1045
 	if err != nil {
1038 1046
 		if srv.runtime.graph.IsNotExist(err) {
1039
-
1040 1047
 			_, tag := utils.ParseRepositoryTag(config.Image)
1041 1048
 			if tag == "" {
1042 1049
 				tag = DEFAULTTAG
1043 1050
 			}
1044
-
1045
-			return "", nil, fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag)
1051
+			return fmt.Sprintf("No such image: %s (tag: %s)", config.Image, tag)
1046 1052
 		}
1047
-		return "", nil, err
1053
+		return err.Error()
1048 1054
 	}
1049 1055
 	srv.LogEvent("create", container.ID, srv.runtime.repositories.ImageName(container.Image))
1050
-	return container.ID, buildWarnings, nil
1056
+	job.Printf("%s\n", container.ID)
1057
+	for _, warning := range buildWarnings {
1058
+		job.Errorf("%s\n", warning)
1059
+	}
1060
+	return "0"
1051 1061
 }
1052 1062
 
1053 1063
 func (srv *Server) ContainerRestart(name string, t int) error {
... ...
@@ -1322,7 +1342,6 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error {
1322 1322
 		return fmt.Errorf("No such container: %s", name)
1323 1323
 	}
1324 1324
 
1325
-	// Register links
1326 1325
 	if hostConfig != nil && hostConfig.Links != nil {
1327 1326
 		for _, l := range hostConfig.Links {
1328 1327
 			parts, err := parseLink(l)
... ...
@@ -1336,7 +1355,6 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error {
1336 1336
 			if child == nil {
1337 1337
 				return fmt.Errorf("Could not get container for %s", parts["name"])
1338 1338
 			}
1339
-
1340 1339
 			if err := runtime.RegisterLink(container, child, parts["alias"]); err != nil {
1341 1340
 				return err
1342 1341
 			}
... ...
@@ -1352,41 +1370,57 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error {
1352 1352
 	return nil
1353 1353
 }
1354 1354
 
1355
-func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error {
1355
+func (srv *Server) ContainerStart(job *engine.Job) string {
1356
+	if len(job.Args) < 1 {
1357
+		return fmt.Sprintf("Usage: %s container_id", job.Name)
1358
+	}
1359
+	name := job.Args[0]
1356 1360
 	runtime := srv.runtime
1357 1361
 	container := runtime.Get(name)
1358 1362
 
1359
-	if hostConfig != nil {
1360
-		for _, bind := range hostConfig.Binds {
1361
-			splitBind := strings.Split(bind, ":")
1362
-			source := splitBind[0]
1363
-
1364
-			// refuse to bind mount "/" to the container
1365
-			if source == "/" {
1366
-				return fmt.Errorf("Invalid bind mount '%s' : source can't be '/'", bind)
1367
-			}
1368
-
1369
-			// ensure the source exists on the host
1370
-			_, err := os.Stat(source)
1371
-			if err != nil && os.IsNotExist(err) {
1372
-				return fmt.Errorf("Invalid bind mount '%s' : source doesn't exist", bind)
1373
-			}
1374
-		}
1375
-	}
1376
-
1377 1363
 	if container == nil {
1378
-		return fmt.Errorf("No such container: %s", name)
1379
-	}
1380
-	if hostConfig != nil {
1381
-		container.hostConfig = hostConfig
1364
+		return fmt.Sprintf("No such container: %s", name)
1365
+	}
1366
+	// If no environment was set, then no hostconfig was passed.
1367
+	if len(job.Environ()) > 0 {
1368
+		var hostConfig HostConfig
1369
+		if err := job.ExportEnv(&hostConfig); err != nil {
1370
+			return err.Error()
1371
+		}
1372
+		// Validate the HostConfig binds. Make sure that:
1373
+                // 1) the source of a bind mount isn't /
1374
+                //         The bind mount "/:/foo" isn't allowed.
1375
+                // 2) Check that the source exists
1376
+                //        The source to be bind mounted must exist.
1377
+                for _, bind := range hostConfig.Binds {
1378
+                        splitBind := strings.Split(bind, ":")
1379
+                        source := splitBind[0]
1380
+
1381
+                        // refuse to bind mount "/" to the container
1382
+                        if source == "/" {
1383
+                                return fmt.Sprintf("Invalid bind mount '%s' : source can't be '/'", bind)
1384
+                        }
1385
+
1386
+                        // ensure the source exists on the host
1387
+                        _, err := os.Stat(source)
1388
+                        if err != nil && os.IsNotExist(err) {
1389
+                                return fmt.Sprintf("Invalid bind mount '%s' : source doesn't exist", bind)
1390
+                        }
1391
+                }
1392
+		// Register any links from the host config before starting the container
1393
+		// FIXME: we could just pass the container here, no need to lookup by name again.
1394
+		if err := srv.RegisterLinks(name, &hostConfig); err != nil {
1395
+			return err.Error()
1396
+		}
1397
+		container.hostConfig = &hostConfig
1382 1398
 		container.ToDisk()
1383 1399
 	}
1384 1400
 	if err := container.Start(); err != nil {
1385
-		return fmt.Errorf("Cannot start container %s: %s", name, err)
1401
+		return fmt.Sprintf("Cannot start container %s: %s", name, err)
1386 1402
 	}
1387 1403
 	srv.LogEvent("start", container.ID, runtime.repositories.ImageName(container.Image))
1388 1404
 
1389
-	return nil
1405
+	return "0"
1390 1406
 }
1391 1407
 
1392 1408
 func (srv *Server) ContainerStop(name string, t int) error {
... ...
@@ -1537,12 +1571,13 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er
1537 1537
 
1538 1538
 }
1539 1539
 
1540
-func NewServer(config *DaemonConfig) (*Server, error) {
1540
+func NewServer(eng *engine.Engine, config *DaemonConfig) (*Server, error) {
1541 1541
 	runtime, err := NewRuntime(config)
1542 1542
 	if err != nil {
1543 1543
 		return nil, err
1544 1544
 	}
1545 1545
 	srv := &Server{
1546
+		Eng:         eng,
1546 1547
 		runtime:     runtime,
1547 1548
 		pullingPool: make(map[string]struct{}),
1548 1549
 		pushingPool: make(map[string]struct{}),
... ...
@@ -1586,4 +1621,5 @@ type Server struct {
1586 1586
 	events      []utils.JSONMessage
1587 1587
 	listeners   map[string]chan utils.JSONMessage
1588 1588
 	reqFactory  *utils.HTTPRequestFactory
1589
+	Eng         *engine.Engine
1589 1590
 }
... ...
@@ -80,20 +80,17 @@ func TestContainerTagImageDelete(t *testing.T) {
80 80
 }
81 81
 
82 82
 func TestCreateRm(t *testing.T) {
83
-	runtime := mkRuntime(t)
83
+	eng := NewTestEngine(t)
84
+	srv := mkServerFromEngine(eng, t)
85
+	runtime := srv.runtime
84 86
 	defer nuke(runtime)
85 87
 
86
-	srv := &Server{runtime: runtime}
87
-
88 88
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
89 89
 	if err != nil {
90 90
 		t.Fatal(err)
91 91
 	}
92 92
 
93
-	id, _, err := srv.ContainerCreate(config, "")
94
-	if err != nil {
95
-		t.Fatal(err)
96
-	}
93
+	id := createTestContainer(eng, config, t)
97 94
 
98 95
 	if len(runtime.List()) != 1 {
99 96
 		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
... ...
@@ -110,27 +107,28 @@ func TestCreateRm(t *testing.T) {
110 110
 }
111 111
 
112 112
 func TestCreateRmVolumes(t *testing.T) {
113
-	runtime := mkRuntime(t)
114
-	defer nuke(runtime)
113
+	eng := NewTestEngine(t)
115 114
 
116
-	srv := &Server{runtime: runtime}
115
+	srv := mkServerFromEngine(eng, t)
116
+	runtime := srv.runtime
117
+	defer nuke(runtime)
117 118
 
118 119
 	config, hostConfig, _, err := ParseRun([]string{"-v", "/srv", GetTestImage(runtime).ID, "echo test"}, nil)
119 120
 	if err != nil {
120 121
 		t.Fatal(err)
121 122
 	}
122 123
 
123
-	id, _, err := srv.ContainerCreate(config, "")
124
-	if err != nil {
125
-		t.Fatal(err)
126
-	}
124
+	id := createTestContainer(eng, config, t)
127 125
 
128 126
 	if len(runtime.List()) != 1 {
129 127
 		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
130 128
 	}
131 129
 
132
-	err = srv.ContainerStart(id, hostConfig)
133
-	if err != nil {
130
+	job := eng.Job("start", id)
131
+	if err := job.ImportEnv(hostConfig); err != nil {
132
+		t.Fatal(err)
133
+	}
134
+	if err := job.Run(); err != nil {
134 135
 		t.Fatal(err)
135 136
 	}
136 137
 
... ...
@@ -149,20 +147,17 @@ func TestCreateRmVolumes(t *testing.T) {
149 149
 }
150 150
 
151 151
 func TestCommit(t *testing.T) {
152
-	runtime := mkRuntime(t)
152
+	eng := NewTestEngine(t)
153
+	srv := mkServerFromEngine(eng, t)
154
+	runtime := srv.runtime
153 155
 	defer nuke(runtime)
154 156
 
155
-	srv := &Server{runtime: runtime}
156
-
157 157
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
158 158
 	if err != nil {
159 159
 		t.Fatal(err)
160 160
 	}
161 161
 
162
-	id, _, err := srv.ContainerCreate(config, "")
163
-	if err != nil {
164
-		t.Fatal(err)
165
-	}
162
+	id := createTestContainer(eng, config, t)
166 163
 
167 164
 	if _, err := srv.ContainerCommit(id, "testrepo", "testtag", "", "", config); err != nil {
168 165
 		t.Fatal(err)
... ...
@@ -170,26 +165,27 @@ func TestCommit(t *testing.T) {
170 170
 }
171 171
 
172 172
 func TestCreateStartRestartStopStartKillRm(t *testing.T) {
173
-	runtime := mkRuntime(t)
173
+	eng := NewTestEngine(t)
174
+	srv := mkServerFromEngine(eng, t)
175
+	runtime := srv.runtime
174 176
 	defer nuke(runtime)
175 177
 
176
-	srv := &Server{runtime: runtime}
177
-
178 178
 	config, hostConfig, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
179 179
 	if err != nil {
180 180
 		t.Fatal(err)
181 181
 	}
182 182
 
183
-	id, _, err := srv.ContainerCreate(config, "")
184
-	if err != nil {
185
-		t.Fatal(err)
186
-	}
183
+	id := createTestContainer(eng, config, t)
187 184
 
188 185
 	if len(runtime.List()) != 1 {
189 186
 		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
190 187
 	}
191 188
 
192
-	if err := srv.ContainerStart(id, hostConfig); err != nil {
189
+	job := eng.Job("start", id)
190
+	if err := job.ImportEnv(hostConfig); err != nil {
191
+		t.Fatal(err)
192
+	}
193
+	if err := job.Run(); err != nil {
193 194
 		t.Fatal(err)
194 195
 	}
195 196
 
... ...
@@ -201,7 +197,11 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
201 201
 		t.Fatal(err)
202 202
 	}
203 203
 
204
-	if err := srv.ContainerStart(id, hostConfig); err != nil {
204
+	job = eng.Job("start", id)
205
+	if err := job.ImportEnv(hostConfig); err != nil {
206
+		t.Fatal(err)
207
+	}
208
+	if err := job.Run(); err != nil {
205 209
 		t.Fatal(err)
206 210
 	}
207 211
 
... ...
@@ -221,22 +221,22 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
221 221
 }
222 222
 
223 223
 func TestRunWithTooLowMemoryLimit(t *testing.T) {
224
-	runtime := mkRuntime(t)
224
+	eng := NewTestEngine(t)
225
+	srv := mkServerFromEngine(eng, t)
226
+	runtime := srv.runtime
225 227
 	defer nuke(runtime)
226 228
 
227 229
 	// Try to create a container with a memory limit of 1 byte less than the minimum allowed limit.
228
-	if _, _, err := (*Server).ContainerCreate(&Server{runtime: runtime},
229
-		&Config{
230
-			Image:     GetTestImage(runtime).ID,
231
-			Memory:    524287,
232
-			CpuShares: 1000,
233
-			Cmd:       []string{"/bin/cat"},
234
-		},
235
-		"",
236
-	); err == nil {
230
+	job := eng.Job("create")
231
+	job.Setenv("Image", GetTestImage(runtime).ID)
232
+	job.Setenv("Memory", "524287")
233
+	job.Setenv("CpuShares", "1000")
234
+	job.SetenvList("Cmd", []string{"/bin/cat"})
235
+	var id string
236
+	job.StdoutParseString(&id)
237
+	if err := job.Run(); err == nil {
237 238
 		t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!")
238 239
 	}
239
-
240 240
 }
241 241
 
242 242
 func TestContainerTop(t *testing.T) {
... ...
@@ -385,9 +385,10 @@ func TestLogEvent(t *testing.T) {
385 385
 }
386 386
 
387 387
 func TestRmi(t *testing.T) {
388
-	runtime := mkRuntime(t)
388
+	eng := NewTestEngine(t)
389
+	srv := mkServerFromEngine(eng, t)
390
+	runtime := srv.runtime
389 391
 	defer nuke(runtime)
390
-	srv := &Server{runtime: runtime}
391 392
 
392 393
 	initialImages, err := srv.Images(false, "")
393 394
 	if err != nil {
... ...
@@ -399,14 +400,14 @@ func TestRmi(t *testing.T) {
399 399
 		t.Fatal(err)
400 400
 	}
401 401
 
402
-	containerID, _, err := srv.ContainerCreate(config, "")
403
-	if err != nil {
404
-		t.Fatal(err)
405
-	}
402
+	containerID := createTestContainer(eng, config, t)
406 403
 
407 404
 	//To remove
408
-	err = srv.ContainerStart(containerID, hostConfig)
409
-	if err != nil {
405
+	job := eng.Job("start", containerID)
406
+	if err := job.ImportEnv(hostConfig); err != nil {
407
+		t.Fatal(err)
408
+	}
409
+	if err := job.Run(); err != nil {
410 410
 		t.Fatal(err)
411 411
 	}
412 412
 
... ...
@@ -420,14 +421,14 @@ func TestRmi(t *testing.T) {
420 420
 		t.Fatal(err)
421 421
 	}
422 422
 
423
-	containerID, _, err = srv.ContainerCreate(config, "")
424
-	if err != nil {
425
-		t.Fatal(err)
426
-	}
423
+	containerID = createTestContainer(eng, config, t)
427 424
 
428 425
 	//To remove
429
-	err = srv.ContainerStart(containerID, hostConfig)
430
-	if err != nil {
426
+	job = eng.Job("start", containerID)
427
+	if err := job.ImportEnv(hostConfig); err != nil {
428
+		t.Fatal(err)
429
+	}
430
+	if err := job.Run(); err != nil {
431 431
 		t.Fatal(err)
432 432
 	}
433 433
 
... ...
@@ -28,6 +28,12 @@ var (
28 28
 	INITSHA1  string // sha1sum of separate static dockerinit, if Docker itself was compiled dynamically via ./hack/make.sh dynbinary
29 29
 )
30 30
 
31
+// A common interface to access the Fatal method of
32
+// both testing.B and testing.T.
33
+type Fataler interface {
34
+	Fatal(args ...interface{})
35
+}
36
+
31 37
 // ListOpts type
32 38
 type ListOpts []string
33 39
 
... ...
@@ -2,6 +2,7 @@ package docker
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"github.com/dotcloud/docker/engine"
5 6
 	"github.com/dotcloud/docker/utils"
6 7
 	"io"
7 8
 	"io/ioutil"
... ...
@@ -20,62 +21,98 @@ var globalTestID string
20 20
 
21 21
 // Create a temporary runtime suitable for unit testing.
22 22
 // Call t.Fatal() at the first error.
23
-func mkRuntime(f Fataler) *Runtime {
24
-	// Use the caller function name as a prefix.
25
-	// This helps trace temp directories back to their test.
26
-	pc, _, _, _ := runtime.Caller(1)
27
-	callerLongName := runtime.FuncForPC(pc).Name()
28
-	parts := strings.Split(callerLongName, ".")
29
-	callerShortName := parts[len(parts)-1]
30
-	if globalTestID == "" {
31
-		globalTestID = GenerateID()[:4]
23
+func mkRuntime(f utils.Fataler) *Runtime {
24
+	root, err := newTestDirectory(unitTestStoreBase)
25
+	if err != nil {
26
+		f.Fatal(err)
32 27
 	}
33
-	prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, callerShortName)
34
-	utils.Debugf("prefix = '%s'", prefix)
35
-
36
-	runtime, err := newTestRuntime(prefix)
28
+	config := &DaemonConfig{
29
+		Root:   root,
30
+		AutoRestart: false,
31
+	}
32
+	r, err := NewRuntimeFromDirectory(config)
37 33
 	if err != nil {
38 34
 		f.Fatal(err)
39 35
 	}
40
-	return runtime
36
+	r.UpdateCapabilities(true)
37
+	return r
41 38
 }
42 39
 
43
-// A common interface to access the Fatal method of
44
-// both testing.B and testing.T.
45
-type Fataler interface {
46
-	Fatal(args ...interface{})
40
+func createNamedTestContainer(eng *engine.Engine, config *Config, f utils.Fataler, name string) (shortId string) {
41
+	job := eng.Job("create", name)
42
+	if err := job.ImportEnv(config); err != nil {
43
+		f.Fatal(err)
44
+	}
45
+	job.StdoutParseString(&shortId)
46
+	if err := job.Run(); err != nil {
47
+		f.Fatal(err)
48
+	}
49
+	return
47 50
 }
48 51
 
49
-func newTestRuntime(prefix string) (runtime *Runtime, err error) {
50
-	if prefix == "" {
51
-		prefix = "docker-test-"
52
+func createTestContainer(eng *engine.Engine, config *Config, f utils.Fataler) (shortId string) {
53
+	return createNamedTestContainer(eng, config, f, "")
54
+}
55
+
56
+func mkServerFromEngine(eng *engine.Engine, t utils.Fataler) *Server {
57
+	iSrv := eng.Hack_GetGlobalVar("httpapi.server")
58
+	if iSrv == nil {
59
+		panic("Legacy server field not set in engine")
52 60
 	}
53
-	utils.Debugf("prefix = %s", prefix)
54
-	utils.Debugf("newTestRuntime start")
55
-	root, err := ioutil.TempDir("", prefix)
56
-	defer func() {
57
-		utils.Debugf("newTestRuntime: %s", root)
58
-	}()
61
+	srv, ok := iSrv.(*Server)
62
+	if !ok {
63
+		panic("Legacy server field in engine does not cast to *Server")
64
+	}
65
+	return srv
66
+}
67
+
68
+
69
+func NewTestEngine(t utils.Fataler) *engine.Engine {
70
+	root, err := newTestDirectory(unitTestStoreBase)
59 71
 	if err != nil {
60
-		return nil, err
72
+		t.Fatal(err)
61 73
 	}
62
-	if err := os.Remove(root); err != nil {
63
-		return nil, err
74
+	eng, err := engine.New(root)
75
+	if err != nil {
76
+		t.Fatal(err)
64 77
 	}
65
-	if err := utils.CopyDirectory(unitTestStoreBase, root); err != nil {
66
-		return nil, err
78
+	// Load default plugins
79
+	// (This is manually copied and modified from main() until we have a more generic plugin system)
80
+	job := eng.Job("initapi")
81
+	job.Setenv("Root", root)
82
+	job.SetenvBool("AutoRestart", false)
83
+	if err := job.Run(); err != nil {
84
+		t.Fatal(err)
67 85
 	}
86
+	return eng
87
+}
68 88
 
69
-	config := &DaemonConfig{
70
-		Root:        root,
71
-		AutoRestart: false,
89
+func newTestDirectory(templateDir string) (dir string, err error) {
90
+	if globalTestID == "" {
91
+		globalTestID = GenerateID()[:4]
72 92
 	}
73
-	runtime, err = NewRuntimeFromDirectory(config)
74
-	if err != nil {
75
-		return nil, err
93
+	prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, getCallerName(2))
94
+	if prefix == "" {
95
+		prefix = "docker-test-"
96
+	}
97
+	dir, err = ioutil.TempDir("", prefix)
98
+	if err = os.Remove(dir); err != nil {
99
+		return
76 100
 	}
77
-	runtime.UpdateCapabilities(true)
78
-	return runtime, nil
101
+	if err = utils.CopyDirectory(templateDir, dir); err != nil {
102
+		return
103
+	}
104
+	return
105
+}
106
+
107
+func getCallerName(depth int) string {
108
+	// Use the caller function name as a prefix.
109
+	// This helps trace temp directories back to their test.
110
+	pc, _, _, _ := runtime.Caller(depth + 1)
111
+	callerLongName := runtime.FuncForPC(pc).Name()
112
+	parts := strings.Split(callerLongName, ".")
113
+	callerShortName := parts[len(parts)-1]
114
+	return callerShortName
79 115
 }
80 116
 
81 117
 // Write `content` to the file at path `dst`, creating it if necessary,