Browse code

Engine: 'create' creates a container and prints its ID on stdout

Solomon Hykes authored on 2013/10/28 11:20:00
Showing 8 changed files
... ...
@@ -526,43 +526,36 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
526 526
 	if err := parseForm(r); err != nil {
527 527
 		return nil
528 528
 	}
529
-	config := &Config{}
530 529
 	out := &APIRun{}
531
-	name := r.Form.Get("name")
532
-
533
-	if err := json.NewDecoder(r.Body).Decode(config); err != nil {
530
+	job := srv.Eng.Job("create", r.Form.Get("name"))
531
+	if err := job.DecodeEnv(r.Body); err != nil {
534 532
 		return err
535 533
 	}
536
-
537 534
 	resolvConf, err := utils.GetResolvConf()
538 535
 	if err != nil {
539 536
 		return err
540 537
 	}
541
-
542
-	if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
538
+	if !job.GetenvBool("NetworkDisabled") && len(job.Getenv("Dns")) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
543 539
 		out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns))
544
-		config.Dns = defaultDns
540
+		job.SetenvList("Dns", defaultDns)
545 541
 	}
546
-
547
-	id, warnings, err := srv.ContainerCreate(config, name)
548
-	if err != nil {
542
+	// Read container ID from the first line of stdout
543
+	job.StdoutParseString(&out.ID)
544
+	// Read warnings from stderr
545
+	job.StderrParseLines(&out.Warnings, 0)
546
+	if err := job.Run(); err != nil {
549 547
 		return err
550 548
 	}
551
-	out.ID = id
552
-	for _, warning := range warnings {
553
-		out.Warnings = append(out.Warnings, warning)
554
-	}
555
-
556
-	if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
549
+	if job.GetenvInt("Memory") > 0 && !srv.runtime.capabilities.MemoryLimit {
557 550
 		log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
558 551
 		out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.")
559 552
 	}
560
-	if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit {
553
+	if job.GetenvInt("Memory") > 0 && !srv.runtime.capabilities.SwapLimit {
561 554
 		log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.")
562 555
 		out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.")
563 556
 	}
564 557
 
565
-	if !config.NetworkDisabled && srv.runtime.capabilities.IPv4ForwardingDisabled {
558
+	if !job.GetenvBool("NetworkDisabled") && srv.runtime.capabilities.IPv4ForwardingDisabled {
566 559
 		log.Println("Warning: IPv4 forwarding is disabled.")
567 560
 		out.Warnings = append(out.Warnings, "IPv4 forwarding is disabled.")
568 561
 	}
... ...
@@ -634,11 +634,11 @@ func TestPostCommit(t *testing.T) {
634 634
 }
635 635
 
636 636
 func TestPostContainersCreate(t *testing.T) {
637
-	runtime := mkRuntime(t)
637
+	eng := NewTestEngine(t)
638
+	srv := mkServerFromEngine(eng, t)
639
+	runtime := srv.runtime
638 640
 	defer nuke(runtime)
639 641
 
640
-	srv := &Server{runtime: runtime}
641
-
642 642
 	configJSON, err := json.Marshal(&Config{
643 643
 		Image:  GetTestImage(runtime).ID,
644 644
 		Memory: 33554432,
... ...
@@ -786,22 +786,18 @@ func TestPostContainersStart(t *testing.T) {
786 786
 	runtime := srv.runtime
787 787
 	defer nuke(runtime)
788 788
 
789
-	container, _, err := runtime.Create(
789
+	id := createTestContainer(
790
+		eng,
790 791
 		&Config{
791 792
 			Image:     GetTestImage(runtime).ID,
792 793
 			Cmd:       []string{"/bin/cat"},
793 794
 			OpenStdin: true,
794 795
 		},
795
-		"",
796
-	)
797
-	if err != nil {
798
-		t.Fatal(err)
799
-	}
800
-	defer runtime.Destroy(container)
796
+		t)
801 797
 
802 798
 	hostConfigJSON, err := json.Marshal(&HostConfig{})
803 799
 
804
-	req, err := http.NewRequest("POST", "/containers/"+container.ID+"/start", bytes.NewReader(hostConfigJSON))
800
+	req, err := http.NewRequest("POST", "/containers/"+id+"/start", bytes.NewReader(hostConfigJSON))
805 801
 	if err != nil {
806 802
 		t.Fatal(err)
807 803
 	}
... ...
@@ -809,22 +805,26 @@ func TestPostContainersStart(t *testing.T) {
809 809
 	req.Header.Set("Content-Type", "application/json")
810 810
 
811 811
 	r := httptest.NewRecorder()
812
-	if err := postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
812
+	if err := postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": id}); err != nil {
813 813
 		t.Fatal(err)
814 814
 	}
815 815
 	if r.Code != http.StatusNoContent {
816 816
 		t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
817 817
 	}
818 818
 
819
+	container := runtime.Get(id)
820
+	if container == nil {
821
+		t.Fatalf("Container %s was not created", id)
822
+	}
819 823
 	// Give some time to the process to start
824
+	// FIXME: use Wait once it's available as a job
820 825
 	container.WaitTimeout(500 * time.Millisecond)
821
-
822 826
 	if !container.State.Running {
823 827
 		t.Errorf("Container should be running")
824 828
 	}
825 829
 
826 830
 	r = httptest.NewRecorder()
827
-	if err = postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err == nil {
831
+	if err = postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": id}); err == nil {
828 832
 		t.Fatalf("A running container should be able to be started")
829 833
 	}
830 834
 
... ...
@@ -115,5 +115,5 @@ func (eng *Engine) Job(name string, args ...string) *Job {
115 115
 
116 116
 func (eng *Engine) Logf(format string, args ...interface{}) (n int, err error) {
117 117
 	prefixedFormat := fmt.Sprintf("[%s] %s\n", eng, strings.TrimRight(format, "\n"))
118
-	return fmt.Printf(prefixedFormat, args...)
118
+	return fmt.Fprintf(os.Stderr, prefixedFormat, args...)
119 119
 }
... ...
@@ -1,11 +1,16 @@
1 1
 package engine
2 2
 
3 3
 import (
4
+	"bufio"
4 5
 	"bytes"
5 6
 	"io"
7
+	"io/ioutil"
8
+	"strconv"
6 9
 	"strings"
7 10
 	"fmt"
11
+	"sync"
8 12
 	"encoding/json"
13
+	"os"
9 14
 )
10 15
 
11 16
 // A job is the fundamental unit of work in the docker engine.
... ...
@@ -26,20 +31,38 @@ type Job struct {
26 26
 	Name	string
27 27
 	Args	[]string
28 28
 	env	[]string
29
-	Stdin	io.ReadCloser
30
-	Stdout	io.WriteCloser
31
-	Stderr	io.WriteCloser
29
+	Stdin	io.Reader
30
+	Stdout	io.Writer
31
+	Stderr	io.Writer
32 32
 	handler	func(*Job) string
33 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
-	job.Logf("{")
41 41
 	defer func() {
42
-		job.Logf("}")
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())
43 61
 	}()
44 62
 	if job.handler == nil {
45 63
 		job.status = "command not found"
... ...
@@ -52,9 +75,66 @@ func (job *Job) Run() error {
52 52
 	return nil
53 53
 }
54 54
 
55
-// String returns a human-readable description of `job`
56
-func (job *Job) String() string {
57
-	s := fmt.Sprintf("%s.%s(%s)", job.Eng, job.Name, strings.Join(job.Args, ", "))
55
+func (job *Job) StdoutParseLines(dst *[]string, limit int) {
56
+	job.parseLines(job.StdoutPipe(), dst, limit)
57
+}
58
+
59
+func (job *Job) StderrParseLines(dst *[]string, limit int) {
60
+	job.parseLines(job.StderrPipe(), dst, limit)
61
+}
62
+
63
+func (job *Job) parseLines(src io.Reader, dst *[]string, limit int) {
64
+	var wg sync.WaitGroup
65
+	wg.Add(1)
66
+	go func() {
67
+		defer wg.Done()
68
+		scanner := bufio.NewScanner(src)
69
+		for scanner.Scan() {
70
+			// If the limit is reached, flush the rest of the source and return
71
+			if limit > 0 && len(*dst) >= limit {
72
+				io.Copy(ioutil.Discard, src)
73
+				return
74
+			}
75
+			line := scanner.Text()
76
+			// Append the line (with delimitor removed)
77
+			*dst = append(*dst, line)
78
+		}
79
+	}()
80
+	job.onExit = append(job.onExit, wg.Wait)
81
+}
82
+
83
+func (job *Job) StdoutParseString(dst *string) {
84
+	lines := make([]string, 0, 1)
85
+	job.StdoutParseLines(&lines, 1)
86
+	job.onExit = append(job.onExit, func() { if len(lines) >= 1 { *dst = lines[0] }})
87
+}
88
+
89
+func (job *Job) StderrParseString(dst *string) {
90
+	lines := make([]string, 0, 1)
91
+	job.StderrParseLines(&lines, 1)
92
+	job.onExit = append(job.onExit, func() { *dst = lines[0]; })
93
+}
94
+
95
+func (job *Job) StdoutPipe() io.ReadCloser {
96
+	r, w := io.Pipe()
97
+	job.Stdout = w
98
+	job.onExit = append(job.onExit, func(){ w.Close() })
99
+	return r
100
+}
101
+
102
+func (job *Job) StderrPipe() io.ReadCloser {
103
+	r, w := io.Pipe()
104
+	job.Stderr = w
105
+	job.onExit = append(job.onExit, func(){ w.Close() })
106
+	return r
107
+}
108
+
109
+
110
+func (job *Job) CallString() string {
111
+	return fmt.Sprintf("%s(%s)", job.Name, strings.Join(job.Args, ", "))
112
+}
113
+
114
+func (job *Job) StatusString() string {
58 115
 	// FIXME: if a job returns the empty string, it will be printed
59 116
 	// as not having returned.
60 117
 	// (this only affects String which is a convenience function).
... ...
@@ -65,9 +145,14 @@ func (job *Job) String() string {
65 65
 		} else {
66 66
 			okerr = "ERR"
67 67
 		}
68
-		s = fmt.Sprintf("%s = %s (%s)", s, okerr, job.status)
68
+		return fmt.Sprintf(" = %s (%s)", okerr, job.status)
69 69
 	}
70
-	return s
70
+	return ""
71
+}
72
+
73
+// String returns a human-readable description of `job`
74
+func (job *Job) String() string {
75
+	return fmt.Sprintf("%s.%s%s", job.Eng, job.CallString(), job.StatusString())
71 76
 }
72 77
 
73 78
 func (job *Job) Getenv(key string) (value string) {
... ...
@@ -104,6 +189,19 @@ func (job *Job) SetenvBool(key string, value bool) {
104 104
 	}
105 105
 }
106 106
 
107
+func (job *Job) GetenvInt(key string) int64 {
108
+	s := strings.Trim(job.Getenv(key), " \t")
109
+	val, err := strconv.ParseInt(s, 10, 64)
110
+	if err != nil {
111
+		return -1
112
+	}
113
+	return val
114
+}
115
+
116
+func (job *Job) SetenvInt(key string, value int64) {
117
+	job.Setenv(key, fmt.Sprintf("%d", value))
118
+}
119
+
107 120
 func (job *Job) GetenvList(key string) []string {
108 121
 	sval := job.Getenv(key)
109 122
 	l := make([]string, 0, 1)
... ...
@@ -137,13 +235,21 @@ func (job *Job) DecodeEnv(src io.Reader) error {
137 137
 		return err
138 138
 	}
139 139
 	for k, v := range m {
140
-		if sval, ok := v.(string); ok {
140
+		// FIXME: we fix-convert float values to int, because
141
+		// encoding/json decodes integers to float64, but cannot encode them back.
142
+		// (See http://golang.org/src/pkg/encoding/json/decode.go#L46)
143
+		if fval, ok := v.(float64); ok {
144
+			job.Logf("Converted to float: %v->%v", v, fval)
145
+			job.SetenvInt(k, int64(fval))
146
+		} else if sval, ok := v.(string); ok {
147
+			job.Logf("Converted to string: %v->%v", v, sval)
141 148
 			job.Setenv(k, sval)
142 149
 		} else	if val, err := json.Marshal(v); err == nil {
143 150
 			job.Setenv(k, string(val))
144 151
 		} else {
145 152
 			job.Setenv(k, fmt.Sprintf("%v", v))
146 153
 		}
154
+		job.Logf("Decoded %s=%#v to %s=%#v", k, v, k, job.Getenv(k))
147 155
 	}
148 156
 	return nil
149 157
 }
... ...
@@ -153,10 +259,17 @@ func (job *Job) EncodeEnv(dst io.Writer) error {
153 153
 	for k, v := range job.Environ() {
154 154
 		var val interface{}
155 155
 		if err := json.Unmarshal([]byte(v), &val); err == nil {
156
+			// FIXME: we fix-convert float values to int, because
157
+			// encoding/json decodes integers to float64, but cannot encode them back.
158
+			// (See http://golang.org/src/pkg/encoding/json/decode.go#L46)
159
+			if fval, isFloat := val.(float64); isFloat {
160
+				val = int(fval)
161
+			}
156 162
 			m[k] = val
157 163
 		} else {
158 164
 			m[k] = v
159 165
 		}
166
+		job.Logf("Encoded %s=%#v to %s=%#v", k, v, k, m[k])
160 167
 	}
161 168
 	if err := json.NewEncoder(dst).Encode(&m); err != nil {
162 169
 		return err
... ...
@@ -165,21 +278,38 @@ func (job *Job) EncodeEnv(dst io.Writer) error {
165 165
 }
166 166
 
167 167
 func (job *Job) ExportEnv(dst interface{}) (err error) {
168
+	fmt.Fprintf(os.Stderr, "ExportEnv()\n")
169
+	defer func() {
170
+		if err != nil {
171
+			err = fmt.Errorf("ExportEnv %s", err)
172
+		}
173
+	}()
168 174
 	var buf bytes.Buffer
175
+	job.Logf("ExportEnv: step 1: encode/marshal the env to an intermediary json representation")
176
+	fmt.Fprintf(os.Stderr, "Printed ExportEnv step 1\n")
169 177
 	if err := job.EncodeEnv(&buf); err != nil {
170 178
 		return err
171 179
 	}
180
+	job.Logf("ExportEnv: step 1 complete: json=|%s|", buf)
181
+	job.Logf("ExportEnv: step 2: decode/unmarshal the intermediary json into the destination object")
172 182
 	if err := json.NewDecoder(&buf).Decode(dst); err != nil {
173 183
 		return err
174 184
 	}
185
+	job.Logf("ExportEnv: step 2 complete")
175 186
 	return nil
176 187
 }
177 188
 
178
-func (job *Job) ImportEnv(src interface{}) error {
189
+func (job *Job) ImportEnv(src interface{}) (err error) {
190
+	defer func() {
191
+		if err != nil {
192
+			err = fmt.Errorf("ImportEnv: %s", err)
193
+		}
194
+	}()
179 195
 	var buf bytes.Buffer
180 196
 	if err := json.NewEncoder(&buf).Encode(src); err != nil {
181 197
 		return err
182 198
 	}
199
+	job.Logf("ImportEnv: json=|%s|", buf)
183 200
 	if err := job.DecodeEnv(&buf); err != nil {
184 201
 		return err
185 202
 	}
... ...
@@ -197,5 +327,14 @@ func (job *Job) Environ() map[string]string {
197 197
 
198 198
 func (job *Job) Logf(format string, args ...interface{}) (n int, err error) {
199 199
 	prefixedFormat := fmt.Sprintf("[%s] %s\n", job, strings.TrimRight(format, "\n"))
200
-	return fmt.Fprintf(job.Stdout, prefixedFormat, args...)
200
+	return fmt.Fprintf(job.Stderr, prefixedFormat, args...)
201
+}
202
+
203
+func (job *Job) Printf(format string, args ...interface{}) (n int, err error) {
204
+	return fmt.Fprintf(job.Stdout, format, args...)
205
+}
206
+
207
+func (job *Job) Errorf(format string, args ...interface{}) (n int, err error) {
208
+	return fmt.Fprintf(job.Stderr, format, args...)
209
+
201 210
 }
... ...
@@ -645,20 +645,17 @@ func TestReloadContainerLinks(t *testing.T) {
645 645
 }
646 646
 
647 647
 func TestDefaultContainerName(t *testing.T) {
648
-	runtime := mkRuntime(t)
648
+	eng := NewTestEngine(t)
649
+	srv := mkServerFromEngine(eng, t)
650
+	runtime := srv.runtime
649 651
 	defer nuke(runtime)
650
-	srv := &Server{runtime: runtime}
651 652
 
652 653
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
653 654
 	if err != nil {
654 655
 		t.Fatal(err)
655 656
 	}
656 657
 
657
-	shortId, _, err := srv.ContainerCreate(config, "some_name")
658
-	if err != nil {
659
-		t.Fatal(err)
660
-	}
661
-	container := runtime.Get(shortId)
658
+	container := runtime.Get(createNamedTestContainer(eng, config, t, "some_name"))
662 659
 	containerID := container.ID
663 660
 
664 661
 	if container.Name != "/some_name" {
... ...
@@ -682,20 +679,17 @@ func TestDefaultContainerName(t *testing.T) {
682 682
 }
683 683
 
684 684
 func TestRandomContainerName(t *testing.T) {
685
-	runtime := mkRuntime(t)
685
+	eng := NewTestEngine(t)
686
+	srv := mkServerFromEngine(eng, t)
687
+	runtime := srv.runtime
686 688
 	defer nuke(runtime)
687
-	srv := &Server{runtime: runtime}
688 689
 
689 690
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
690 691
 	if err != nil {
691 692
 		t.Fatal(err)
692 693
 	}
693 694
 
694
-	shortId, _, err := srv.ContainerCreate(config, "")
695
-	if err != nil {
696
-		t.Fatal(err)
697
-	}
698
-	container := runtime.Get(shortId)
695
+	container := runtime.Get(createTestContainer(eng, config, t))
699 696
 	containerID := container.ID
700 697
 
701 698
 	if container.Name == "" {
... ...
@@ -719,20 +713,17 @@ func TestRandomContainerName(t *testing.T) {
719 719
 }
720 720
 
721 721
 func TestLinkChildContainer(t *testing.T) {
722
-	runtime := mkRuntime(t)
722
+	eng := NewTestEngine(t)
723
+	srv := mkServerFromEngine(eng, t)
724
+	runtime := srv.runtime
723 725
 	defer nuke(runtime)
724
-	srv := &Server{runtime: runtime}
725 726
 
726 727
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
727 728
 	if err != nil {
728 729
 		t.Fatal(err)
729 730
 	}
730 731
 
731
-	shortId, _, err := srv.ContainerCreate(config, "/webapp")
732
-	if err != nil {
733
-		t.Fatal(err)
734
-	}
735
-	container := runtime.Get(shortId)
732
+	container := runtime.Get(createNamedTestContainer(eng, config, t, "/webapp"))
736 733
 
737 734
 	webapp, err := runtime.GetByName("/webapp")
738 735
 	if err != nil {
... ...
@@ -748,12 +739,7 @@ func TestLinkChildContainer(t *testing.T) {
748 748
 		t.Fatal(err)
749 749
 	}
750 750
 
751
-	shortId, _, err = srv.ContainerCreate(config, "")
752
-	if err != nil {
753
-		t.Fatal(err)
754
-	}
755
-
756
-	childContainer := runtime.Get(shortId)
751
+	childContainer := runtime.Get(createTestContainer(eng, config, t))
757 752
 
758 753
 	if err := runtime.RegisterLink(webapp, childContainer, "db"); err != nil {
759 754
 		t.Fatal(err)
... ...
@@ -770,20 +756,17 @@ func TestLinkChildContainer(t *testing.T) {
770 770
 }
771 771
 
772 772
 func TestGetAllChildren(t *testing.T) {
773
-	runtime := mkRuntime(t)
773
+	eng := NewTestEngine(t)
774
+	srv := mkServerFromEngine(eng, t)
775
+	runtime := srv.runtime
774 776
 	defer nuke(runtime)
775
-	srv := &Server{runtime: runtime}
776 777
 
777 778
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
778 779
 	if err != nil {
779 780
 		t.Fatal(err)
780 781
 	}
781 782
 
782
-	shortId, _, err := srv.ContainerCreate(config, "/webapp")
783
-	if err != nil {
784
-		t.Fatal(err)
785
-	}
786
-	container := runtime.Get(shortId)
783
+	container := runtime.Get(createNamedTestContainer(eng, config, t, "/webapp"))
787 784
 
788 785
 	webapp, err := runtime.GetByName("/webapp")
789 786
 	if err != nil {
... ...
@@ -799,12 +782,7 @@ func TestGetAllChildren(t *testing.T) {
799 799
 		t.Fatal(err)
800 800
 	}
801 801
 
802
-	shortId, _, err = srv.ContainerCreate(config, "")
803
-	if err != nil {
804
-		t.Fatal(err)
805
-	}
806
-
807
-	childContainer := runtime.Get(shortId)
802
+	childContainer := runtime.Get(createTestContainer(eng, config, t))
808 803
 
809 804
 	if err := runtime.RegisterLink(webapp, childContainer, "db"); err != nil {
810 805
 		t.Fatal(err)
... ...
@@ -62,6 +62,9 @@ func jobInitApi(job *engine.Job) string {
62 62
 		os.Exit(0)
63 63
 	}()
64 64
 	job.Eng.Hack_SetGlobalVar("httpapi.server", srv)
65
+	if err := job.Eng.Register("create", srv.ContainerCreate); err != nil {
66
+		return err.Error()
67
+	}
65 68
 	if err := job.Eng.Register("start", srv.ContainerStart); err != nil {
66 69
 		return err.Error()
67 70
 	}
... ...
@@ -1009,33 +1012,43 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
1009 1009
 	return nil
1010 1010
 }
1011 1011
 
1012
-func (srv *Server) ContainerCreate(config *Config, name string) (string, []string, error) {
1012
+func (srv *Server) ContainerCreate(job *engine.Job) string {
1013
+	var name string
1014
+	if len(job.Args) == 1 {
1015
+		name = job.Args[0]
1016
+	} else if len(job.Args) > 1 {
1017
+		return fmt.Sprintf("Usage: %s ", job.Name)
1018
+	}
1019
+	var config Config
1020
+	if err := job.ExportEnv(&config); err != nil {
1021
+		return err.Error()
1022
+	}
1013 1023
 	if config.Memory != 0 && config.Memory < 524288 {
1014
-		return "", nil, fmt.Errorf("Memory limit must be given in bytes (minimum 524288 bytes)")
1024
+		return "Memory limit must be given in bytes (minimum 524288 bytes)"
1015 1025
 	}
1016
-
1017 1026
 	if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
1018 1027
 		config.Memory = 0
1019 1028
 	}
1020
-
1021 1029
 	if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit {
1022 1030
 		config.MemorySwap = -1
1023 1031
 	}
1024
-	container, buildWarnings, err := srv.runtime.Create(config, name)
1032
+	container, buildWarnings, err := srv.runtime.Create(&config, name)
1025 1033
 	if err != nil {
1026 1034
 		if srv.runtime.graph.IsNotExist(err) {
1027
-
1028 1035
 			_, tag := utils.ParseRepositoryTag(config.Image)
1029 1036
 			if tag == "" {
1030 1037
 				tag = DEFAULTTAG
1031 1038
 			}
1032
-
1033
-			return "", nil, fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag)
1039
+			return fmt.Sprintf("No such image: %s (tag: %s)", config.Image, tag)
1034 1040
 		}
1035
-		return "", nil, err
1041
+		return err.Error()
1036 1042
 	}
1037 1043
 	srv.LogEvent("create", container.ShortID(), srv.runtime.repositories.ImageName(container.Image))
1038
-	return container.ShortID(), buildWarnings, nil
1044
+	job.Printf("%s\n", container.ShortID())
1045
+	for _, warning := range buildWarnings {
1046
+		job.Errorf("%s\n", warning)
1047
+	}
1048
+	return "0"
1039 1049
 }
1040 1050
 
1041 1051
 func (srv *Server) ContainerRestart(name string, t int) error {
... ...
@@ -79,20 +79,17 @@ func TestContainerTagImageDelete(t *testing.T) {
79 79
 }
80 80
 
81 81
 func TestCreateRm(t *testing.T) {
82
-	runtime := mkRuntime(t)
82
+	eng := NewTestEngine(t)
83
+	srv := mkServerFromEngine(eng, t)
84
+	runtime := srv.runtime
83 85
 	defer nuke(runtime)
84 86
 
85
-	srv := &Server{runtime: runtime}
86
-
87 87
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
88 88
 	if err != nil {
89 89
 		t.Fatal(err)
90 90
 	}
91 91
 
92
-	id, _, err := srv.ContainerCreate(config, "")
93
-	if err != nil {
94
-		t.Fatal(err)
95
-	}
92
+	id := createTestContainer(eng, config, t)
96 93
 
97 94
 	if len(runtime.List()) != 1 {
98 95
 		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
... ...
@@ -120,10 +117,7 @@ func TestCreateRmVolumes(t *testing.T) {
120 120
 		t.Fatal(err)
121 121
 	}
122 122
 
123
-	id, _, err := srv.ContainerCreate(config, "")
124
-	if err != nil {
125
-		t.Fatal(err)
126
-	}
123
+	id := createTestContainer(eng, config, t)
127 124
 
128 125
 	if len(runtime.List()) != 1 {
129 126
 		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
... ...
@@ -152,20 +146,17 @@ func TestCreateRmVolumes(t *testing.T) {
152 152
 }
153 153
 
154 154
 func TestCommit(t *testing.T) {
155
-	runtime := mkRuntime(t)
155
+	eng := NewTestEngine(t)
156
+	srv := mkServerFromEngine(eng, t)
157
+	runtime := srv.runtime
156 158
 	defer nuke(runtime)
157 159
 
158
-	srv := &Server{runtime: runtime}
159
-
160 160
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
161 161
 	if err != nil {
162 162
 		t.Fatal(err)
163 163
 	}
164 164
 
165
-	id, _, err := srv.ContainerCreate(config, "")
166
-	if err != nil {
167
-		t.Fatal(err)
168
-	}
165
+	id := createTestContainer(eng, config, t)
169 166
 
170 167
 	if _, err := srv.ContainerCommit(id, "testrepo", "testtag", "", "", config); err != nil {
171 168
 		t.Fatal(err)
... ...
@@ -183,10 +174,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
183 183
 		t.Fatal(err)
184 184
 	}
185 185
 
186
-	id, _, err := srv.ContainerCreate(config, "")
187
-	if err != nil {
188
-		t.Fatal(err)
189
-	}
186
+	id := createTestContainer(eng, config, t)
190 187
 
191 188
 	if len(runtime.List()) != 1 {
192 189
 		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
... ...
@@ -232,22 +220,22 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
232 232
 }
233 233
 
234 234
 func TestRunWithTooLowMemoryLimit(t *testing.T) {
235
-	runtime := mkRuntime(t)
235
+	eng := NewTestEngine(t)
236
+	srv := mkServerFromEngine(eng, t)
237
+	runtime := srv.runtime
236 238
 	defer nuke(runtime)
237 239
 
238 240
 	// Try to create a container with a memory limit of 1 byte less than the minimum allowed limit.
239
-	if _, _, err := (*Server).ContainerCreate(&Server{runtime: runtime},
240
-		&Config{
241
-			Image:     GetTestImage(runtime).ID,
242
-			Memory:    524287,
243
-			CpuShares: 1000,
244
-			Cmd:       []string{"/bin/cat"},
245
-		},
246
-		"",
247
-	); err == nil {
241
+	job := eng.Job("create")
242
+	job.Setenv("Image", GetTestImage(runtime).ID)
243
+	job.Setenv("Memory", "524287")
244
+	job.Setenv("CpuShares", "1000")
245
+	job.SetenvList("Cmd", []string{"/bin/cat"})
246
+	var id string
247
+	job.StdoutParseString(&id)
248
+	if err := job.Run(); err == nil {
248 249
 		t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!")
249 250
 	}
250
-
251 251
 }
252 252
 
253 253
 func TestContainerTop(t *testing.T) {
... ...
@@ -411,10 +399,7 @@ func TestRmi(t *testing.T) {
411 411
 		t.Fatal(err)
412 412
 	}
413 413
 
414
-	containerID, _, err := srv.ContainerCreate(config, "")
415
-	if err != nil {
416
-		t.Fatal(err)
417
-	}
414
+	containerID := createTestContainer(eng, config, t)
418 415
 
419 416
 	//To remove
420 417
 	job := eng.Job("start", containerID)
... ...
@@ -435,10 +420,7 @@ func TestRmi(t *testing.T) {
435 435
 		t.Fatal(err)
436 436
 	}
437 437
 
438
-	containerID, _, err = srv.ContainerCreate(config, "")
439
-	if err != nil {
440
-		t.Fatal(err)
441
-	}
438
+	containerID = createTestContainer(eng, config, t)
442 439
 
443 440
 	//To remove
444 441
 	job = eng.Job("start", containerID)
... ...
@@ -38,6 +38,22 @@ func mkRuntime(f utils.Fataler) *Runtime {
38 38
 	return r
39 39
 }
40 40
 
41
+func createNamedTestContainer(eng *engine.Engine, config *Config, f utils.Fataler, name string) (shortId string) {
42
+	job := eng.Job("create", name)
43
+	if err := job.ImportEnv(config); err != nil {
44
+		f.Fatal(err)
45
+	}
46
+	job.StdoutParseString(&shortId)
47
+	if err := job.Run(); err != nil {
48
+		f.Fatal(err)
49
+	}
50
+	return
51
+}
52
+
53
+func createTestContainer(eng *engine.Engine, config *Config, f utils.Fataler) (shortId string) {
54
+	return createNamedTestContainer(eng, config, f, "")
55
+}
56
+
41 57
 func mkServerFromEngine(eng *engine.Engine, t utils.Fataler) *Server {
42 58
 	iSrv := eng.Hack_GetGlobalVar("httpapi.server")
43 59
 	if iSrv == nil {