Browse code

Merge pull request #7452 from shykes/7370-on-7427

Cleanup: move image management and logging out of deprecated Server

Solomon Hykes authored on 2014/08/07 10:47:45
Showing 33 changed files
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"github.com/docker/docker/daemon/networkdriver/bridge"
9 9
 	"github.com/docker/docker/dockerversion"
10 10
 	"github.com/docker/docker/engine"
11
+	"github.com/docker/docker/events"
11 12
 	"github.com/docker/docker/pkg/parsers/kernel"
12 13
 	"github.com/docker/docker/registry"
13 14
 	"github.com/docker/docker/server"
... ...
@@ -20,6 +21,9 @@ func Register(eng *engine.Engine) error {
20 20
 	if err := remote(eng); err != nil {
21 21
 		return err
22 22
 	}
23
+	if err := events.New().Install(eng); err != nil {
24
+		return err
25
+	}
23 26
 	if err := eng.Register("version", dockerVersion); err != nil {
24 27
 		return err
25 28
 	}
... ...
@@ -168,6 +168,13 @@ func (container *Container) WriteHostConfig() error {
168 168
 	return ioutil.WriteFile(pth, data, 0666)
169 169
 }
170 170
 
171
+func (container *Container) LogEvent(action string) {
172
+	d := container.daemon
173
+	if err := d.eng.Job("log", action, container.ID, d.Repositories().ImageName(container.Image)).Run(); err != nil {
174
+		utils.Errorf("Error running container: %s", err)
175
+	}
176
+}
177
+
171 178
 func (container *Container) getResourcePath(path string) (string, error) {
172 179
 	cleanPath := filepath.Join("/", path)
173 180
 	return symlink.FollowSymlinkInScope(filepath.Join(container.basefs, cleanPath), container.basefs)
... ...
@@ -508,7 +515,7 @@ func (container *Container) monitor(callback execdriver.StartCallback) error {
508 508
 		container.stdin, container.stdinPipe = io.Pipe()
509 509
 	}
510 510
 	if container.daemon != nil && container.daemon.srv != nil {
511
-		container.daemon.srv.LogEvent("die", container.ID, container.daemon.repositories.ImageName(container.Image))
511
+		container.LogEvent("die")
512 512
 	}
513 513
 	if container.daemon != nil && container.daemon.srv != nil && container.daemon.srv.IsRunning() {
514 514
 		// FIXME: here is race condition between two RUN instructions in Dockerfile
... ...
@@ -40,7 +40,7 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) engine.Status {
40 40
 	if !container.Config.NetworkDisabled && daemon.SystemConfig().IPv4ForwardingDisabled {
41 41
 		job.Errorf("IPv4 forwarding is disabled.\n")
42 42
 	}
43
-	job.Eng.Job("log", "create", container.ID, daemon.Repositories().ImageName(container.Image)).Run()
43
+	container.LogEvent("create")
44 44
 	// FIXME: this is necessary because daemon.Create might return a nil container
45 45
 	// with a non-nil error. This should not happen! Once it's fixed we
46 46
 	// can remove this workaround.
... ...
@@ -107,6 +107,7 @@ type Daemon struct {
107 107
 func (daemon *Daemon) Install(eng *engine.Engine) error {
108 108
 	// FIXME: rename "delete" to "rm" for consistency with the CLI command
109 109
 	// FIXME: rename ContainerDestroy to ContainerRm for consistency with the CLI command
110
+	// FIXME: remove ImageDelete's dependency on Daemon, then move to graph/
110 111
 	for name, method := range map[string]engine.Handler{
111 112
 		"attach":            daemon.ContainerAttach,
112 113
 		"commit":            daemon.ContainerCommit,
... ...
@@ -127,6 +128,7 @@ func (daemon *Daemon) Install(eng *engine.Engine) error {
127 127
 		"top":               daemon.ContainerTop,
128 128
 		"unpause":           daemon.ContainerUnpause,
129 129
 		"wait":              daemon.ContainerWait,
130
+		"image_delete":      daemon.ImageDelete, // FIXME: see above
130 131
 	} {
131 132
 		if err := eng.Register(name, method); err != nil {
132 133
 			return err
... ...
@@ -70,7 +70,7 @@ func (daemon *Daemon) ContainerDestroy(job *engine.Job) engine.Status {
70 70
 		if err := daemon.Destroy(container); err != nil {
71 71
 			return job.Errorf("Cannot destroy container %s: %s", name, err)
72 72
 		}
73
-		job.Eng.Job("log", "destroy", container.ID, daemon.Repositories().ImageName(container.Image)).Run()
73
+		container.LogEvent("destroy")
74 74
 
75 75
 		if removeVolume {
76 76
 			var (
... ...
@@ -23,7 +23,7 @@ func (daemon *Daemon) ContainerExport(job *engine.Job) engine.Status {
23 23
 			return job.Errorf("%s: %s", name, err)
24 24
 		}
25 25
 		// FIXME: factor job-specific LogEvent to engine.Job.Run()
26
-		job.Eng.Job("log", "export", container.ID, daemon.Repositories().ImageName(container.Image)).Run()
26
+		container.LogEvent("export")
27 27
 		return engine.StatusOK
28 28
 	}
29 29
 	return job.Errorf("No such container: %s", name)
30 30
new file mode 100644
... ...
@@ -0,0 +1,156 @@
0
+package daemon
1
+
2
+import (
3
+	"fmt"
4
+	"strings"
5
+
6
+	"github.com/docker/docker/engine"
7
+	"github.com/docker/docker/graph"
8
+	"github.com/docker/docker/image"
9
+	"github.com/docker/docker/pkg/parsers"
10
+	"github.com/docker/docker/utils"
11
+)
12
+
13
+func (daemon *Daemon) ImageDelete(job *engine.Job) engine.Status {
14
+	if n := len(job.Args); n != 1 {
15
+		return job.Errorf("Usage: %s IMAGE", job.Name)
16
+	}
17
+	imgs := engine.NewTable("", 0)
18
+	if err := daemon.DeleteImage(job.Eng, job.Args[0], imgs, true, job.GetenvBool("force"), job.GetenvBool("noprune")); err != nil {
19
+		return job.Error(err)
20
+	}
21
+	if len(imgs.Data) == 0 {
22
+		return job.Errorf("Conflict, %s wasn't deleted", job.Args[0])
23
+	}
24
+	if _, err := imgs.WriteListTo(job.Stdout); err != nil {
25
+		return job.Error(err)
26
+	}
27
+	return engine.StatusOK
28
+}
29
+
30
+// FIXME: make this private and use the job instead
31
+func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, imgs *engine.Table, first, force, noprune bool) error {
32
+	var (
33
+		repoName, tag string
34
+		tags          = []string{}
35
+		tagDeleted    bool
36
+	)
37
+
38
+	// FIXME: please respect DRY and centralize repo+tag parsing in a single central place! -- shykes
39
+	repoName, tag = parsers.ParseRepositoryTag(name)
40
+	if tag == "" {
41
+		tag = graph.DEFAULTTAG
42
+	}
43
+
44
+	img, err := daemon.Repositories().LookupImage(name)
45
+	if err != nil {
46
+		if r, _ := daemon.Repositories().Get(repoName); r != nil {
47
+			return fmt.Errorf("No such image: %s:%s", repoName, tag)
48
+		}
49
+		return fmt.Errorf("No such image: %s", name)
50
+	}
51
+
52
+	if strings.Contains(img.ID, name) {
53
+		repoName = ""
54
+		tag = ""
55
+	}
56
+
57
+	byParents, err := daemon.Graph().ByParent()
58
+	if err != nil {
59
+		return err
60
+	}
61
+
62
+	//If delete by id, see if the id belong only to one repository
63
+	if repoName == "" {
64
+		for _, repoAndTag := range daemon.Repositories().ByID()[img.ID] {
65
+			parsedRepo, parsedTag := parsers.ParseRepositoryTag(repoAndTag)
66
+			if repoName == "" || repoName == parsedRepo {
67
+				repoName = parsedRepo
68
+				if parsedTag != "" {
69
+					tags = append(tags, parsedTag)
70
+				}
71
+			} else if repoName != parsedRepo && !force {
72
+				// the id belongs to multiple repos, like base:latest and user:test,
73
+				// in that case return conflict
74
+				return fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", name)
75
+			}
76
+		}
77
+	} else {
78
+		tags = append(tags, tag)
79
+	}
80
+
81
+	if !first && len(tags) > 0 {
82
+		return nil
83
+	}
84
+
85
+	//Untag the current image
86
+	for _, tag := range tags {
87
+		tagDeleted, err = daemon.Repositories().Delete(repoName, tag)
88
+		if err != nil {
89
+			return err
90
+		}
91
+		if tagDeleted {
92
+			out := &engine.Env{}
93
+			out.Set("Untagged", repoName+":"+tag)
94
+			imgs.Add(out)
95
+			eng.Job("log", "untag", img.ID, "").Run()
96
+		}
97
+	}
98
+	tags = daemon.Repositories().ByID()[img.ID]
99
+	if (len(tags) <= 1 && repoName == "") || len(tags) == 0 {
100
+		if len(byParents[img.ID]) == 0 {
101
+			if err := daemon.canDeleteImage(img.ID, force, tagDeleted); err != nil {
102
+				return err
103
+			}
104
+			if err := daemon.Repositories().DeleteAll(img.ID); err != nil {
105
+				return err
106
+			}
107
+			if err := daemon.Graph().Delete(img.ID); err != nil {
108
+				return err
109
+			}
110
+			out := &engine.Env{}
111
+			out.Set("Deleted", img.ID)
112
+			imgs.Add(out)
113
+			eng.Job("log", "delete", img.ID, "").Run()
114
+			if img.Parent != "" && !noprune {
115
+				err := daemon.DeleteImage(eng, img.Parent, imgs, false, force, noprune)
116
+				if first {
117
+					return err
118
+				}
119
+
120
+			}
121
+
122
+		}
123
+	}
124
+	return nil
125
+}
126
+
127
+func (daemon *Daemon) canDeleteImage(imgID string, force, untagged bool) error {
128
+	var message string
129
+	if untagged {
130
+		message = " (docker untagged the image)"
131
+	}
132
+	for _, container := range daemon.List() {
133
+		parent, err := daemon.Repositories().LookupImage(container.Image)
134
+		if err != nil {
135
+			return err
136
+		}
137
+
138
+		if err := parent.WalkHistory(func(p *image.Image) error {
139
+			if imgID == p.ID {
140
+				if container.State.IsRunning() {
141
+					if force {
142
+						return fmt.Errorf("Conflict, cannot force delete %s because the running container %s is using it%s, stop it and retry", utils.TruncateID(imgID), utils.TruncateID(container.ID), message)
143
+					}
144
+					return fmt.Errorf("Conflict, cannot delete %s because the running container %s is using it%s, stop it and use -f to force", utils.TruncateID(imgID), utils.TruncateID(container.ID), message)
145
+				} else if !force {
146
+					return fmt.Errorf("Conflict, cannot delete %s because the container %s is using it%s, use -f to force", utils.TruncateID(imgID), utils.TruncateID(container.ID), message)
147
+				}
148
+			}
149
+			return nil
150
+		}); err != nil {
151
+			return err
152
+		}
153
+	}
154
+	return nil
155
+}
... ...
@@ -44,7 +44,7 @@ func (daemon *Daemon) ContainerKill(job *engine.Job) engine.Status {
44 44
 			if err := container.Kill(); err != nil {
45 45
 				return job.Errorf("Cannot kill container %s: %s", name, err)
46 46
 			}
47
-			job.Eng.Job("log", "kill", container.ID, daemon.Repositories().ImageName(container.Image)).Run()
47
+			container.LogEvent("kill")
48 48
 		} else {
49 49
 			// Otherwise, just send the requested signal
50 50
 			if err := container.KillSig(int(sig)); err != nil {
... ...
@@ -16,7 +16,7 @@ func (daemon *Daemon) ContainerPause(job *engine.Job) engine.Status {
16 16
 	if err := container.Pause(); err != nil {
17 17
 		return job.Errorf("Cannot pause container %s: %s", name, err)
18 18
 	}
19
-	job.Eng.Job("log", "pause", container.ID, daemon.Repositories().ImageName(container.Image)).Run()
19
+	container.LogEvent("pause")
20 20
 	return engine.StatusOK
21 21
 }
22 22
 
... ...
@@ -32,6 +32,6 @@ func (daemon *Daemon) ContainerUnpause(job *engine.Job) engine.Status {
32 32
 	if err := container.Unpause(); err != nil {
33 33
 		return job.Errorf("Cannot unpause container %s: %s", name, err)
34 34
 	}
35
-	job.Eng.Job("log", "unpause", container.ID, daemon.Repositories().ImageName(container.Image)).Run()
35
+	container.LogEvent("unpause")
36 36
 	return engine.StatusOK
37 37
 }
... ...
@@ -19,7 +19,7 @@ func (daemon *Daemon) ContainerRestart(job *engine.Job) engine.Status {
19 19
 		if err := container.Restart(int(t)); err != nil {
20 20
 			return job.Errorf("Cannot restart container %s: %s\n", name, err)
21 21
 		}
22
-		job.Eng.Job("log", "restart", container.ID, daemon.Repositories().ImageName(container.Image)).Run()
22
+		container.LogEvent("restart")
23 23
 	} else {
24 24
 		return job.Errorf("No such container: %s\n", name)
25 25
 	}
... ...
@@ -1,10 +1,5 @@
1 1
 package daemon
2 2
 
3
-import (
4
-	"github.com/docker/docker/utils"
5
-)
6
-
7 3
 type Server interface {
8
-	LogEvent(action, id, from string) *utils.JSONMessage
9 4
 	IsRunning() bool // returns true if the server is currently in operation
10 5
 }
... ...
@@ -36,8 +36,7 @@ func (daemon *Daemon) ContainerStart(job *engine.Job) engine.Status {
36 36
 	if err := container.Start(); err != nil {
37 37
 		return job.Errorf("Cannot start container %s: %s", name, err)
38 38
 	}
39
-	job.Eng.Job("log", "start", container.ID, daemon.Repositories().ImageName(container.Image)).Run()
40
-
39
+	container.LogEvent("start")
41 40
 	return engine.StatusOK
42 41
 }
43 42
 
... ...
@@ -22,7 +22,7 @@ func (daemon *Daemon) ContainerStop(job *engine.Job) engine.Status {
22 22
 		if err := container.Stop(int(t)); err != nil {
23 23
 			return job.Errorf("Cannot stop container %s: %s\n", name, err)
24 24
 		}
25
-		job.Eng.Job("log", "stop", container.ID, daemon.Repositories().ImageName(container.Image)).Run()
25
+		container.LogEvent("stop")
26 26
 	} else {
27 27
 		return job.Errorf("No such container: %s\n", name)
28 28
 	}
29 29
new file mode 100644
... ...
@@ -0,0 +1,176 @@
0
+package events
1
+
2
+import (
3
+	"encoding/json"
4
+	"sync"
5
+	"time"
6
+
7
+	"github.com/docker/docker/engine"
8
+	"github.com/docker/docker/utils"
9
+)
10
+
11
+const eventsLimit = 64
12
+
13
+type listener chan<- *utils.JSONMessage
14
+
15
+type Events struct {
16
+	mu          sync.RWMutex
17
+	events      []*utils.JSONMessage
18
+	subscribers []listener
19
+}
20
+
21
+func New() *Events {
22
+	return &Events{
23
+		events: make([]*utils.JSONMessage, 0, eventsLimit),
24
+	}
25
+}
26
+
27
+// Install installs events public api in docker engine
28
+func (e *Events) Install(eng *engine.Engine) error {
29
+	// Here you should describe public interface
30
+	jobs := map[string]engine.Handler{
31
+		"events":            e.Get,
32
+		"log":               e.Log,
33
+		"subscribers_count": e.SubscribersCount,
34
+	}
35
+	for name, job := range jobs {
36
+		if err := eng.Register(name, job); err != nil {
37
+			return err
38
+		}
39
+	}
40
+	return nil
41
+}
42
+
43
+func (e *Events) Get(job *engine.Job) engine.Status {
44
+	var (
45
+		since   = job.GetenvInt64("since")
46
+		until   = job.GetenvInt64("until")
47
+		timeout = time.NewTimer(time.Unix(until, 0).Sub(time.Now()))
48
+	)
49
+
50
+	// If no until, disable timeout
51
+	if until == 0 {
52
+		timeout.Stop()
53
+	}
54
+
55
+	listener := make(chan *utils.JSONMessage)
56
+	e.subscribe(listener)
57
+	defer e.unsubscribe(listener)
58
+
59
+	job.Stdout.Write(nil)
60
+
61
+	// Resend every event in the [since, until] time interval.
62
+	if since != 0 {
63
+		if err := e.writeCurrent(job, since, until); err != nil {
64
+			return job.Error(err)
65
+		}
66
+	}
67
+
68
+	for {
69
+		select {
70
+		case event, ok := <-listener:
71
+			if !ok {
72
+				return engine.StatusOK
73
+			}
74
+			if err := writeEvent(job, event); err != nil {
75
+				return job.Error(err)
76
+			}
77
+		case <-timeout.C:
78
+			return engine.StatusOK
79
+		}
80
+	}
81
+}
82
+
83
+func (e *Events) Log(job *engine.Job) engine.Status {
84
+	if len(job.Args) != 3 {
85
+		return job.Errorf("usage: %s ACTION ID FROM", job.Name)
86
+	}
87
+	// not waiting for receivers
88
+	go e.log(job.Args[0], job.Args[1], job.Args[2])
89
+	return engine.StatusOK
90
+}
91
+
92
+func (e *Events) SubscribersCount(job *engine.Job) engine.Status {
93
+	ret := &engine.Env{}
94
+	ret.SetInt("count", e.subscribersCount())
95
+	ret.WriteTo(job.Stdout)
96
+	return engine.StatusOK
97
+}
98
+
99
+func writeEvent(job *engine.Job, event *utils.JSONMessage) error {
100
+	// When sending an event JSON serialization errors are ignored, but all
101
+	// other errors lead to the eviction of the listener.
102
+	if b, err := json.Marshal(event); err == nil {
103
+		if _, err = job.Stdout.Write(b); err != nil {
104
+			return err
105
+		}
106
+	}
107
+	return nil
108
+}
109
+
110
+func (e *Events) writeCurrent(job *engine.Job, since, until int64) error {
111
+	e.mu.RLock()
112
+	for _, event := range e.events {
113
+		if event.Time >= since && (event.Time <= until || until == 0) {
114
+			if err := writeEvent(job, event); err != nil {
115
+				e.mu.RUnlock()
116
+				return err
117
+			}
118
+		}
119
+	}
120
+	e.mu.RUnlock()
121
+	return nil
122
+}
123
+
124
+func (e *Events) subscribersCount() int {
125
+	e.mu.RLock()
126
+	c := len(e.subscribers)
127
+	e.mu.RUnlock()
128
+	return c
129
+}
130
+
131
+func (e *Events) log(action, id, from string) {
132
+	e.mu.Lock()
133
+	now := time.Now().UTC().Unix()
134
+	jm := &utils.JSONMessage{Status: action, ID: id, From: from, Time: now}
135
+	if len(e.events) == cap(e.events) {
136
+		// discard oldest event
137
+		copy(e.events, e.events[1:])
138
+		e.events[len(e.events)-1] = jm
139
+	} else {
140
+		e.events = append(e.events, jm)
141
+	}
142
+	for _, s := range e.subscribers {
143
+		// We give each subscriber a 100ms time window to receive the event,
144
+		// after which we move to the next.
145
+		select {
146
+		case s <- jm:
147
+		case <-time.After(100 * time.Millisecond):
148
+		}
149
+	}
150
+	e.mu.Unlock()
151
+}
152
+
153
+func (e *Events) subscribe(l listener) {
154
+	e.mu.Lock()
155
+	e.subscribers = append(e.subscribers, l)
156
+	e.mu.Unlock()
157
+}
158
+
159
+// unsubscribe closes and removes the specified listener from the list of
160
+// previously registed ones.
161
+// It returns a boolean value indicating if the listener was successfully
162
+// found, closed and unregistered.
163
+func (e *Events) unsubscribe(l listener) bool {
164
+	e.mu.Lock()
165
+	for i, subscriber := range e.subscribers {
166
+		if subscriber == l {
167
+			close(l)
168
+			e.subscribers = append(e.subscribers[:i], e.subscribers[i+1:]...)
169
+			e.mu.Unlock()
170
+			return true
171
+		}
172
+	}
173
+	e.mu.Unlock()
174
+	return false
175
+}
0 176
new file mode 100644
... ...
@@ -0,0 +1,154 @@
0
+package events
1
+
2
+import (
3
+	"bytes"
4
+	"encoding/json"
5
+	"fmt"
6
+	"io"
7
+	"testing"
8
+	"time"
9
+
10
+	"github.com/docker/docker/engine"
11
+	"github.com/docker/docker/utils"
12
+)
13
+
14
+func TestEventsPublish(t *testing.T) {
15
+	e := New()
16
+	l1 := make(chan *utils.JSONMessage)
17
+	l2 := make(chan *utils.JSONMessage)
18
+	e.subscribe(l1)
19
+	e.subscribe(l2)
20
+	count := e.subscribersCount()
21
+	if count != 2 {
22
+		t.Fatalf("Must be 2 subscribers, got %d", count)
23
+	}
24
+	go e.log("test", "cont", "image")
25
+	select {
26
+	case msg := <-l1:
27
+		if len(e.events) != 1 {
28
+			t.Fatalf("Must be only one event, got %d", len(e.events))
29
+		}
30
+		if msg.Status != "test" {
31
+			t.Fatalf("Status should be test, got %s", msg.Status)
32
+		}
33
+		if msg.ID != "cont" {
34
+			t.Fatalf("ID should be cont, got %s", msg.ID)
35
+		}
36
+		if msg.From != "image" {
37
+			t.Fatalf("From should be image, got %s", msg.From)
38
+		}
39
+	case <-time.After(1 * time.Second):
40
+		t.Fatal("Timeout waiting for broadcasted message")
41
+	}
42
+	select {
43
+	case msg := <-l2:
44
+		if len(e.events) != 1 {
45
+			t.Fatalf("Must be only one event, got %d", len(e.events))
46
+		}
47
+		if msg.Status != "test" {
48
+			t.Fatalf("Status should be test, got %s", msg.Status)
49
+		}
50
+		if msg.ID != "cont" {
51
+			t.Fatalf("ID should be cont, got %s", msg.ID)
52
+		}
53
+		if msg.From != "image" {
54
+			t.Fatalf("From should be image, got %s", msg.From)
55
+		}
56
+	case <-time.After(1 * time.Second):
57
+		t.Fatal("Timeout waiting for broadcasted message")
58
+	}
59
+}
60
+
61
+func TestEventsPublishTimeout(t *testing.T) {
62
+	e := New()
63
+	l := make(chan *utils.JSONMessage)
64
+	e.subscribe(l)
65
+
66
+	c := make(chan struct{})
67
+	go func() {
68
+		e.log("test", "cont", "image")
69
+		close(c)
70
+	}()
71
+
72
+	select {
73
+	case <-c:
74
+	case <-time.After(time.Second):
75
+		t.Fatal("Timeout publishing message")
76
+	}
77
+}
78
+
79
+func TestLogEvents(t *testing.T) {
80
+	e := New()
81
+	eng := engine.New()
82
+	if err := e.Install(eng); err != nil {
83
+		t.Fatal(err)
84
+	}
85
+
86
+	for i := 0; i < eventsLimit+16; i++ {
87
+		action := fmt.Sprintf("action_%d", i)
88
+		id := fmt.Sprintf("cont_%d", i)
89
+		from := fmt.Sprintf("image_%d", i)
90
+		job := eng.Job("log", action, id, from)
91
+		if err := job.Run(); err != nil {
92
+			t.Fatal(err)
93
+		}
94
+	}
95
+	time.Sleep(50 * time.Millisecond)
96
+	if len(e.events) != eventsLimit {
97
+		t.Fatalf("Must be %d events, got %d", eventsLimit, len(e.events))
98
+	}
99
+
100
+	job := eng.Job("events")
101
+	job.SetenvInt64("since", 1)
102
+	job.SetenvInt64("until", time.Now().Unix())
103
+	buf := bytes.NewBuffer(nil)
104
+	job.Stdout.Add(buf)
105
+	if err := job.Run(); err != nil {
106
+		t.Fatal(err)
107
+	}
108
+	buf = bytes.NewBuffer(buf.Bytes())
109
+	dec := json.NewDecoder(buf)
110
+	var msgs []utils.JSONMessage
111
+	for {
112
+		var jm utils.JSONMessage
113
+		if err := dec.Decode(&jm); err != nil {
114
+			if err == io.EOF {
115
+				break
116
+			}
117
+			t.Fatal(err)
118
+		}
119
+		msgs = append(msgs, jm)
120
+	}
121
+	if len(msgs) != eventsLimit {
122
+		t.Fatalf("Must be %d events, got %d", eventsLimit, len(msgs))
123
+	}
124
+	first := msgs[0]
125
+	if first.Status != "action_16" {
126
+		t.Fatalf("First action is %s, must be action_15", first.Status)
127
+	}
128
+	last := msgs[len(msgs)-1]
129
+	if last.Status != "action_79" {
130
+		t.Fatalf("First action is %s, must be action_79", first.Status)
131
+	}
132
+}
133
+
134
+func TestEventsCountJob(t *testing.T) {
135
+	e := New()
136
+	eng := engine.New()
137
+	if err := e.Install(eng); err != nil {
138
+		t.Fatal(err)
139
+	}
140
+	l1 := make(chan *utils.JSONMessage)
141
+	l2 := make(chan *utils.JSONMessage)
142
+	e.subscribe(l1)
143
+	e.subscribe(l2)
144
+	job := eng.Job("subscribers_count")
145
+	env, _ := job.Stdout.AddEnv()
146
+	if err := job.Run(); err != nil {
147
+		t.Fatal(err)
148
+	}
149
+	count := env.GetInt("count")
150
+	if count != 2 {
151
+		t.Fatalf("There must be 2 subscribers, got %d", count)
152
+	}
153
+}
0 154
new file mode 100644
... ...
@@ -0,0 +1,147 @@
0
+package graph
1
+
2
+import (
3
+	"encoding/json"
4
+	"io"
5
+	"io/ioutil"
6
+	"os"
7
+	"path"
8
+
9
+	"github.com/docker/docker/archive"
10
+	"github.com/docker/docker/engine"
11
+	"github.com/docker/docker/pkg/parsers"
12
+	"github.com/docker/docker/utils"
13
+)
14
+
15
+// CmdImageExport exports all images with the given tag. All versions
16
+// containing the same tag are exported. The resulting output is an
17
+// uncompressed tar ball.
18
+// name is the set of tags to export.
19
+// out is the writer where the images are written to.
20
+func (s *TagStore) CmdImageExport(job *engine.Job) engine.Status {
21
+	if len(job.Args) != 1 {
22
+		return job.Errorf("Usage: %s IMAGE\n", job.Name)
23
+	}
24
+	name := job.Args[0]
25
+	// get image json
26
+	tempdir, err := ioutil.TempDir("", "docker-export-")
27
+	if err != nil {
28
+		return job.Error(err)
29
+	}
30
+	defer os.RemoveAll(tempdir)
31
+
32
+	utils.Debugf("Serializing %s", name)
33
+
34
+	rootRepoMap := map[string]Repository{}
35
+	rootRepo, err := s.Get(name)
36
+	if err != nil {
37
+		return job.Error(err)
38
+	}
39
+	if rootRepo != nil {
40
+		// this is a base repo name, like 'busybox'
41
+
42
+		for _, id := range rootRepo {
43
+			if err := s.exportImage(job.Eng, id, tempdir); err != nil {
44
+				return job.Error(err)
45
+			}
46
+		}
47
+		rootRepoMap[name] = rootRepo
48
+	} else {
49
+		img, err := s.LookupImage(name)
50
+		if err != nil {
51
+			return job.Error(err)
52
+		}
53
+		if img != nil {
54
+			// This is a named image like 'busybox:latest'
55
+			repoName, repoTag := parsers.ParseRepositoryTag(name)
56
+			if err := s.exportImage(job.Eng, img.ID, tempdir); err != nil {
57
+				return job.Error(err)
58
+			}
59
+			// check this length, because a lookup of a truncated has will not have a tag
60
+			// and will not need to be added to this map
61
+			if len(repoTag) > 0 {
62
+				rootRepoMap[repoName] = Repository{repoTag: img.ID}
63
+			}
64
+		} else {
65
+			// this must be an ID that didn't get looked up just right?
66
+			if err := s.exportImage(job.Eng, name, tempdir); err != nil {
67
+				return job.Error(err)
68
+			}
69
+		}
70
+	}
71
+	// write repositories, if there is something to write
72
+	if len(rootRepoMap) > 0 {
73
+		rootRepoJson, _ := json.Marshal(rootRepoMap)
74
+
75
+		if err := ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.FileMode(0644)); err != nil {
76
+			return job.Error(err)
77
+		}
78
+	} else {
79
+		utils.Debugf("There were no repositories to write")
80
+	}
81
+
82
+	fs, err := archive.Tar(tempdir, archive.Uncompressed)
83
+	if err != nil {
84
+		return job.Error(err)
85
+	}
86
+	defer fs.Close()
87
+
88
+	if _, err := io.Copy(job.Stdout, fs); err != nil {
89
+		return job.Error(err)
90
+	}
91
+	utils.Debugf("End Serializing %s", name)
92
+	return engine.StatusOK
93
+}
94
+
95
+// FIXME: this should be a top-level function, not a class method
96
+func (s *TagStore) exportImage(eng *engine.Engine, name, tempdir string) error {
97
+	for n := name; n != ""; {
98
+		// temporary directory
99
+		tmpImageDir := path.Join(tempdir, n)
100
+		if err := os.Mkdir(tmpImageDir, os.FileMode(0755)); err != nil {
101
+			if os.IsExist(err) {
102
+				return nil
103
+			}
104
+			return err
105
+		}
106
+
107
+		var version = "1.0"
108
+		var versionBuf = []byte(version)
109
+
110
+		if err := ioutil.WriteFile(path.Join(tmpImageDir, "VERSION"), versionBuf, os.FileMode(0644)); err != nil {
111
+			return err
112
+		}
113
+
114
+		// serialize json
115
+		json, err := os.Create(path.Join(tmpImageDir, "json"))
116
+		if err != nil {
117
+			return err
118
+		}
119
+		job := eng.Job("image_inspect", n)
120
+		job.SetenvBool("raw", true)
121
+		job.Stdout.Add(json)
122
+		if err := job.Run(); err != nil {
123
+			return err
124
+		}
125
+
126
+		// serialize filesystem
127
+		fsTar, err := os.Create(path.Join(tmpImageDir, "layer.tar"))
128
+		if err != nil {
129
+			return err
130
+		}
131
+		job = eng.Job("image_tarlayer", n)
132
+		job.Stdout.Add(fsTar)
133
+		if err := job.Run(); err != nil {
134
+			return err
135
+		}
136
+
137
+		// find parent
138
+		job = eng.Job("image_get", n)
139
+		info, _ := job.Stdout.AddEnv()
140
+		if err := job.Run(); err != nil {
141
+			return err
142
+		}
143
+		n = info.Get("Parent")
144
+	}
145
+	return nil
146
+}
0 147
new file mode 100644
... ...
@@ -0,0 +1,46 @@
0
+package graph
1
+
2
+import (
3
+	"strings"
4
+
5
+	"github.com/docker/docker/engine"
6
+	"github.com/docker/docker/image"
7
+)
8
+
9
+func (s *TagStore) CmdHistory(job *engine.Job) engine.Status {
10
+	if n := len(job.Args); n != 1 {
11
+		return job.Errorf("Usage: %s IMAGE", job.Name)
12
+	}
13
+	name := job.Args[0]
14
+	foundImage, err := s.LookupImage(name)
15
+	if err != nil {
16
+		return job.Error(err)
17
+	}
18
+
19
+	lookupMap := make(map[string][]string)
20
+	for name, repository := range s.Repositories {
21
+		for tag, id := range repository {
22
+			// If the ID already has a reverse lookup, do not update it unless for "latest"
23
+			if _, exists := lookupMap[id]; !exists {
24
+				lookupMap[id] = []string{}
25
+			}
26
+			lookupMap[id] = append(lookupMap[id], name+":"+tag)
27
+		}
28
+	}
29
+
30
+	outs := engine.NewTable("Created", 0)
31
+	err = foundImage.WalkHistory(func(img *image.Image) error {
32
+		out := &engine.Env{}
33
+		out.Set("Id", img.ID)
34
+		out.SetInt64("Created", img.Created.Unix())
35
+		out.Set("CreatedBy", strings.Join(img.ContainerConfig.Cmd, " "))
36
+		out.SetList("Tags", lookupMap[img.ID])
37
+		out.SetInt64("Size", img.Size)
38
+		outs.Add(out)
39
+		return nil
40
+	})
41
+	if _, err := outs.WriteListTo(job.Stdout); err != nil {
42
+		return job.Error(err)
43
+	}
44
+	return engine.StatusOK
45
+}
0 46
new file mode 100644
... ...
@@ -0,0 +1,61 @@
0
+package graph
1
+
2
+import (
3
+	"net/http"
4
+	"net/url"
5
+
6
+	"github.com/docker/docker/archive"
7
+	"github.com/docker/docker/engine"
8
+	"github.com/docker/docker/utils"
9
+)
10
+
11
+func (s *TagStore) CmdImport(job *engine.Job) engine.Status {
12
+	if n := len(job.Args); n != 2 && n != 3 {
13
+		return job.Errorf("Usage: %s SRC REPO [TAG]", job.Name)
14
+	}
15
+	var (
16
+		src     = job.Args[0]
17
+		repo    = job.Args[1]
18
+		tag     string
19
+		sf      = utils.NewStreamFormatter(job.GetenvBool("json"))
20
+		archive archive.ArchiveReader
21
+		resp    *http.Response
22
+	)
23
+	if len(job.Args) > 2 {
24
+		tag = job.Args[2]
25
+	}
26
+
27
+	if src == "-" {
28
+		archive = job.Stdin
29
+	} else {
30
+		u, err := url.Parse(src)
31
+		if err != nil {
32
+			return job.Error(err)
33
+		}
34
+		if u.Scheme == "" {
35
+			u.Scheme = "http"
36
+			u.Host = src
37
+			u.Path = ""
38
+		}
39
+		job.Stdout.Write(sf.FormatStatus("", "Downloading from %s", u))
40
+		resp, err = utils.Download(u.String())
41
+		if err != nil {
42
+			return job.Error(err)
43
+		}
44
+		progressReader := utils.ProgressReader(resp.Body, int(resp.ContentLength), job.Stdout, sf, true, "", "Importing")
45
+		defer progressReader.Close()
46
+		archive = progressReader
47
+	}
48
+	img, err := s.graph.Create(archive, "", "", "Imported from "+src, "", nil, nil)
49
+	if err != nil {
50
+		return job.Error(err)
51
+	}
52
+	// Optionally register the image at REPO/TAG
53
+	if repo != "" {
54
+		if err := s.Set(repo, tag, img.ID, true); err != nil {
55
+			return job.Error(err)
56
+		}
57
+	}
58
+	job.Stdout.Write(sf.FormatStatus("", img.ID))
59
+	return engine.StatusOK
60
+}
0 61
new file mode 100644
... ...
@@ -0,0 +1,103 @@
0
+package graph
1
+
2
+import (
3
+	"fmt"
4
+	"log"
5
+	"path"
6
+	"strings"
7
+
8
+	"github.com/docker/docker/engine"
9
+	"github.com/docker/docker/image"
10
+	"github.com/docker/docker/pkg/parsers/filters"
11
+)
12
+
13
+func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
14
+	var (
15
+		allImages   map[string]*image.Image
16
+		err         error
17
+		filt_tagged = true
18
+	)
19
+
20
+	imageFilters, err := filters.FromParam(job.Getenv("filters"))
21
+	if err != nil {
22
+		return job.Error(err)
23
+	}
24
+	if i, ok := imageFilters["dangling"]; ok {
25
+		for _, value := range i {
26
+			if strings.ToLower(value) == "true" {
27
+				filt_tagged = false
28
+			}
29
+		}
30
+	}
31
+
32
+	if job.GetenvBool("all") && filt_tagged {
33
+		allImages, err = s.graph.Map()
34
+	} else {
35
+		allImages, err = s.graph.Heads()
36
+	}
37
+	if err != nil {
38
+		return job.Error(err)
39
+	}
40
+	lookup := make(map[string]*engine.Env)
41
+	s.Lock()
42
+	for name, repository := range s.Repositories {
43
+		if job.Getenv("filter") != "" {
44
+			if match, _ := path.Match(job.Getenv("filter"), name); !match {
45
+				continue
46
+			}
47
+		}
48
+		for tag, id := range repository {
49
+			image, err := s.graph.Get(id)
50
+			if err != nil {
51
+				log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err)
52
+				continue
53
+			}
54
+
55
+			if out, exists := lookup[id]; exists {
56
+				if filt_tagged {
57
+					out.SetList("RepoTags", append(out.GetList("RepoTags"), fmt.Sprintf("%s:%s", name, tag)))
58
+				}
59
+			} else {
60
+				// get the boolean list for if only the untagged images are requested
61
+				delete(allImages, id)
62
+				if filt_tagged {
63
+					out := &engine.Env{}
64
+					out.Set("ParentId", image.Parent)
65
+					out.SetList("RepoTags", []string{fmt.Sprintf("%s:%s", name, tag)})
66
+					out.Set("Id", image.ID)
67
+					out.SetInt64("Created", image.Created.Unix())
68
+					out.SetInt64("Size", image.Size)
69
+					out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
70
+					lookup[id] = out
71
+				}
72
+			}
73
+
74
+		}
75
+	}
76
+	s.Unlock()
77
+
78
+	outs := engine.NewTable("Created", len(lookup))
79
+	for _, value := range lookup {
80
+		outs.Add(value)
81
+	}
82
+
83
+	// Display images which aren't part of a repository/tag
84
+	if job.Getenv("filter") == "" {
85
+		for _, image := range allImages {
86
+			out := &engine.Env{}
87
+			out.Set("ParentId", image.Parent)
88
+			out.SetList("RepoTags", []string{"<none>:<none>"})
89
+			out.Set("Id", image.ID)
90
+			out.SetInt64("Created", image.Created.Unix())
91
+			out.SetInt64("Size", image.Size)
92
+			out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
93
+			outs.Add(out)
94
+		}
95
+	}
96
+
97
+	outs.ReverseSort()
98
+	if _, err := outs.WriteListTo(job.Stdout); err != nil {
99
+		return job.Error(err)
100
+	}
101
+	return engine.StatusOK
102
+}
0 103
new file mode 100644
... ...
@@ -0,0 +1,118 @@
0
+package graph
1
+
2
+import (
3
+	"encoding/json"
4
+	"io"
5
+	"io/ioutil"
6
+	"os"
7
+	"path"
8
+
9
+	"github.com/docker/docker/archive"
10
+	"github.com/docker/docker/engine"
11
+	"github.com/docker/docker/image"
12
+	"github.com/docker/docker/utils"
13
+)
14
+
15
+// Loads a set of images into the repository. This is the complementary of ImageExport.
16
+// The input stream is an uncompressed tar ball containing images and metadata.
17
+func (s *TagStore) CmdLoad(job *engine.Job) engine.Status {
18
+	tmpImageDir, err := ioutil.TempDir("", "docker-import-")
19
+	if err != nil {
20
+		return job.Error(err)
21
+	}
22
+	defer os.RemoveAll(tmpImageDir)
23
+
24
+	var (
25
+		repoTarFile = path.Join(tmpImageDir, "repo.tar")
26
+		repoDir     = path.Join(tmpImageDir, "repo")
27
+	)
28
+
29
+	tarFile, err := os.Create(repoTarFile)
30
+	if err != nil {
31
+		return job.Error(err)
32
+	}
33
+	if _, err := io.Copy(tarFile, job.Stdin); err != nil {
34
+		return job.Error(err)
35
+	}
36
+	tarFile.Close()
37
+
38
+	repoFile, err := os.Open(repoTarFile)
39
+	if err != nil {
40
+		return job.Error(err)
41
+	}
42
+	if err := os.Mkdir(repoDir, os.ModeDir); err != nil {
43
+		return job.Error(err)
44
+	}
45
+	if err := archive.Untar(repoFile, repoDir, nil); err != nil {
46
+		return job.Error(err)
47
+	}
48
+
49
+	dirs, err := ioutil.ReadDir(repoDir)
50
+	if err != nil {
51
+		return job.Error(err)
52
+	}
53
+
54
+	for _, d := range dirs {
55
+		if d.IsDir() {
56
+			if err := s.recursiveLoad(job.Eng, d.Name(), tmpImageDir); err != nil {
57
+				return job.Error(err)
58
+			}
59
+		}
60
+	}
61
+
62
+	repositoriesJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories"))
63
+	if err == nil {
64
+		repositories := map[string]Repository{}
65
+		if err := json.Unmarshal(repositoriesJson, &repositories); err != nil {
66
+			return job.Error(err)
67
+		}
68
+
69
+		for imageName, tagMap := range repositories {
70
+			for tag, address := range tagMap {
71
+				if err := s.Set(imageName, tag, address, true); err != nil {
72
+					return job.Error(err)
73
+				}
74
+			}
75
+		}
76
+	} else if !os.IsNotExist(err) {
77
+		return job.Error(err)
78
+	}
79
+
80
+	return engine.StatusOK
81
+}
82
+
83
+func (s *TagStore) recursiveLoad(eng *engine.Engine, address, tmpImageDir string) error {
84
+	if err := eng.Job("image_get", address).Run(); err != nil {
85
+		utils.Debugf("Loading %s", address)
86
+
87
+		imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json"))
88
+		if err != nil {
89
+			utils.Debugf("Error reading json", err)
90
+			return err
91
+		}
92
+
93
+		layer, err := os.Open(path.Join(tmpImageDir, "repo", address, "layer.tar"))
94
+		if err != nil {
95
+			utils.Debugf("Error reading embedded tar", err)
96
+			return err
97
+		}
98
+		img, err := image.NewImgJSON(imageJson)
99
+		if err != nil {
100
+			utils.Debugf("Error unmarshalling json", err)
101
+			return err
102
+		}
103
+		if img.Parent != "" {
104
+			if !s.graph.Exists(img.Parent) {
105
+				if err := s.recursiveLoad(eng, img.Parent, tmpImageDir); err != nil {
106
+					return err
107
+				}
108
+			}
109
+		}
110
+		if err := s.graph.Register(imageJson, layer, img); err != nil {
111
+			return err
112
+		}
113
+	}
114
+	utils.Debugf("Completed processing %s", address)
115
+
116
+	return nil
117
+}
... ...
@@ -1,20 +1,33 @@
1 1
 package graph
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 	"io"
5 6
 
6 7
 	"github.com/docker/docker/engine"
7 8
 	"github.com/docker/docker/image"
8
-	"github.com/docker/docker/pkg/parsers"
9 9
 	"github.com/docker/docker/utils"
10 10
 )
11 11
 
12 12
 func (s *TagStore) Install(eng *engine.Engine) error {
13
-	eng.Register("image_set", s.CmdSet)
14
-	eng.Register("image_tag", s.CmdTag)
15
-	eng.Register("image_get", s.CmdGet)
16
-	eng.Register("image_inspect", s.CmdLookup)
17
-	eng.Register("image_tarlayer", s.CmdTarLayer)
13
+	for name, handler := range map[string]engine.Handler{
14
+		"image_set":      s.CmdSet,
15
+		"image_tag":      s.CmdTag,
16
+		"tag":            s.CmdTagLegacy, // FIXME merge with "image_tag"
17
+		"image_get":      s.CmdGet,
18
+		"image_inspect":  s.CmdLookup,
19
+		"image_tarlayer": s.CmdTarLayer,
20
+		"image_export":   s.CmdImageExport,
21
+		"history":        s.CmdHistory,
22
+		"images":         s.CmdImages,
23
+		"viz":            s.CmdViz,
24
+		"load":           s.CmdLoad,
25
+		"import":         s.CmdImport,
26
+	} {
27
+		if err := eng.Register(name, handler); err != nil {
28
+			return fmt.Errorf("Could not register %q: %v", name, err)
29
+		}
30
+	}
18 31
 	return nil
19 32
 }
20 33
 
... ...
@@ -65,29 +78,6 @@ func (s *TagStore) CmdSet(job *engine.Job) engine.Status {
65 65
 	return engine.StatusOK
66 66
 }
67 67
 
68
-// CmdTag assigns a new name and tag to an existing image. If the tag already exists,
69
-// it is changed and the image previously referenced by the tag loses that reference.
70
-// This may cause the old image to be garbage-collected if its reference count reaches zero.
71
-//
72
-// Syntax: image_tag NEWNAME OLDNAME
73
-// Example: image_tag shykes/myapp:latest shykes/myapp:1.42.0
74
-func (s *TagStore) CmdTag(job *engine.Job) engine.Status {
75
-	if len(job.Args) != 2 {
76
-		return job.Errorf("usage: %s NEWNAME OLDNAME", job.Name)
77
-	}
78
-	var (
79
-		newName = job.Args[0]
80
-		oldName = job.Args[1]
81
-	)
82
-	newRepo, newTag := parsers.ParseRepositoryTag(newName)
83
-	// FIXME: Set should either parse both old and new name, or neither.
84
-	// 	the current prototype is inconsistent.
85
-	if err := s.Set(newRepo, newTag, oldName, true); err != nil {
86
-		return job.Error(err)
87
-	}
88
-	return engine.StatusOK
89
-}
90
-
91 68
 // CmdGet returns information about an image.
92 69
 // If the image doesn't exist, an empty object is returned, to allow
93 70
 // checking for an image's existence.
94 71
new file mode 100644
... ...
@@ -0,0 +1,44 @@
0
+package graph
1
+
2
+import (
3
+	"github.com/docker/docker/engine"
4
+	"github.com/docker/docker/pkg/parsers"
5
+)
6
+
7
+// CmdTag assigns a new name and tag to an existing image. If the tag already exists,
8
+// it is changed and the image previously referenced by the tag loses that reference.
9
+// This may cause the old image to be garbage-collected if its reference count reaches zero.
10
+//
11
+// Syntax: image_tag NEWNAME OLDNAME
12
+// Example: image_tag shykes/myapp:latest shykes/myapp:1.42.0
13
+func (s *TagStore) CmdTag(job *engine.Job) engine.Status {
14
+	if len(job.Args) != 2 {
15
+		return job.Errorf("usage: %s NEWNAME OLDNAME", job.Name)
16
+	}
17
+	var (
18
+		newName = job.Args[0]
19
+		oldName = job.Args[1]
20
+	)
21
+	newRepo, newTag := parsers.ParseRepositoryTag(newName)
22
+	// FIXME: Set should either parse both old and new name, or neither.
23
+	// 	the current prototype is inconsistent.
24
+	if err := s.Set(newRepo, newTag, oldName, true); err != nil {
25
+		return job.Error(err)
26
+	}
27
+	return engine.StatusOK
28
+}
29
+
30
+// FIXME: merge into CmdTag above, and merge "image_tag" and "tag" into a single job.
31
+func (s *TagStore) CmdTagLegacy(job *engine.Job) engine.Status {
32
+	if len(job.Args) != 2 && len(job.Args) != 3 {
33
+		return job.Errorf("Usage: %s IMAGE REPOSITORY [TAG]\n", job.Name)
34
+	}
35
+	var tag string
36
+	if len(job.Args) == 3 {
37
+		tag = job.Args[2]
38
+	}
39
+	if err := s.Set(job.Args[1], tag, job.Args[0], job.GetenvBool("force")); err != nil {
40
+		return job.Error(err)
41
+	}
42
+	return engine.StatusOK
43
+}
0 44
new file mode 100644
... ...
@@ -0,0 +1,38 @@
0
+package graph
1
+
2
+import (
3
+	"strings"
4
+
5
+	"github.com/docker/docker/engine"
6
+	"github.com/docker/docker/image"
7
+)
8
+
9
+func (s *TagStore) CmdViz(job *engine.Job) engine.Status {
10
+	images, _ := s.graph.Map()
11
+	if images == nil {
12
+		return engine.StatusOK
13
+	}
14
+	job.Stdout.Write([]byte("digraph docker {\n"))
15
+
16
+	var (
17
+		parentImage *image.Image
18
+		err         error
19
+	)
20
+	for _, image := range images {
21
+		parentImage, err = image.GetParent()
22
+		if err != nil {
23
+			return job.Errorf("Error while getting parent image: %v", err)
24
+		}
25
+		if parentImage != nil {
26
+			job.Stdout.Write([]byte(" \"" + parentImage.ID + "\" -> \"" + image.ID + "\"\n"))
27
+		} else {
28
+			job.Stdout.Write([]byte(" base -> \"" + image.ID + "\" [style=invis]\n"))
29
+		}
30
+	}
31
+
32
+	for id, repos := range s.GetRepoRefs() {
33
+		job.Stdout.Write([]byte(" \"" + id + "\" [label=\"" + id + "\\n" + strings.Join(repos, "\\n") + "\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n"))
34
+	}
35
+	job.Stdout.Write([]byte(" base [style=invisible]\n}\n"))
36
+	return engine.StatusOK
37
+}
... ...
@@ -3,6 +3,7 @@ package main
3 3
 import (
4 4
 	"os"
5 5
 	"os/exec"
6
+	"strings"
6 7
 	"testing"
7 8
 )
8 9
 
... ...
@@ -92,6 +93,69 @@ func TestRemoveContainerWithStopAndKill(t *testing.T) {
92 92
 	logDone("rm - with --stop=true and --kill=true")
93 93
 }
94 94
 
95
+func TestContainerOrphaning(t *testing.T) {
96
+	dockerfile1 := `FROM busybox:latest
97
+	ENTRYPOINT ["/bin/true"]`
98
+	img := "test-container-orphaning"
99
+	dockerfile2 := `FROM busybox:latest
100
+	ENTRYPOINT ["/bin/true"]
101
+	MAINTAINER Integration Tests`
102
+
103
+	// build first dockerfile
104
+	img1, err := buildImage(img, dockerfile1, true)
105
+	if err != nil {
106
+		t.Fatalf("Could not build image %s: %v", img, err)
107
+	}
108
+	// run container on first image
109
+	if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", img)); err != nil {
110
+		t.Fatalf("Could not run image %s: %v: %s", img, err, out)
111
+	}
112
+	// rebuild dockerfile with a small addition at the end
113
+	if _, err := buildImage(img, dockerfile2, true); err != nil {
114
+		t.Fatalf("Could not rebuild image %s: %v", img, err)
115
+	}
116
+	// try to remove the image, should error out.
117
+	if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rmi", img)); err == nil {
118
+		t.Fatalf("Expected to error out removing the image, but succeeded: %s", out)
119
+	}
120
+	// check if we deleted the first image
121
+	out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "images", "-q", "--no-trunc"))
122
+	if err != nil {
123
+		t.Fatalf("%v: %s", err, out)
124
+	}
125
+	if !strings.Contains(out, img1) {
126
+		t.Fatal("Orphaned container (could not find '%s' in docker images): %s", img1, out)
127
+	}
128
+
129
+	deleteAllContainers()
130
+
131
+	logDone("rm - container orphaning")
132
+}
133
+
134
+func TestDeleteTagWithExistingContainers(t *testing.T) {
135
+	container := "test-delete-tag"
136
+	newtag := "busybox:newtag"
137
+	bb := "busybox:latest"
138
+	if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", bb, newtag)); err != nil {
139
+		t.Fatalf("Could not tag busybox: %v: %s", err, out)
140
+	}
141
+	if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", container, bb, "/bin/true")); err != nil {
142
+		t.Fatalf("Could not run busybox: %v: %s", err, out)
143
+	}
144
+	out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rmi", newtag))
145
+	if err != nil {
146
+		t.Fatalf("Could not remove tag %s: %v: %s", newtag, err, out)
147
+	}
148
+	if d := strings.Count(out, "Untagged: "); d != 1 {
149
+		t.Fatalf("Expected 1 untagged entry got %d: %q", d, out)
150
+	}
151
+
152
+	deleteAllContainers()
153
+
154
+	logDone("rm - delete tag with existing containers")
155
+
156
+}
157
+
95 158
 func createRunningContainer(t *testing.T, name string) {
96 159
 	cmd := exec.Command(dockerBinary, "run", "-dt", "--name", name, "busybox", "top")
97 160
 	if _, err := runCommand(cmd); err != nil {
... ...
@@ -13,7 +13,6 @@ import (
13 13
 
14 14
 	"github.com/docker/docker/api/client"
15 15
 	"github.com/docker/docker/daemon"
16
-	"github.com/docker/docker/engine"
17 16
 	"github.com/docker/docker/pkg/term"
18 17
 	"github.com/docker/docker/utils"
19 18
 )
... ...
@@ -682,70 +681,3 @@ func TestRunCidFileCleanupIfEmpty(t *testing.T) {
682 682
 		<-c
683 683
 	})
684 684
 }
685
-
686
-func TestContainerOrphaning(t *testing.T) {
687
-
688
-	// setup a temporary directory
689
-	tmpDir, err := ioutil.TempDir("", "project")
690
-	if err != nil {
691
-		t.Fatal(err)
692
-	}
693
-	defer os.RemoveAll(tmpDir)
694
-
695
-	// setup a CLI and server
696
-	cli := client.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr, nil)
697
-	defer cleanup(globalEngine, t)
698
-	srv := mkServerFromEngine(globalEngine, t)
699
-
700
-	// closure to build something
701
-	buildSomething := func(template string, image string) string {
702
-		dockerfile := path.Join(tmpDir, "Dockerfile")
703
-		replacer := strings.NewReplacer("{IMAGE}", unitTestImageID)
704
-		contents := replacer.Replace(template)
705
-		ioutil.WriteFile(dockerfile, []byte(contents), 0x777)
706
-		if err := cli.CmdBuild("-t", image, tmpDir); err != nil {
707
-			t.Fatal(err)
708
-		}
709
-		job := globalEngine.Job("image_get", image)
710
-		info, _ := job.Stdout.AddEnv()
711
-		if err := job.Run(); err != nil {
712
-			t.Fatal(err)
713
-		}
714
-		return info.Get("Id")
715
-	}
716
-
717
-	// build an image
718
-	imageName := "orphan-test"
719
-	template1 := `
720
-	from {IMAGE}
721
-	cmd ["/bin/echo", "holla"]
722
-	`
723
-	img1 := buildSomething(template1, imageName)
724
-
725
-	// create a container using the fist image
726
-	if err := cli.CmdRun(imageName); err != nil {
727
-		t.Fatal(err)
728
-	}
729
-
730
-	// build a new image that splits lineage
731
-	template2 := `
732
-	from {IMAGE}
733
-	cmd ["/bin/echo", "holla"]
734
-	expose 22
735
-	`
736
-	buildSomething(template2, imageName)
737
-
738
-	// remove the second image by name
739
-	resp := engine.NewTable("", 0)
740
-	if err := srv.DeleteImage(imageName, resp, true, false, false); err == nil {
741
-		t.Fatal("Expected error, got none")
742
-	}
743
-
744
-	// see if we deleted the first image (and orphaned the container)
745
-	for _, i := range resp.Data {
746
-		if img1 == i.Get("Deleted") {
747
-			t.Fatal("Orphaned image with container")
748
-		}
749
-	}
750
-
751
-}
... ...
@@ -297,56 +297,3 @@ func TestImagesFilter(t *testing.T) {
297 297
 		t.Fatal("incorrect number of matches returned")
298 298
 	}
299 299
 }
300
-
301
-// Regression test for being able to untag an image with an existing
302
-// container
303
-func TestDeleteTagWithExistingContainers(t *testing.T) {
304
-	eng := NewTestEngine(t)
305
-	defer nuke(mkDaemonFromEngine(eng, t))
306
-
307
-	srv := mkServerFromEngine(eng, t)
308
-
309
-	// Tag the image
310
-	if err := eng.Job("tag", unitTestImageID, "utest", "tag1").Run(); err != nil {
311
-		t.Fatal(err)
312
-	}
313
-
314
-	// Create a container from the image
315
-	config, _, _, err := runconfig.Parse([]string{unitTestImageID, "echo test"}, nil)
316
-	if err != nil {
317
-		t.Fatal(err)
318
-	}
319
-
320
-	id := createNamedTestContainer(eng, config, t, "testingtags")
321
-	if id == "" {
322
-		t.Fatal("No id returned")
323
-	}
324
-
325
-	job := srv.Eng.Job("containers")
326
-	job.SetenvBool("all", true)
327
-	outs, err := job.Stdout.AddListTable()
328
-	if err != nil {
329
-		t.Fatal(err)
330
-	}
331
-	if err := job.Run(); err != nil {
332
-		t.Fatal(err)
333
-	}
334
-
335
-	if len(outs.Data) != 1 {
336
-		t.Fatalf("Expected 1 container got %d", len(outs.Data))
337
-	}
338
-
339
-	// Try to remove the tag
340
-	imgs := engine.NewTable("", 0)
341
-	if err := srv.DeleteImage("utest:tag1", imgs, true, false, false); err != nil {
342
-		t.Fatal(err)
343
-	}
344
-
345
-	if len(imgs.Data) != 1 {
346
-		t.Fatalf("Should only have deleted one untag %d", len(imgs.Data))
347
-	}
348
-
349
-	if untag := imgs.Data[0].Get("Untagged"); untag != "utest:tag1" {
350
-		t.Fatalf("Expected %s got %s", unitTestImageID, untag)
351
-	}
352
-}
353 300
deleted file mode 100644
... ...
@@ -1,108 +0,0 @@
1
-// DEPRECATION NOTICE. PLEASE DO NOT ADD ANYTHING TO THIS FILE.
2
-//
3
-// For additional commments see server/server.go
4
-//
5
-package server
6
-
7
-import (
8
-	"encoding/json"
9
-	"time"
10
-
11
-	"github.com/docker/docker/engine"
12
-	"github.com/docker/docker/utils"
13
-)
14
-
15
-func (srv *Server) Events(job *engine.Job) engine.Status {
16
-	if len(job.Args) != 0 {
17
-		return job.Errorf("Usage: %s", job.Name)
18
-	}
19
-
20
-	var (
21
-		since   = job.GetenvInt64("since")
22
-		until   = job.GetenvInt64("until")
23
-		timeout = time.NewTimer(time.Unix(until, 0).Sub(time.Now()))
24
-	)
25
-
26
-	// If no until, disable timeout
27
-	if until == 0 {
28
-		timeout.Stop()
29
-	}
30
-
31
-	listener := make(chan utils.JSONMessage)
32
-	srv.eventPublisher.Subscribe(listener)
33
-	defer srv.eventPublisher.Unsubscribe(listener)
34
-
35
-	// When sending an event JSON serialization errors are ignored, but all
36
-	// other errors lead to the eviction of the listener.
37
-	sendEvent := func(event *utils.JSONMessage) error {
38
-		if b, err := json.Marshal(event); err == nil {
39
-			if _, err = job.Stdout.Write(b); err != nil {
40
-				return err
41
-			}
42
-		}
43
-		return nil
44
-	}
45
-
46
-	job.Stdout.Write(nil)
47
-
48
-	// Resend every event in the [since, until] time interval.
49
-	if since != 0 {
50
-		for _, event := range srv.GetEvents() {
51
-			if event.Time >= since && (event.Time <= until || until == 0) {
52
-				if err := sendEvent(&event); err != nil {
53
-					return job.Error(err)
54
-				}
55
-			}
56
-		}
57
-	}
58
-
59
-	for {
60
-		select {
61
-		case event, ok := <-listener:
62
-			if !ok {
63
-				return engine.StatusOK
64
-			}
65
-			if err := sendEvent(&event); err != nil {
66
-				return job.Error(err)
67
-			}
68
-		case <-timeout.C:
69
-			return engine.StatusOK
70
-		}
71
-	}
72
-}
73
-
74
-// FIXME: this is a shim to allow breaking up other parts of Server without
75
-// dragging the sphagetti dependency along.
76
-func (srv *Server) Log(job *engine.Job) engine.Status {
77
-	if len(job.Args) != 3 {
78
-		return job.Errorf("usage: %s ACTION ID FROM", job.Name)
79
-	}
80
-	srv.LogEvent(job.Args[0], job.Args[1], job.Args[2])
81
-	return engine.StatusOK
82
-}
83
-
84
-func (srv *Server) LogEvent(action, id, from string) *utils.JSONMessage {
85
-	now := time.Now().UTC().Unix()
86
-	jm := utils.JSONMessage{Status: action, ID: id, From: from, Time: now}
87
-	srv.AddEvent(jm)
88
-	srv.eventPublisher.Publish(jm)
89
-	return &jm
90
-}
91
-
92
-func (srv *Server) AddEvent(jm utils.JSONMessage) {
93
-	srv.Lock()
94
-	if len(srv.events) == cap(srv.events) {
95
-		// discard oldest event
96
-		copy(srv.events, srv.events[1:])
97
-		srv.events[len(srv.events)-1] = jm
98
-	} else {
99
-		srv.events = append(srv.events, jm)
100
-	}
101
-	srv.Unlock()
102
-}
103
-
104
-func (srv *Server) GetEvents() []utils.JSONMessage {
105
-	srv.RLock()
106
-	defer srv.RUnlock()
107
-	return srv.events
108
-}
... ...
@@ -5,13 +5,10 @@
5 5
 package server
6 6
 
7 7
 import (
8
-	"encoding/json"
9 8
 	"fmt"
10 9
 	"io"
11 10
 	"io/ioutil"
12
-	"log"
13 11
 	"net"
14
-	"net/http"
15 12
 	"net/url"
16 13
 	"os"
17 14
 	"os/exec"
... ...
@@ -22,146 +19,12 @@ import (
22 22
 	"github.com/docker/docker/archive"
23 23
 	"github.com/docker/docker/builder"
24 24
 	"github.com/docker/docker/engine"
25
-	"github.com/docker/docker/graph"
26 25
 	"github.com/docker/docker/image"
27 26
 	"github.com/docker/docker/pkg/parsers"
28
-	"github.com/docker/docker/pkg/parsers/filters"
29 27
 	"github.com/docker/docker/registry"
30 28
 	"github.com/docker/docker/utils"
31 29
 )
32 30
 
33
-// ImageExport exports all images with the given tag. All versions
34
-// containing the same tag are exported. The resulting output is an
35
-// uncompressed tar ball.
36
-// name is the set of tags to export.
37
-// out is the writer where the images are written to.
38
-func (srv *Server) ImageExport(job *engine.Job) engine.Status {
39
-	if len(job.Args) != 1 {
40
-		return job.Errorf("Usage: %s IMAGE\n", job.Name)
41
-	}
42
-	name := job.Args[0]
43
-	// get image json
44
-	tempdir, err := ioutil.TempDir("", "docker-export-")
45
-	if err != nil {
46
-		return job.Error(err)
47
-	}
48
-	defer os.RemoveAll(tempdir)
49
-
50
-	utils.Debugf("Serializing %s", name)
51
-
52
-	rootRepoMap := map[string]graph.Repository{}
53
-	rootRepo, err := srv.daemon.Repositories().Get(name)
54
-	if err != nil {
55
-		return job.Error(err)
56
-	}
57
-	if rootRepo != nil {
58
-		// this is a base repo name, like 'busybox'
59
-
60
-		for _, id := range rootRepo {
61
-			if err := srv.exportImage(job.Eng, id, tempdir); err != nil {
62
-				return job.Error(err)
63
-			}
64
-		}
65
-		rootRepoMap[name] = rootRepo
66
-	} else {
67
-		img, err := srv.daemon.Repositories().LookupImage(name)
68
-		if err != nil {
69
-			return job.Error(err)
70
-		}
71
-		if img != nil {
72
-			// This is a named image like 'busybox:latest'
73
-			repoName, repoTag := parsers.ParseRepositoryTag(name)
74
-			if err := srv.exportImage(job.Eng, img.ID, tempdir); err != nil {
75
-				return job.Error(err)
76
-			}
77
-			// check this length, because a lookup of a truncated has will not have a tag
78
-			// and will not need to be added to this map
79
-			if len(repoTag) > 0 {
80
-				rootRepoMap[repoName] = graph.Repository{repoTag: img.ID}
81
-			}
82
-		} else {
83
-			// this must be an ID that didn't get looked up just right?
84
-			if err := srv.exportImage(job.Eng, name, tempdir); err != nil {
85
-				return job.Error(err)
86
-			}
87
-		}
88
-	}
89
-	// write repositories, if there is something to write
90
-	if len(rootRepoMap) > 0 {
91
-		rootRepoJson, _ := json.Marshal(rootRepoMap)
92
-
93
-		if err := ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.FileMode(0644)); err != nil {
94
-			return job.Error(err)
95
-		}
96
-	} else {
97
-		utils.Debugf("There were no repositories to write")
98
-	}
99
-
100
-	fs, err := archive.Tar(tempdir, archive.Uncompressed)
101
-	if err != nil {
102
-		return job.Error(err)
103
-	}
104
-	defer fs.Close()
105
-
106
-	if _, err := io.Copy(job.Stdout, fs); err != nil {
107
-		return job.Error(err)
108
-	}
109
-	utils.Debugf("End Serializing %s", name)
110
-	return engine.StatusOK
111
-}
112
-
113
-func (srv *Server) exportImage(eng *engine.Engine, name, tempdir string) error {
114
-	for n := name; n != ""; {
115
-		// temporary directory
116
-		tmpImageDir := path.Join(tempdir, n)
117
-		if err := os.Mkdir(tmpImageDir, os.FileMode(0755)); err != nil {
118
-			if os.IsExist(err) {
119
-				return nil
120
-			}
121
-			return err
122
-		}
123
-
124
-		var version = "1.0"
125
-		var versionBuf = []byte(version)
126
-
127
-		if err := ioutil.WriteFile(path.Join(tmpImageDir, "VERSION"), versionBuf, os.FileMode(0644)); err != nil {
128
-			return err
129
-		}
130
-
131
-		// serialize json
132
-		json, err := os.Create(path.Join(tmpImageDir, "json"))
133
-		if err != nil {
134
-			return err
135
-		}
136
-		job := eng.Job("image_inspect", n)
137
-		job.SetenvBool("raw", true)
138
-		job.Stdout.Add(json)
139
-		if err := job.Run(); err != nil {
140
-			return err
141
-		}
142
-
143
-		// serialize filesystem
144
-		fsTar, err := os.Create(path.Join(tmpImageDir, "layer.tar"))
145
-		if err != nil {
146
-			return err
147
-		}
148
-		job = eng.Job("image_tarlayer", n)
149
-		job.Stdout.Add(fsTar)
150
-		if err := job.Run(); err != nil {
151
-			return err
152
-		}
153
-
154
-		// find parent
155
-		job = eng.Job("image_get", n)
156
-		info, _ := job.Stdout.AddEnv()
157
-		if err := job.Run(); err != nil {
158
-			return err
159
-		}
160
-		n = info.Get("Parent")
161
-	}
162
-	return nil
163
-}
164
-
165 31
 func (srv *Server) Build(job *engine.Job) engine.Status {
166 32
 	if len(job.Args) != 0 {
167 33
 		return job.Errorf("Usage: %s\n", job.Name)
... ...
@@ -242,282 +105,6 @@ func (srv *Server) Build(job *engine.Job) engine.Status {
242 242
 	return engine.StatusOK
243 243
 }
244 244
 
245
-// Loads a set of images into the repository. This is the complementary of ImageExport.
246
-// The input stream is an uncompressed tar ball containing images and metadata.
247
-func (srv *Server) ImageLoad(job *engine.Job) engine.Status {
248
-	tmpImageDir, err := ioutil.TempDir("", "docker-import-")
249
-	if err != nil {
250
-		return job.Error(err)
251
-	}
252
-	defer os.RemoveAll(tmpImageDir)
253
-
254
-	var (
255
-		repoTarFile = path.Join(tmpImageDir, "repo.tar")
256
-		repoDir     = path.Join(tmpImageDir, "repo")
257
-	)
258
-
259
-	tarFile, err := os.Create(repoTarFile)
260
-	if err != nil {
261
-		return job.Error(err)
262
-	}
263
-	if _, err := io.Copy(tarFile, job.Stdin); err != nil {
264
-		return job.Error(err)
265
-	}
266
-	tarFile.Close()
267
-
268
-	repoFile, err := os.Open(repoTarFile)
269
-	if err != nil {
270
-		return job.Error(err)
271
-	}
272
-	if err := os.Mkdir(repoDir, os.ModeDir); err != nil {
273
-		return job.Error(err)
274
-	}
275
-	if err := archive.Untar(repoFile, repoDir, nil); err != nil {
276
-		return job.Error(err)
277
-	}
278
-
279
-	dirs, err := ioutil.ReadDir(repoDir)
280
-	if err != nil {
281
-		return job.Error(err)
282
-	}
283
-
284
-	for _, d := range dirs {
285
-		if d.IsDir() {
286
-			if err := srv.recursiveLoad(job.Eng, d.Name(), tmpImageDir); err != nil {
287
-				return job.Error(err)
288
-			}
289
-		}
290
-	}
291
-
292
-	repositoriesJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories"))
293
-	if err == nil {
294
-		repositories := map[string]graph.Repository{}
295
-		if err := json.Unmarshal(repositoriesJson, &repositories); err != nil {
296
-			return job.Error(err)
297
-		}
298
-
299
-		for imageName, tagMap := range repositories {
300
-			for tag, address := range tagMap {
301
-				if err := srv.daemon.Repositories().Set(imageName, tag, address, true); err != nil {
302
-					return job.Error(err)
303
-				}
304
-			}
305
-		}
306
-	} else if !os.IsNotExist(err) {
307
-		return job.Error(err)
308
-	}
309
-
310
-	return engine.StatusOK
311
-}
312
-
313
-func (srv *Server) recursiveLoad(eng *engine.Engine, address, tmpImageDir string) error {
314
-	if err := eng.Job("image_get", address).Run(); err != nil {
315
-		utils.Debugf("Loading %s", address)
316
-
317
-		imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json"))
318
-		if err != nil {
319
-			utils.Debugf("Error reading json", err)
320
-			return err
321
-		}
322
-
323
-		layer, err := os.Open(path.Join(tmpImageDir, "repo", address, "layer.tar"))
324
-		if err != nil {
325
-			utils.Debugf("Error reading embedded tar", err)
326
-			return err
327
-		}
328
-		img, err := image.NewImgJSON(imageJson)
329
-		if err != nil {
330
-			utils.Debugf("Error unmarshalling json", err)
331
-			return err
332
-		}
333
-		if img.Parent != "" {
334
-			if !srv.daemon.Graph().Exists(img.Parent) {
335
-				if err := srv.recursiveLoad(eng, img.Parent, tmpImageDir); err != nil {
336
-					return err
337
-				}
338
-			}
339
-		}
340
-		if err := srv.daemon.Graph().Register(imageJson, layer, img); err != nil {
341
-			return err
342
-		}
343
-	}
344
-	utils.Debugf("Completed processing %s", address)
345
-
346
-	return nil
347
-}
348
-
349
-func (srv *Server) ImagesViz(job *engine.Job) engine.Status {
350
-	images, _ := srv.daemon.Graph().Map()
351
-	if images == nil {
352
-		return engine.StatusOK
353
-	}
354
-	job.Stdout.Write([]byte("digraph docker {\n"))
355
-
356
-	var (
357
-		parentImage *image.Image
358
-		err         error
359
-	)
360
-	for _, image := range images {
361
-		parentImage, err = image.GetParent()
362
-		if err != nil {
363
-			return job.Errorf("Error while getting parent image: %v", err)
364
-		}
365
-		if parentImage != nil {
366
-			job.Stdout.Write([]byte(" \"" + parentImage.ID + "\" -> \"" + image.ID + "\"\n"))
367
-		} else {
368
-			job.Stdout.Write([]byte(" base -> \"" + image.ID + "\" [style=invis]\n"))
369
-		}
370
-	}
371
-
372
-	for id, repos := range srv.daemon.Repositories().GetRepoRefs() {
373
-		job.Stdout.Write([]byte(" \"" + id + "\" [label=\"" + id + "\\n" + strings.Join(repos, "\\n") + "\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n"))
374
-	}
375
-	job.Stdout.Write([]byte(" base [style=invisible]\n}\n"))
376
-	return engine.StatusOK
377
-}
378
-
379
-func (srv *Server) Images(job *engine.Job) engine.Status {
380
-	var (
381
-		allImages   map[string]*image.Image
382
-		err         error
383
-		filt_tagged = true
384
-	)
385
-
386
-	imageFilters, err := filters.FromParam(job.Getenv("filters"))
387
-	if err != nil {
388
-		return job.Error(err)
389
-	}
390
-	if i, ok := imageFilters["dangling"]; ok {
391
-		for _, value := range i {
392
-			if strings.ToLower(value) == "true" {
393
-				filt_tagged = false
394
-			}
395
-		}
396
-	}
397
-
398
-	if job.GetenvBool("all") && filt_tagged {
399
-		allImages, err = srv.daemon.Graph().Map()
400
-	} else {
401
-		allImages, err = srv.daemon.Graph().Heads()
402
-	}
403
-	if err != nil {
404
-		return job.Error(err)
405
-	}
406
-	lookup := make(map[string]*engine.Env)
407
-	srv.daemon.Repositories().Lock()
408
-	for name, repository := range srv.daemon.Repositories().Repositories {
409
-		if job.Getenv("filter") != "" {
410
-			if match, _ := path.Match(job.Getenv("filter"), name); !match {
411
-				continue
412
-			}
413
-		}
414
-		for tag, id := range repository {
415
-			image, err := srv.daemon.Graph().Get(id)
416
-			if err != nil {
417
-				log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err)
418
-				continue
419
-			}
420
-
421
-			if out, exists := lookup[id]; exists {
422
-				if filt_tagged {
423
-					out.SetList("RepoTags", append(out.GetList("RepoTags"), fmt.Sprintf("%s:%s", name, tag)))
424
-				}
425
-			} else {
426
-				// get the boolean list for if only the untagged images are requested
427
-				delete(allImages, id)
428
-				if filt_tagged {
429
-					out := &engine.Env{}
430
-					out.Set("ParentId", image.Parent)
431
-					out.SetList("RepoTags", []string{fmt.Sprintf("%s:%s", name, tag)})
432
-					out.Set("Id", image.ID)
433
-					out.SetInt64("Created", image.Created.Unix())
434
-					out.SetInt64("Size", image.Size)
435
-					out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
436
-					lookup[id] = out
437
-				}
438
-			}
439
-
440
-		}
441
-	}
442
-	srv.daemon.Repositories().Unlock()
443
-
444
-	outs := engine.NewTable("Created", len(lookup))
445
-	for _, value := range lookup {
446
-		outs.Add(value)
447
-	}
448
-
449
-	// Display images which aren't part of a repository/tag
450
-	if job.Getenv("filter") == "" {
451
-		for _, image := range allImages {
452
-			out := &engine.Env{}
453
-			out.Set("ParentId", image.Parent)
454
-			out.SetList("RepoTags", []string{"<none>:<none>"})
455
-			out.Set("Id", image.ID)
456
-			out.SetInt64("Created", image.Created.Unix())
457
-			out.SetInt64("Size", image.Size)
458
-			out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
459
-			outs.Add(out)
460
-		}
461
-	}
462
-
463
-	outs.ReverseSort()
464
-	if _, err := outs.WriteListTo(job.Stdout); err != nil {
465
-		return job.Error(err)
466
-	}
467
-	return engine.StatusOK
468
-}
469
-
470
-func (srv *Server) ImageHistory(job *engine.Job) engine.Status {
471
-	if n := len(job.Args); n != 1 {
472
-		return job.Errorf("Usage: %s IMAGE", job.Name)
473
-	}
474
-	name := job.Args[0]
475
-	foundImage, err := srv.daemon.Repositories().LookupImage(name)
476
-	if err != nil {
477
-		return job.Error(err)
478
-	}
479
-
480
-	lookupMap := make(map[string][]string)
481
-	for name, repository := range srv.daemon.Repositories().Repositories {
482
-		for tag, id := range repository {
483
-			// If the ID already has a reverse lookup, do not update it unless for "latest"
484
-			if _, exists := lookupMap[id]; !exists {
485
-				lookupMap[id] = []string{}
486
-			}
487
-			lookupMap[id] = append(lookupMap[id], name+":"+tag)
488
-		}
489
-	}
490
-
491
-	outs := engine.NewTable("Created", 0)
492
-	err = foundImage.WalkHistory(func(img *image.Image) error {
493
-		out := &engine.Env{}
494
-		out.Set("Id", img.ID)
495
-		out.SetInt64("Created", img.Created.Unix())
496
-		out.Set("CreatedBy", strings.Join(img.ContainerConfig.Cmd, " "))
497
-		out.SetList("Tags", lookupMap[img.ID])
498
-		out.SetInt64("Size", img.Size)
499
-		outs.Add(out)
500
-		return nil
501
-	})
502
-	if _, err := outs.WriteListTo(job.Stdout); err != nil {
503
-		return job.Error(err)
504
-	}
505
-	return engine.StatusOK
506
-}
507
-func (srv *Server) ImageTag(job *engine.Job) engine.Status {
508
-	if len(job.Args) != 2 && len(job.Args) != 3 {
509
-		return job.Errorf("Usage: %s IMAGE REPOSITORY [TAG]\n", job.Name)
510
-	}
511
-	var tag string
512
-	if len(job.Args) == 3 {
513
-		tag = job.Args[2]
514
-	}
515
-	if err := srv.daemon.Repositories().Set(job.Args[1], tag, job.Args[0], job.GetenvBool("force")); err != nil {
516
-		return job.Error(err)
517
-	}
518
-	return engine.StatusOK
519
-}
520
-
521 245
 func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoint string, token []string, sf *utils.StreamFormatter) error {
522 246
 	history, err := r.GetRemoteHistory(imgID, endpoint, token)
523 247
 	if err != nil {
... ...
@@ -1038,198 +625,6 @@ func (srv *Server) ImagePush(job *engine.Job) engine.Status {
1038 1038
 	return engine.StatusOK
1039 1039
 }
1040 1040
 
1041
-func (srv *Server) ImageImport(job *engine.Job) engine.Status {
1042
-	if n := len(job.Args); n != 2 && n != 3 {
1043
-		return job.Errorf("Usage: %s SRC REPO [TAG]", job.Name)
1044
-	}
1045
-	var (
1046
-		src     = job.Args[0]
1047
-		repo    = job.Args[1]
1048
-		tag     string
1049
-		sf      = utils.NewStreamFormatter(job.GetenvBool("json"))
1050
-		archive archive.ArchiveReader
1051
-		resp    *http.Response
1052
-	)
1053
-	if len(job.Args) > 2 {
1054
-		tag = job.Args[2]
1055
-	}
1056
-
1057
-	if src == "-" {
1058
-		archive = job.Stdin
1059
-	} else {
1060
-		u, err := url.Parse(src)
1061
-		if err != nil {
1062
-			return job.Error(err)
1063
-		}
1064
-		if u.Scheme == "" {
1065
-			u.Scheme = "http"
1066
-			u.Host = src
1067
-			u.Path = ""
1068
-		}
1069
-		job.Stdout.Write(sf.FormatStatus("", "Downloading from %s", u))
1070
-		resp, err = utils.Download(u.String())
1071
-		if err != nil {
1072
-			return job.Error(err)
1073
-		}
1074
-		progressReader := utils.ProgressReader(resp.Body, int(resp.ContentLength), job.Stdout, sf, true, "", "Importing")
1075
-		defer progressReader.Close()
1076
-		archive = progressReader
1077
-	}
1078
-	img, err := srv.daemon.Graph().Create(archive, "", "", "Imported from "+src, "", nil, nil)
1079
-	if err != nil {
1080
-		return job.Error(err)
1081
-	}
1082
-	// Optionally register the image at REPO/TAG
1083
-	if repo != "" {
1084
-		if err := srv.daemon.Repositories().Set(repo, tag, img.ID, true); err != nil {
1085
-			return job.Error(err)
1086
-		}
1087
-	}
1088
-	job.Stdout.Write(sf.FormatStatus("", img.ID))
1089
-	return engine.StatusOK
1090
-}
1091
-func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force, noprune bool) error {
1092
-	var (
1093
-		repoName, tag string
1094
-		tags          = []string{}
1095
-		tagDeleted    bool
1096
-	)
1097
-
1098
-	repoName, tag = parsers.ParseRepositoryTag(name)
1099
-	if tag == "" {
1100
-		tag = graph.DEFAULTTAG
1101
-	}
1102
-
1103
-	img, err := srv.daemon.Repositories().LookupImage(name)
1104
-	if err != nil {
1105
-		if r, _ := srv.daemon.Repositories().Get(repoName); r != nil {
1106
-			return fmt.Errorf("No such image: %s:%s", repoName, tag)
1107
-		}
1108
-		return fmt.Errorf("No such image: %s", name)
1109
-	}
1110
-
1111
-	if strings.Contains(img.ID, name) {
1112
-		repoName = ""
1113
-		tag = ""
1114
-	}
1115
-
1116
-	byParents, err := srv.daemon.Graph().ByParent()
1117
-	if err != nil {
1118
-		return err
1119
-	}
1120
-
1121
-	//If delete by id, see if the id belong only to one repository
1122
-	if repoName == "" {
1123
-		for _, repoAndTag := range srv.daemon.Repositories().ByID()[img.ID] {
1124
-			parsedRepo, parsedTag := parsers.ParseRepositoryTag(repoAndTag)
1125
-			if repoName == "" || repoName == parsedRepo {
1126
-				repoName = parsedRepo
1127
-				if parsedTag != "" {
1128
-					tags = append(tags, parsedTag)
1129
-				}
1130
-			} else if repoName != parsedRepo && !force {
1131
-				// the id belongs to multiple repos, like base:latest and user:test,
1132
-				// in that case return conflict
1133
-				return fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", name)
1134
-			}
1135
-		}
1136
-	} else {
1137
-		tags = append(tags, tag)
1138
-	}
1139
-
1140
-	if !first && len(tags) > 0 {
1141
-		return nil
1142
-	}
1143
-
1144
-	//Untag the current image
1145
-	for _, tag := range tags {
1146
-		tagDeleted, err = srv.daemon.Repositories().Delete(repoName, tag)
1147
-		if err != nil {
1148
-			return err
1149
-		}
1150
-		if tagDeleted {
1151
-			out := &engine.Env{}
1152
-			out.Set("Untagged", repoName+":"+tag)
1153
-			imgs.Add(out)
1154
-			srv.LogEvent("untag", img.ID, "")
1155
-		}
1156
-	}
1157
-	tags = srv.daemon.Repositories().ByID()[img.ID]
1158
-	if (len(tags) <= 1 && repoName == "") || len(tags) == 0 {
1159
-		if len(byParents[img.ID]) == 0 {
1160
-			if err := srv.canDeleteImage(img.ID, force, tagDeleted); err != nil {
1161
-				return err
1162
-			}
1163
-			if err := srv.daemon.Repositories().DeleteAll(img.ID); err != nil {
1164
-				return err
1165
-			}
1166
-			if err := srv.daemon.Graph().Delete(img.ID); err != nil {
1167
-				return err
1168
-			}
1169
-			out := &engine.Env{}
1170
-			out.Set("Deleted", img.ID)
1171
-			imgs.Add(out)
1172
-			srv.LogEvent("delete", img.ID, "")
1173
-			if img.Parent != "" && !noprune {
1174
-				err := srv.DeleteImage(img.Parent, imgs, false, force, noprune)
1175
-				if first {
1176
-					return err
1177
-				}
1178
-
1179
-			}
1180
-
1181
-		}
1182
-	}
1183
-	return nil
1184
-}
1185
-
1186
-func (srv *Server) ImageDelete(job *engine.Job) engine.Status {
1187
-	if n := len(job.Args); n != 1 {
1188
-		return job.Errorf("Usage: %s IMAGE", job.Name)
1189
-	}
1190
-	imgs := engine.NewTable("", 0)
1191
-	if err := srv.DeleteImage(job.Args[0], imgs, true, job.GetenvBool("force"), job.GetenvBool("noprune")); err != nil {
1192
-		return job.Error(err)
1193
-	}
1194
-	if len(imgs.Data) == 0 {
1195
-		return job.Errorf("Conflict, %s wasn't deleted", job.Args[0])
1196
-	}
1197
-	if _, err := imgs.WriteListTo(job.Stdout); err != nil {
1198
-		return job.Error(err)
1199
-	}
1200
-	return engine.StatusOK
1201
-}
1202
-
1203
-func (srv *Server) canDeleteImage(imgID string, force, untagged bool) error {
1204
-	var message string
1205
-	if untagged {
1206
-		message = " (docker untagged the image)"
1207
-	}
1208
-	for _, container := range srv.daemon.List() {
1209
-		parent, err := srv.daemon.Repositories().LookupImage(container.Image)
1210
-		if err != nil {
1211
-			return err
1212
-		}
1213
-
1214
-		if err := parent.WalkHistory(func(p *image.Image) error {
1215
-			if imgID == p.ID {
1216
-				if container.State.IsRunning() {
1217
-					if force {
1218
-						return fmt.Errorf("Conflict, cannot force delete %s because the running container %s is using it%s, stop it and retry", utils.TruncateID(imgID), utils.TruncateID(container.ID), message)
1219
-					}
1220
-					return fmt.Errorf("Conflict, cannot delete %s because the running container %s is using it%s, stop it and use -f to force", utils.TruncateID(imgID), utils.TruncateID(container.ID), message)
1221
-				} else if !force {
1222
-					return fmt.Errorf("Conflict, cannot delete %s because the container %s is using it%s, use -f to force", utils.TruncateID(imgID), utils.TruncateID(container.ID), message)
1223
-				}
1224
-			}
1225
-			return nil
1226
-		}); err != nil {
1227
-			return err
1228
-		}
1229
-	}
1230
-	return nil
1231
-}
1232
-
1233 1041
 func (srv *Server) poolAdd(kind, key string) (chan struct{}, error) {
1234 1042
 	srv.Lock()
1235 1043
 	defer srv.Unlock()
... ...
@@ -86,20 +86,10 @@ func InitServer(job *engine.Job) engine.Status {
86 86
 	job.Eng.Hack_SetGlobalVar("httpapi.daemon", srv.daemon)
87 87
 
88 88
 	for name, handler := range map[string]engine.Handler{
89
-		"tag":          srv.ImageTag, // FIXME merge with "image_tag"
90
-		"info":         srv.DockerInfo,
91
-		"image_export": srv.ImageExport,
92
-		"images":       srv.Images,
93
-		"history":      srv.ImageHistory,
94
-		"viz":          srv.ImagesViz,
95
-		"log":          srv.Log,
96
-		"load":         srv.ImageLoad,
97
-		"build":        srv.Build,
98
-		"pull":         srv.ImagePull,
99
-		"import":       srv.ImageImport,
100
-		"image_delete": srv.ImageDelete,
101
-		"events":       srv.Events,
102
-		"push":         srv.ImagePush,
89
+		"info":  srv.DockerInfo,
90
+		"build": srv.Build,
91
+		"pull":  srv.ImagePull,
92
+		"push":  srv.ImagePush,
103 93
 	} {
104 94
 		if err := job.Eng.Register(name, srv.handlerWrap(handler)); err != nil {
105 95
 			return job.Error(err)
... ...
@@ -125,12 +115,10 @@ func NewServer(eng *engine.Engine, config *daemonconfig.Config) (*Server, error)
125 125
 		return nil, err
126 126
 	}
127 127
 	srv := &Server{
128
-		Eng:            eng,
129
-		daemon:         daemon,
130
-		pullingPool:    make(map[string]chan struct{}),
131
-		pushingPool:    make(map[string]chan struct{}),
132
-		events:         make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events
133
-		eventPublisher: utils.NewJSONMessagePublisher(),
128
+		Eng:         eng,
129
+		daemon:      daemon,
130
+		pullingPool: make(map[string]chan struct{}),
131
+		pushingPool: make(map[string]chan struct{}),
134 132
 	}
135 133
 	daemon.SetServer(srv)
136 134
 	return srv, nil
... ...
@@ -67,6 +67,11 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status {
67 67
 		initPath = srv.daemon.SystemInitPath()
68 68
 	}
69 69
 
70
+	cjob := job.Eng.Job("subscribers_count")
71
+	env, _ := cjob.Stdout.AddEnv()
72
+	if err := cjob.Run(); err != nil {
73
+		return job.Error(err)
74
+	}
70 75
 	v := &engine.Env{}
71 76
 	v.SetInt("Containers", len(srv.daemon.List()))
72 77
 	v.SetInt("Images", imgcount)
... ...
@@ -79,7 +84,7 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status {
79 79
 	v.SetInt("NFd", utils.GetTotalUsedFds())
80 80
 	v.SetInt("NGoroutines", runtime.NumGoroutine())
81 81
 	v.Set("ExecutionDriver", srv.daemon.ExecutionDriver().Name())
82
-	v.SetInt("NEventsListener", srv.eventPublisher.SubscribersCount())
82
+	v.SetInt("NEventsListener", env.GetInt("count"))
83 83
 	v.Set("KernelVersion", kernelVersion)
84 84
 	v.Set("OperatingSystem", operatingSystem)
85 85
 	v.Set("IndexServerAddress", registry.IndexServerAddress())
... ...
@@ -128,12 +133,10 @@ func (srv *Server) Close() error {
128 128
 
129 129
 type Server struct {
130 130
 	sync.RWMutex
131
-	daemon         *daemon.Daemon
132
-	pullingPool    map[string]chan struct{}
133
-	pushingPool    map[string]chan struct{}
134
-	events         []utils.JSONMessage
135
-	eventPublisher *utils.JSONMessagePublisher
136
-	Eng            *engine.Engine
137
-	running        bool
138
-	tasks          sync.WaitGroup
131
+	daemon      *daemon.Daemon
132
+	pullingPool map[string]chan struct{}
133
+	pushingPool map[string]chan struct{}
134
+	Eng         *engine.Engine
135
+	running     bool
136
+	tasks       sync.WaitGroup
139 137
 }
... ...
@@ -1,11 +1,6 @@
1 1
 package server
2 2
 
3
-import (
4
-	"testing"
5
-	"time"
6
-
7
-	"github.com/docker/docker/utils"
8
-)
3
+import "testing"
9 4
 
10 5
 func TestPools(t *testing.T) {
11 6
 	srv := &Server{
... ...
@@ -44,55 +39,3 @@ func TestPools(t *testing.T) {
44 44
 		t.Fatalf("Expected `Unknown pool type`")
45 45
 	}
46 46
 }
47
-
48
-func TestLogEvent(t *testing.T) {
49
-	srv := &Server{
50
-		events:         make([]utils.JSONMessage, 0, 64),
51
-		eventPublisher: utils.NewJSONMessagePublisher(),
52
-	}
53
-
54
-	srv.LogEvent("fakeaction", "fakeid", "fakeimage")
55
-
56
-	listener := make(chan utils.JSONMessage)
57
-	srv.eventPublisher.Subscribe(listener)
58
-
59
-	srv.LogEvent("fakeaction2", "fakeid", "fakeimage")
60
-
61
-	numEvents := len(srv.GetEvents())
62
-	if numEvents != 2 {
63
-		t.Fatalf("Expected 2 events, found %d", numEvents)
64
-	}
65
-	go func() {
66
-		time.Sleep(200 * time.Millisecond)
67
-		srv.LogEvent("fakeaction3", "fakeid", "fakeimage")
68
-		time.Sleep(200 * time.Millisecond)
69
-		srv.LogEvent("fakeaction4", "fakeid", "fakeimage")
70
-	}()
71
-
72
-	setTimeout(t, "Listening for events timed out", 2*time.Second, func() {
73
-		for i := 2; i < 4; i++ {
74
-			event := <-listener
75
-			if event != srv.GetEvents()[i] {
76
-				t.Fatalf("Event received it different than expected")
77
-			}
78
-		}
79
-	})
80
-}
81
-
82
-// FIXME: this is duplicated from integration/commands_test.go
83
-func setTimeout(t *testing.T, msg string, d time.Duration, f func()) {
84
-	c := make(chan bool)
85
-
86
-	// Make sure we are not too long
87
-	go func() {
88
-		time.Sleep(d)
89
-		c <- true
90
-	}()
91
-	go func() {
92
-		f()
93
-		c <- false
94
-	}()
95
-	if <-c && msg != "" {
96
-		t.Fatal(msg)
97
-	}
98
-}
99 47
deleted file mode 100644
... ...
@@ -1,61 +0,0 @@
1
-package utils
2
-
3
-import (
4
-	"sync"
5
-	"time"
6
-)
7
-
8
-func NewJSONMessagePublisher() *JSONMessagePublisher {
9
-	return &JSONMessagePublisher{}
10
-}
11
-
12
-type JSONMessageListener chan<- JSONMessage
13
-
14
-type JSONMessagePublisher struct {
15
-	m           sync.RWMutex
16
-	subscribers []JSONMessageListener
17
-}
18
-
19
-func (p *JSONMessagePublisher) Subscribe(l JSONMessageListener) {
20
-	p.m.Lock()
21
-	p.subscribers = append(p.subscribers, l)
22
-	p.m.Unlock()
23
-}
24
-
25
-func (p *JSONMessagePublisher) SubscribersCount() int {
26
-	p.m.RLock()
27
-	count := len(p.subscribers)
28
-	p.m.RUnlock()
29
-	return count
30
-}
31
-
32
-// Unsubscribe closes and removes the specified listener from the list of
33
-// previously registed ones.
34
-// It returns a boolean value indicating if the listener was successfully
35
-// found, closed and unregistered.
36
-func (p *JSONMessagePublisher) Unsubscribe(l JSONMessageListener) bool {
37
-	p.m.Lock()
38
-	defer p.m.Unlock()
39
-
40
-	for i, subscriber := range p.subscribers {
41
-		if subscriber == l {
42
-			close(l)
43
-			p.subscribers = append(p.subscribers[:i], p.subscribers[i+1:]...)
44
-			return true
45
-		}
46
-	}
47
-	return false
48
-}
49
-
50
-func (p *JSONMessagePublisher) Publish(m JSONMessage) {
51
-	p.m.RLock()
52
-	for _, subscriber := range p.subscribers {
53
-		// We give each subscriber a 100ms time window to receive the event,
54
-		// after which we move to the next.
55
-		select {
56
-		case subscriber <- m:
57
-		case <-time.After(100 * time.Millisecond):
58
-		}
59
-	}
60
-	p.m.RUnlock()
61
-}
62 1
deleted file mode 100644
... ...
@@ -1,73 +0,0 @@
1
-package utils
2
-
3
-import (
4
-	"testing"
5
-	"time"
6
-)
7
-
8
-func assertSubscribersCount(t *testing.T, q *JSONMessagePublisher, expected int) {
9
-	if q.SubscribersCount() != expected {
10
-		t.Fatalf("Expected %d registered subscribers, got %d", expected, q.SubscribersCount())
11
-	}
12
-}
13
-
14
-func TestJSONMessagePublisherSubscription(t *testing.T) {
15
-	q := NewJSONMessagePublisher()
16
-	l1 := make(chan JSONMessage)
17
-	l2 := make(chan JSONMessage)
18
-
19
-	assertSubscribersCount(t, q, 0)
20
-	q.Subscribe(l1)
21
-	assertSubscribersCount(t, q, 1)
22
-	q.Subscribe(l2)
23
-	assertSubscribersCount(t, q, 2)
24
-
25
-	q.Unsubscribe(l1)
26
-	q.Unsubscribe(l2)
27
-	assertSubscribersCount(t, q, 0)
28
-}
29
-
30
-func TestJSONMessagePublisherPublish(t *testing.T) {
31
-	q := NewJSONMessagePublisher()
32
-	l1 := make(chan JSONMessage)
33
-	l2 := make(chan JSONMessage)
34
-
35
-	go func() {
36
-		for {
37
-			select {
38
-			case <-l1:
39
-				close(l1)
40
-				l1 = nil
41
-			case <-l2:
42
-				close(l2)
43
-				l2 = nil
44
-			case <-time.After(1 * time.Second):
45
-				q.Unsubscribe(l1)
46
-				q.Unsubscribe(l2)
47
-				t.Fatal("Timeout waiting for broadcasted message")
48
-			}
49
-		}
50
-	}()
51
-
52
-	q.Subscribe(l1)
53
-	q.Subscribe(l2)
54
-	q.Publish(JSONMessage{})
55
-}
56
-
57
-func TestJSONMessagePublishTimeout(t *testing.T) {
58
-	q := NewJSONMessagePublisher()
59
-	l := make(chan JSONMessage)
60
-	q.Subscribe(l)
61
-
62
-	c := make(chan struct{})
63
-	go func() {
64
-		q.Publish(JSONMessage{})
65
-		close(c)
66
-	}()
67
-
68
-	select {
69
-	case <-c:
70
-	case <-time.After(time.Second):
71
-		t.Fatal("Timeout publishing message")
72
-	}
73
-}