Browse code

Merge pull request #18888 from calavera/event_types

Event all the things!

David Calavera authored on 2016/01/05 06:07:33
Showing 40 changed files
... ...
@@ -1,11 +1,18 @@
1 1
 package client
2 2
 
3 3
 import (
4
+	"encoding/json"
5
+	"fmt"
6
+	"io"
7
+	"strings"
8
+	"time"
9
+
4 10
 	"github.com/docker/docker/api/types"
11
+	eventtypes "github.com/docker/docker/api/types/events"
5 12
 	"github.com/docker/docker/api/types/filters"
6 13
 	Cli "github.com/docker/docker/cli"
7 14
 	"github.com/docker/docker/opts"
8
-	"github.com/docker/docker/pkg/jsonmessage"
15
+	"github.com/docker/docker/pkg/jsonlog"
9 16
 	flag "github.com/docker/docker/pkg/mflag"
10 17
 )
11 18
 
... ...
@@ -46,5 +53,56 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
46 46
 	}
47 47
 	defer responseBody.Close()
48 48
 
49
-	return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut)
49
+	return streamEvents(responseBody, cli.out)
50
+}
51
+
52
+// streamEvents decodes prints the incoming events in the provided output.
53
+func streamEvents(input io.Reader, output io.Writer) error {
54
+	return decodeEvents(input, func(event eventtypes.Message, err error) error {
55
+		if err != nil {
56
+			return err
57
+		}
58
+		printOutput(event, output)
59
+		return nil
60
+	})
61
+}
62
+
63
+type eventProcessor func(event eventtypes.Message, err error) error
64
+
65
+func decodeEvents(input io.Reader, ep eventProcessor) error {
66
+	dec := json.NewDecoder(input)
67
+	for {
68
+		var event eventtypes.Message
69
+		err := dec.Decode(&event)
70
+		if err != nil && err == io.EOF {
71
+			break
72
+		}
73
+
74
+		if procErr := ep(event, err); procErr != nil {
75
+			return procErr
76
+		}
77
+	}
78
+	return nil
79
+}
80
+
81
+// printOutput prints all types of event information.
82
+// Each output includes the event type, actor id, name and action.
83
+// Actor attributes are printed at the end if the actor has any.
84
+func printOutput(event eventtypes.Message, output io.Writer) {
85
+	if event.TimeNano != 0 {
86
+		fmt.Fprintf(output, "%s ", time.Unix(0, event.TimeNano).Format(jsonlog.RFC3339NanoFixed))
87
+	} else if event.Time != 0 {
88
+		fmt.Fprintf(output, "%s ", time.Unix(event.Time, 0).Format(jsonlog.RFC3339NanoFixed))
89
+	}
90
+
91
+	fmt.Fprintf(output, "%s %s %s", event.Type, event.Action, event.Actor.ID)
92
+
93
+	if len(event.Actor.Attributes) > 0 {
94
+		var attrs []string
95
+		for k, v := range event.Actor.Attributes {
96
+			attrs = append(attrs, fmt.Sprintf("%s=%s", k, v))
97
+		}
98
+		fmt.Fprintf(output, " (%s)", strings.Join(attrs, ", "))
99
+	}
100
+	fmt.Fprint(output, "\n")
50 101
 }
... ...
@@ -11,8 +11,9 @@ import (
11 11
 	"time"
12 12
 
13 13
 	"github.com/docker/docker/api/types"
14
+	"github.com/docker/docker/api/types/events"
15
+	"github.com/docker/docker/api/types/filters"
14 16
 	Cli "github.com/docker/docker/cli"
15
-	"github.com/docker/docker/pkg/jsonmessage"
16 17
 	"github.com/docker/go-units"
17 18
 )
18 19
 
... ...
@@ -189,7 +190,11 @@ func (cli *DockerCli) CmdStats(args ...string) error {
189 189
 			err   error
190 190
 		}
191 191
 		getNewContainers := func(c chan<- watch) {
192
-			options := types.EventsOptions{}
192
+			f := filters.NewArgs()
193
+			f.Add("type", "container")
194
+			options := types.EventsOptions{
195
+				Filters: f,
196
+			}
193 197
 			resBody, err := cli.client.Events(options)
194 198
 			if err != nil {
195 199
 				c <- watch{err: err}
... ...
@@ -197,15 +202,15 @@ func (cli *DockerCli) CmdStats(args ...string) error {
197 197
 			}
198 198
 			defer resBody.Close()
199 199
 
200
-			dec := json.NewDecoder(resBody)
201
-			for {
202
-				var j *jsonmessage.JSONMessage
203
-				if err := dec.Decode(&j); err != nil {
200
+			decodeEvents(resBody, func(event events.Message, err error) error {
201
+				if err != nil {
204 202
 					c <- watch{err: err}
205
-					return
203
+					return nil
206 204
 				}
207
-				c <- watch{j.ID[:12], j.Status, nil}
208
-			}
205
+
206
+				c <- watch{event.ID[:12], event.Action, nil}
207
+				return nil
208
+			})
209 209
 		}
210 210
 		go func(stopChan chan<- error) {
211 211
 			cChan := make(chan watch)
... ...
@@ -19,4 +19,5 @@ type Backend interface {
19 19
 	DisconnectContainerFromNetwork(containerName string,
20 20
 		network libnetwork.Network) error
21 21
 	NetworkControllerEnabled() bool
22
+	DeleteNetwork(name string) error
22 23
 }
... ...
@@ -148,21 +148,7 @@ func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.Respon
148 148
 }
149 149
 
150 150
 func (n *networkRouter) deleteNetwork(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
151
-	if err := httputils.ParseForm(r); err != nil {
152
-		return err
153
-	}
154
-
155
-	nw, err := n.backend.FindNetwork(vars["id"])
156
-	if err != nil {
157
-		return err
158
-	}
159
-
160
-	if runconfig.IsPreDefinedNetwork(nw.Name()) {
161
-		return httputils.WriteJSON(w, http.StatusForbidden,
162
-			fmt.Sprintf("%s is a pre-defined network and cannot be removed", nw.Name()))
163
-	}
164
-
165
-	return nw.Delete()
151
+	return n.backend.DeleteNetwork(vars["id"])
166 152
 }
167 153
 
168 154
 func buildNetworkResource(nw libnetwork.Network) *types.NetworkResource {
... ...
@@ -2,8 +2,8 @@ package system
2 2
 
3 3
 import (
4 4
 	"github.com/docker/docker/api/types"
5
+	"github.com/docker/docker/api/types/events"
5 6
 	"github.com/docker/docker/api/types/filters"
6
-	"github.com/docker/docker/pkg/jsonmessage"
7 7
 )
8 8
 
9 9
 // Backend is the methods that need to be implemented to provide
... ...
@@ -11,7 +11,7 @@ import (
11 11
 type Backend interface {
12 12
 	SystemInfo() (*types.Info, error)
13 13
 	SystemVersion() types.Version
14
-	SubscribeToEvents(since, sinceNano int64, ef filters.Args) ([]*jsonmessage.JSONMessage, chan interface{})
14
+	SubscribeToEvents(since, sinceNano int64, ef filters.Args) ([]events.Message, chan interface{})
15 15
 	UnsubscribeFromEvents(chan interface{})
16 16
 	AuthenticateToRegistry(authConfig *types.AuthConfig) (string, error)
17 17
 }
... ...
@@ -9,10 +9,10 @@ import (
9 9
 	"github.com/docker/docker/api"
10 10
 	"github.com/docker/docker/api/server/httputils"
11 11
 	"github.com/docker/docker/api/types"
12
+	"github.com/docker/docker/api/types/events"
12 13
 	"github.com/docker/docker/api/types/filters"
13 14
 	timetypes "github.com/docker/docker/api/types/time"
14 15
 	"github.com/docker/docker/pkg/ioutils"
15
-	"github.com/docker/docker/pkg/jsonmessage"
16 16
 	"golang.org/x/net/context"
17 17
 )
18 18
 
... ...
@@ -98,8 +98,9 @@ func (s *systemRouter) getEvents(ctx context.Context, w http.ResponseWriter, r *
98 98
 	for {
99 99
 		select {
100 100
 		case ev := <-l:
101
-			jev, ok := ev.(*jsonmessage.JSONMessage)
101
+			jev, ok := ev.(events.Message)
102 102
 			if !ok {
103
+				logrus.Warnf("unexpected event message: %q", ev)
103 104
 				continue
104 105
 			}
105 106
 			if err := enc.Encode(jev); err != nil {
106 107
new file mode 100644
... ...
@@ -0,0 +1,38 @@
0
+package events
1
+
2
+const (
3
+	// ContainerEventType is the event type that containers generate
4
+	ContainerEventType = "container"
5
+	// ImageEventType is the event type that images generate
6
+	ImageEventType = "image"
7
+	// VolumeEventType is the event type that volumes generate
8
+	VolumeEventType = "volume"
9
+	// NetworkEventType is the event type that networks generate
10
+	NetworkEventType = "network"
11
+)
12
+
13
+// Actor describes something that generates events,
14
+// like a container, or a network, or a volume.
15
+// It has a defined name and a set or attributes.
16
+// The container attributes are its labels, other actors
17
+// can generate these attributes from other properties.
18
+type Actor struct {
19
+	ID         string
20
+	Attributes map[string]string
21
+}
22
+
23
+// Message represents the information an event contains
24
+type Message struct {
25
+	// Deprecated information from JSONMessage.
26
+	// With data only in container events.
27
+	Status string `json:"status,omitempty"`
28
+	ID     string `json:"id,omitempty"`
29
+	From   string `json:"from,omitempty"`
30
+
31
+	Type   string
32
+	Action string
33
+	Actor  Actor
34
+
35
+	Time     int64 `json:"time,omitempty"`
36
+	TimeNano int64 `json:"timeNano,omitempty"`
37
+}
... ...
@@ -197,6 +197,22 @@ func (filters Args) ExactMatch(field, source string) bool {
197 197
 	return false
198 198
 }
199 199
 
200
+// FuzzyMatch returns true if the source matches exactly one of the filters,
201
+// or the source has one of the filters as a prefix.
202
+func (filters Args) FuzzyMatch(field, source string) bool {
203
+	if filters.ExactMatch(field, source) {
204
+		return true
205
+	}
206
+
207
+	fieldValues := filters.fields[field]
208
+	for prefix := range fieldValues {
209
+		if strings.HasPrefix(source, prefix) {
210
+			return true
211
+		}
212
+	}
213
+	return false
214
+}
215
+
200 216
 // Include returns true if the name of the field to filter is in the filters.
201 217
 func (filters Args) Include(field string) bool {
202 218
 	_, ok := filters.fields[field]
... ...
@@ -349,3 +349,21 @@ func TestWalkValues(t *testing.T) {
349 349
 		t.Fatalf("Expected to not iterate when the field doesn't exist, got %v", err)
350 350
 	}
351 351
 }
352
+
353
+func TestFuzzyMatch(t *testing.T) {
354
+	f := NewArgs()
355
+	f.Add("container", "foo")
356
+
357
+	cases := map[string]bool{
358
+		"foo":    true,
359
+		"foobar": true,
360
+		"barfoo": false,
361
+		"bar":    false,
362
+	}
363
+	for source, match := range cases {
364
+		got := f.FuzzyMatch("container", source)
365
+		if got != match {
366
+			t.Fatalf("Expected %v, got %v: %s", match, got, source)
367
+		}
368
+	}
369
+}
... ...
@@ -618,7 +618,7 @@ func detachMounted(path string) error {
618 618
 }
619 619
 
620 620
 // UnmountVolumes unmounts all volumes
621
-func (container *Container) UnmountVolumes(forceSyscall bool) error {
621
+func (container *Container) UnmountVolumes(forceSyscall bool, volumeEventLog func(name, action string, attributes map[string]string)) error {
622 622
 	var (
623 623
 		volumeMounts []volume.MountPoint
624 624
 		err          error
... ...
@@ -649,6 +649,12 @@ func (container *Container) UnmountVolumes(forceSyscall bool) error {
649 649
 			if err := volumeMount.Volume.Unmount(); err != nil {
650 650
 				return err
651 651
 			}
652
+
653
+			attributes := map[string]string{
654
+				"driver":    volumeMount.Volume.DriverName(),
655
+				"container": container.ID,
656
+			}
657
+			volumeEventLog(volumeMount.Volume.Name(), "unmount", attributes)
652 658
 		}
653 659
 	}
654 660
 
... ...
@@ -39,7 +39,7 @@ func (container *Container) IpcMounts() []execdriver.Mount {
39 39
 }
40 40
 
41 41
 // UnmountVolumes explicitly unmounts volumes from the container.
42
-func (container *Container) UnmountVolumes(forceSyscall bool) error {
42
+func (container *Container) UnmountVolumes(forceSyscall bool, volumeEventLog func(name, action string, attributes map[string]string)) error {
43 43
 	return nil
44 44
 }
45 45
 
... ...
@@ -84,7 +84,7 @@ func (daemon *Daemon) containerStatPath(container *container.Container, path str
84 84
 	defer daemon.Unmount(container)
85 85
 
86 86
 	err = daemon.mountVolumes(container)
87
-	defer container.UnmountVolumes(true)
87
+	defer container.UnmountVolumes(true, daemon.LogVolumeEvent)
88 88
 	if err != nil {
89 89
 		return nil, err
90 90
 	}
... ...
@@ -119,7 +119,7 @@ func (daemon *Daemon) containerArchivePath(container *container.Container, path
119 119
 	defer func() {
120 120
 		if err != nil {
121 121
 			// unmount any volumes
122
-			container.UnmountVolumes(true)
122
+			container.UnmountVolumes(true, daemon.LogVolumeEvent)
123 123
 			// unmount the container's rootfs
124 124
 			daemon.Unmount(container)
125 125
 		}
... ...
@@ -154,7 +154,7 @@ func (daemon *Daemon) containerArchivePath(container *container.Container, path
154 154
 
155 155
 	content = ioutils.NewReadCloserWrapper(data, func() error {
156 156
 		err := data.Close()
157
-		container.UnmountVolumes(true)
157
+		container.UnmountVolumes(true, daemon.LogVolumeEvent)
158 158
 		daemon.Unmount(container)
159 159
 		container.Unlock()
160 160
 		return err
... ...
@@ -181,7 +181,7 @@ func (daemon *Daemon) containerExtractToDir(container *container.Container, path
181 181
 	defer daemon.Unmount(container)
182 182
 
183 183
 	err = daemon.mountVolumes(container)
184
-	defer container.UnmountVolumes(true)
184
+	defer container.UnmountVolumes(true, daemon.LogVolumeEvent)
185 185
 	if err != nil {
186 186
 		return err
187 187
 	}
... ...
@@ -283,7 +283,7 @@ func (daemon *Daemon) containerCopy(container *container.Container, resource str
283 283
 	defer func() {
284 284
 		if err != nil {
285 285
 			// unmount any volumes
286
-			container.UnmountVolumes(true)
286
+			container.UnmountVolumes(true, daemon.LogVolumeEvent)
287 287
 			// unmount the container's rootfs
288 288
 			daemon.Unmount(container)
289 289
 		}
... ...
@@ -320,7 +320,7 @@ func (daemon *Daemon) containerCopy(container *container.Container, resource str
320 320
 
321 321
 	reader := ioutils.NewReadCloserWrapper(archive, func() error {
322 322
 		err := archive.Close()
323
-		container.UnmountVolumes(true)
323
+		container.UnmountVolumes(true, daemon.LogVolumeEvent)
324 324
 		daemon.Unmount(container)
325 325
 		container.Unlock()
326 326
 		return err
... ...
@@ -699,6 +699,7 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName
699 699
 		return derr.ErrorCodeJoinInfo.WithArgs(err)
700 700
 	}
701 701
 
702
+	daemon.LogNetworkEventWithAttributes(n, "connect", map[string]string{"container": container.ID})
702 703
 	return nil
703 704
 }
704 705
 
... ...
@@ -719,6 +720,11 @@ func (daemon *Daemon) DisconnectFromNetwork(container *container.Container, n li
719 719
 	if err := container.ToDiskLocking(); err != nil {
720 720
 		return fmt.Errorf("Error saving container to disk: %v", err)
721 721
 	}
722
+
723
+	attributes := map[string]string{
724
+		"container": container.ID,
725
+	}
726
+	daemon.LogNetworkEventWithAttributes(n, "disconnect", attributes)
722 727
 	return nil
723 728
 }
724 729
 
... ...
@@ -844,14 +850,18 @@ func (daemon *Daemon) releaseNetwork(container *container.Container) {
844 844
 	}
845 845
 
846 846
 	sid := container.NetworkSettings.SandboxID
847
-	networks := container.NetworkSettings.Networks
848
-	for n := range networks {
849
-		networks[n] = &networktypes.EndpointSettings{}
847
+	settings := container.NetworkSettings.Networks
848
+	var networks []libnetwork.Network
849
+	for n := range settings {
850
+		if nw, err := daemon.FindNetwork(n); err == nil {
851
+			networks = append(networks, nw)
852
+		}
853
+		settings[n] = &networktypes.EndpointSettings{}
850 854
 	}
851 855
 
852
-	container.NetworkSettings = &network.Settings{Networks: networks}
856
+	container.NetworkSettings = &network.Settings{Networks: settings}
853 857
 
854
-	if sid == "" || len(networks) == 0 {
858
+	if sid == "" || len(settings) == 0 {
855 859
 		return
856 860
 	}
857 861
 
... ...
@@ -864,6 +874,13 @@ func (daemon *Daemon) releaseNetwork(container *container.Container) {
864 864
 	if err := sb.Delete(); err != nil {
865 865
 		logrus.Errorf("Error deleting sandbox id %s for container %s: %v", sid, container.ID, err)
866 866
 	}
867
+
868
+	attributes := map[string]string{
869
+		"container": container.ID,
870
+	}
871
+	for _, nw := range networks {
872
+		daemon.LogNetworkEventWithAttributes(nw, "disconnect", attributes)
873
+	}
867 874
 }
868 875
 
869 876
 func (daemon *Daemon) setupIpcDirs(c *container.Container) error {
... ...
@@ -169,5 +169,10 @@ func (daemon *Daemon) VolumeCreate(name, driverName string, opts map[string]stri
169 169
 	if (driverName != "" && v.DriverName() != driverName) || (driverName == "" && v.DriverName() != volume.DefaultDriverName) {
170 170
 		return nil, derr.ErrorVolumeNameTaken.WithArgs(name, v.DriverName())
171 171
 	}
172
+
173
+	if driverName == "" {
174
+		driverName = volume.DefaultDriverName
175
+	}
176
+	daemon.LogVolumeEvent(name, "create", map[string]string{"driver": driverName})
172 177
 	return volumeToAPIType(v), nil
173 178
 }
... ...
@@ -22,6 +22,7 @@ import (
22 22
 	"github.com/docker/docker/api"
23 23
 	"github.com/docker/docker/api/types"
24 24
 	containertypes "github.com/docker/docker/api/types/container"
25
+	eventtypes "github.com/docker/docker/api/types/events"
25 26
 	"github.com/docker/docker/api/types/filters"
26 27
 	registrytypes "github.com/docker/docker/api/types/registry"
27 28
 	"github.com/docker/docker/api/types/strslice"
... ...
@@ -47,7 +48,6 @@ import (
47 47
 	"github.com/docker/docker/pkg/fileutils"
48 48
 	"github.com/docker/docker/pkg/graphdb"
49 49
 	"github.com/docker/docker/pkg/idtools"
50
-	"github.com/docker/docker/pkg/jsonmessage"
51 50
 	"github.com/docker/docker/pkg/mount"
52 51
 	"github.com/docker/docker/pkg/namesgenerator"
53 52
 	"github.com/docker/docker/pkg/progress"
... ...
@@ -554,23 +554,9 @@ func (daemon *Daemon) GetByName(name string) (*container.Container, error) {
554 554
 	return e, nil
555 555
 }
556 556
 
557
-// getEventFilter returns a filters.Filter for a set of filters
558
-func (daemon *Daemon) getEventFilter(filter filters.Args) *events.Filter {
559
-	// incoming container filter can be name, id or partial id, convert to
560
-	// a full container id
561
-	for _, cn := range filter.Get("container") {
562
-		c, err := daemon.GetContainer(cn)
563
-		filter.Del("container", cn)
564
-		if err == nil {
565
-			filter.Add("container", c.ID)
566
-		}
567
-	}
568
-	return events.NewFilter(filter, daemon.GetLabels)
569
-}
570
-
571 557
 // SubscribeToEvents returns the currently record of events, a channel to stream new events from, and a function to cancel the stream of events.
572
-func (daemon *Daemon) SubscribeToEvents(since, sinceNano int64, filter filters.Args) ([]*jsonmessage.JSONMessage, chan interface{}) {
573
-	ef := daemon.getEventFilter(filter)
558
+func (daemon *Daemon) SubscribeToEvents(since, sinceNano int64, filter filters.Args) ([]eventtypes.Message, chan interface{}) {
559
+	ef := events.NewFilter(filter)
574 560
 	return daemon.EventsService.SubscribeTopic(since, sinceNano, ef)
575 561
 }
576 562
 
... ...
@@ -580,21 +566,6 @@ func (daemon *Daemon) UnsubscribeFromEvents(listener chan interface{}) {
580 580
 	daemon.EventsService.Evict(listener)
581 581
 }
582 582
 
583
-// GetLabels for a container or image id
584
-func (daemon *Daemon) GetLabels(id string) map[string]string {
585
-	// TODO: TestCase
586
-	container := daemon.containers.Get(id)
587
-	if container != nil {
588
-		return container.Config.Labels
589
-	}
590
-
591
-	img, err := daemon.GetImage(id)
592
-	if err == nil {
593
-		return img.ContainerConfig.Labels
594
-	}
595
-	return nil
596
-}
597
-
598 583
 // children returns all child containers of the container with the
599 584
 // given name. The containers are returned as a map from the container
600 585
 // name to a pointer to Container.
... ...
@@ -1032,7 +1003,8 @@ func (daemon *Daemon) TagImage(newTag reference.Named, imageName string) error {
1032 1032
 	if err := daemon.referenceStore.AddTag(newTag, imageID, true); err != nil {
1033 1033
 		return err
1034 1034
 	}
1035
-	daemon.EventsService.Log("tag", newTag.String(), "")
1035
+
1036
+	daemon.LogImageEvent(imageID.String(), newTag.String(), "tag")
1036 1037
 	return nil
1037 1038
 }
1038 1039
 
... ...
@@ -1068,15 +1040,15 @@ func (daemon *Daemon) PullImage(ref reference.Named, metaHeaders map[string][]st
1068 1068
 	}()
1069 1069
 
1070 1070
 	imagePullConfig := &distribution.ImagePullConfig{
1071
-		MetaHeaders:     metaHeaders,
1072
-		AuthConfig:      authConfig,
1073
-		ProgressOutput:  progress.ChanOutput(progressChan),
1074
-		RegistryService: daemon.RegistryService,
1075
-		EventsService:   daemon.EventsService,
1076
-		MetadataStore:   daemon.distributionMetadataStore,
1077
-		ImageStore:      daemon.imageStore,
1078
-		ReferenceStore:  daemon.referenceStore,
1079
-		DownloadManager: daemon.downloadManager,
1071
+		MetaHeaders:      metaHeaders,
1072
+		AuthConfig:       authConfig,
1073
+		ProgressOutput:   progress.ChanOutput(progressChan),
1074
+		RegistryService:  daemon.RegistryService,
1075
+		ImageEventLogger: daemon.LogImageEvent,
1076
+		MetadataStore:    daemon.distributionMetadataStore,
1077
+		ImageStore:       daemon.imageStore,
1078
+		ReferenceStore:   daemon.referenceStore,
1079
+		DownloadManager:  daemon.downloadManager,
1080 1080
 	}
1081 1081
 
1082 1082
 	err := distribution.Pull(ctx, ref, imagePullConfig)
... ...
@@ -1111,17 +1083,17 @@ func (daemon *Daemon) PushImage(ref reference.Named, metaHeaders map[string][]st
1111 1111
 	}()
1112 1112
 
1113 1113
 	imagePushConfig := &distribution.ImagePushConfig{
1114
-		MetaHeaders:     metaHeaders,
1115
-		AuthConfig:      authConfig,
1116
-		ProgressOutput:  progress.ChanOutput(progressChan),
1117
-		RegistryService: daemon.RegistryService,
1118
-		EventsService:   daemon.EventsService,
1119
-		MetadataStore:   daemon.distributionMetadataStore,
1120
-		LayerStore:      daemon.layerStore,
1121
-		ImageStore:      daemon.imageStore,
1122
-		ReferenceStore:  daemon.referenceStore,
1123
-		TrustKey:        daemon.trustKey,
1124
-		UploadManager:   daemon.uploadManager,
1114
+		MetaHeaders:      metaHeaders,
1115
+		AuthConfig:       authConfig,
1116
+		ProgressOutput:   progress.ChanOutput(progressChan),
1117
+		RegistryService:  daemon.RegistryService,
1118
+		ImageEventLogger: daemon.LogImageEvent,
1119
+		MetadataStore:    daemon.distributionMetadataStore,
1120
+		LayerStore:       daemon.layerStore,
1121
+		ImageStore:       daemon.imageStore,
1122
+		ReferenceStore:   daemon.referenceStore,
1123
+		TrustKey:         daemon.trustKey,
1124
+		UploadManager:    daemon.uploadManager,
1125 1125
 	}
1126 1126
 
1127 1127
 	err := distribution.Push(ctx, ref, imagePushConfig)
... ...
@@ -157,5 +157,6 @@ func (daemon *Daemon) VolumeRm(name string) error {
157 157
 		}
158 158
 		return derr.ErrorCodeRmVolume.WithArgs(name, err)
159 159
 	}
160
+	daemon.LogVolumeEvent(v.Name(), "destroy", map[string]string{"driver": v.DriverName()})
160 161
 	return nil
161 162
 }
... ...
@@ -1,14 +1,81 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
+	"strings"
5
+
6
+	"github.com/docker/docker/api/types/events"
4 7
 	"github.com/docker/docker/container"
8
+	"github.com/docker/libnetwork"
5 9
 )
6 10
 
7 11
 // LogContainerEvent generates an event related to a container.
8 12
 func (daemon *Daemon) LogContainerEvent(container *container.Container, action string) {
9
-	daemon.EventsService.Log(
10
-		action,
11
-		container.ID,
12
-		container.Config.Image,
13
-	)
13
+	attributes := copyAttributes(container.Config.Labels)
14
+	if container.Config.Image != "" {
15
+		attributes["image"] = container.Config.Image
16
+	}
17
+	attributes["name"] = strings.TrimLeft(container.Name, "/")
18
+
19
+	actor := events.Actor{
20
+		ID:         container.ID,
21
+		Attributes: attributes,
22
+	}
23
+	daemon.EventsService.Log(action, events.ContainerEventType, actor)
24
+}
25
+
26
+// LogImageEvent generates an event related to a container.
27
+func (daemon *Daemon) LogImageEvent(imageID, refName, action string) {
28
+	attributes := map[string]string{}
29
+	img, err := daemon.GetImage(imageID)
30
+	if err == nil && img.Config != nil {
31
+		// image has not been removed yet.
32
+		// it could be missing if the event is `delete`.
33
+		attributes = copyAttributes(img.Config.Labels)
34
+	}
35
+	if refName != "" {
36
+		attributes["name"] = refName
37
+	}
38
+	actor := events.Actor{
39
+		ID:         imageID,
40
+		Attributes: attributes,
41
+	}
42
+
43
+	daemon.EventsService.Log(action, events.ImageEventType, actor)
44
+}
45
+
46
+// LogVolumeEvent generates an event related to a volume.
47
+func (daemon *Daemon) LogVolumeEvent(volumeID, action string, attributes map[string]string) {
48
+	actor := events.Actor{
49
+		ID:         volumeID,
50
+		Attributes: attributes,
51
+	}
52
+	daemon.EventsService.Log(action, events.VolumeEventType, actor)
53
+}
54
+
55
+// LogNetworkEvent generates an event related to a network with only the default attributes.
56
+func (daemon *Daemon) LogNetworkEvent(nw libnetwork.Network, action string) {
57
+	daemon.LogNetworkEventWithAttributes(nw, action, map[string]string{})
58
+}
59
+
60
+// LogNetworkEventWithAttributes generates an event related to a network with specific given attributes.
61
+func (daemon *Daemon) LogNetworkEventWithAttributes(nw libnetwork.Network, action string, attributes map[string]string) {
62
+	attributes["name"] = nw.Name()
63
+	attributes["type"] = nw.Type()
64
+	actor := events.Actor{
65
+		ID:         nw.ID(),
66
+		Attributes: attributes,
67
+	}
68
+	daemon.EventsService.Log(action, events.NetworkEventType, actor)
69
+}
70
+
71
+// copyAttributes guarantees that labels are not mutated by event triggers.
72
+func copyAttributes(labels map[string]string) map[string]string {
73
+	attributes := map[string]string{}
74
+	if labels == nil {
75
+		return attributes
76
+	}
77
+	for k, v := range labels {
78
+		attributes[k] = v
79
+	}
80
+	return attributes
14 81
 }
... ...
@@ -4,7 +4,7 @@ import (
4 4
 	"sync"
5 5
 	"time"
6 6
 
7
-	"github.com/docker/docker/pkg/jsonmessage"
7
+	eventtypes "github.com/docker/docker/api/types/events"
8 8
 	"github.com/docker/docker/pkg/pubsub"
9 9
 )
10 10
 
... ...
@@ -13,17 +13,17 @@ const (
13 13
 	bufferSize  = 1024
14 14
 )
15 15
 
16
-// Events is pubsub channel for *jsonmessage.JSONMessage
16
+// Events is pubsub channel for events generated by the engine.
17 17
 type Events struct {
18 18
 	mu     sync.Mutex
19
-	events []*jsonmessage.JSONMessage
19
+	events []eventtypes.Message
20 20
 	pub    *pubsub.Publisher
21 21
 }
22 22
 
23 23
 // New returns new *Events instance
24 24
 func New() *Events {
25 25
 	return &Events{
26
-		events: make([]*jsonmessage.JSONMessage, 0, eventsLimit),
26
+		events: make([]eventtypes.Message, 0, eventsLimit),
27 27
 		pub:    pubsub.NewPublisher(100*time.Millisecond, bufferSize),
28 28
 	}
29 29
 }
... ...
@@ -32,9 +32,9 @@ func New() *Events {
32 32
 // last events, a channel in which you can expect new events (in form
33 33
 // of interface{}, so you need type assertion), and a function to call
34 34
 // to stop the stream of events.
35
-func (e *Events) Subscribe() ([]*jsonmessage.JSONMessage, chan interface{}, func()) {
35
+func (e *Events) Subscribe() ([]eventtypes.Message, chan interface{}, func()) {
36 36
 	e.mu.Lock()
37
-	current := make([]*jsonmessage.JSONMessage, len(e.events))
37
+	current := make([]eventtypes.Message, len(e.events))
38 38
 	copy(current, e.events)
39 39
 	l := e.pub.Subscribe()
40 40
 	e.mu.Unlock()
... ...
@@ -48,13 +48,13 @@ func (e *Events) Subscribe() ([]*jsonmessage.JSONMessage, chan interface{}, func
48 48
 // SubscribeTopic adds new listener to events, returns slice of 64 stored
49 49
 // last events, a channel in which you can expect new events (in form
50 50
 // of interface{}, so you need type assertion).
51
-func (e *Events) SubscribeTopic(since, sinceNano int64, ef *Filter) ([]*jsonmessage.JSONMessage, chan interface{}) {
51
+func (e *Events) SubscribeTopic(since, sinceNano int64, ef *Filter) ([]eventtypes.Message, chan interface{}) {
52 52
 	e.mu.Lock()
53 53
 	defer e.mu.Unlock()
54 54
 
55
-	var buffered []*jsonmessage.JSONMessage
55
+	var buffered []eventtypes.Message
56 56
 	topic := func(m interface{}) bool {
57
-		return ef.Include(m.(*jsonmessage.JSONMessage))
57
+		return ef.Include(m.(eventtypes.Message))
58 58
 	}
59 59
 
60 60
 	if since != -1 {
... ...
@@ -64,7 +64,7 @@ func (e *Events) SubscribeTopic(since, sinceNano int64, ef *Filter) ([]*jsonmess
64 64
 				break
65 65
 			}
66 66
 			if ef.filter.Len() == 0 || topic(ev) {
67
-				buffered = append([]*jsonmessage.JSONMessage{ev}, buffered...)
67
+				buffered = append([]eventtypes.Message{ev}, buffered...)
68 68
 			}
69 69
 		}
70 70
 	}
... ...
@@ -87,9 +87,27 @@ func (e *Events) Evict(l chan interface{}) {
87 87
 
88 88
 // Log broadcasts event to listeners. Each listener has 100 millisecond for
89 89
 // receiving event or it will be skipped.
90
-func (e *Events) Log(action, id, from string) {
90
+func (e *Events) Log(action, eventType string, actor eventtypes.Actor) {
91 91
 	now := time.Now().UTC()
92
-	jm := &jsonmessage.JSONMessage{Status: action, ID: id, From: from, Time: now.Unix(), TimeNano: now.UnixNano()}
92
+	jm := eventtypes.Message{
93
+		Action:   action,
94
+		Type:     eventType,
95
+		Actor:    actor,
96
+		Time:     now.Unix(),
97
+		TimeNano: now.UnixNano(),
98
+	}
99
+
100
+	// fill deprecated fields for container and images
101
+	switch eventType {
102
+	case eventtypes.ContainerEventType:
103
+		jm.ID = actor.ID
104
+		jm.Status = action
105
+		jm.From = actor.Attributes["image"]
106
+	case eventtypes.ImageEventType:
107
+		jm.ID = actor.ID
108
+		jm.Status = action
109
+	}
110
+
93 111
 	e.mu.Lock()
94 112
 	if len(e.events) == cap(e.events) {
95 113
 		// discard oldest event
... ...
@@ -5,7 +5,7 @@ import (
5 5
 	"testing"
6 6
 	"time"
7 7
 
8
-	"github.com/docker/docker/pkg/jsonmessage"
8
+	"github.com/docker/docker/api/types/events"
9 9
 )
10 10
 
11 11
 func TestEventsLog(t *testing.T) {
... ...
@@ -18,10 +18,14 @@ func TestEventsLog(t *testing.T) {
18 18
 	if count != 2 {
19 19
 		t.Fatalf("Must be 2 subscribers, got %d", count)
20 20
 	}
21
-	e.Log("test", "cont", "image")
21
+	actor := events.Actor{
22
+		ID:         "cont",
23
+		Attributes: map[string]string{"image": "image"},
24
+	}
25
+	e.Log("test", events.ContainerEventType, actor)
22 26
 	select {
23 27
 	case msg := <-l1:
24
-		jmsg, ok := msg.(*jsonmessage.JSONMessage)
28
+		jmsg, ok := msg.(events.Message)
25 29
 		if !ok {
26 30
 			t.Fatalf("Unexpected type %T", msg)
27 31
 		}
... ...
@@ -42,7 +46,7 @@ func TestEventsLog(t *testing.T) {
42 42
 	}
43 43
 	select {
44 44
 	case msg := <-l2:
45
-		jmsg, ok := msg.(*jsonmessage.JSONMessage)
45
+		jmsg, ok := msg.(events.Message)
46 46
 		if !ok {
47 47
 			t.Fatalf("Unexpected type %T", msg)
48 48
 		}
... ...
@@ -70,7 +74,10 @@ func TestEventsLogTimeout(t *testing.T) {
70 70
 
71 71
 	c := make(chan struct{})
72 72
 	go func() {
73
-		e.Log("test", "cont", "image")
73
+		actor := events.Actor{
74
+			ID: "image",
75
+		}
76
+		e.Log("test", events.ImageEventType, actor)
74 77
 		close(c)
75 78
 	}()
76 79
 
... ...
@@ -88,7 +95,12 @@ func TestLogEvents(t *testing.T) {
88 88
 		action := fmt.Sprintf("action_%d", i)
89 89
 		id := fmt.Sprintf("cont_%d", i)
90 90
 		from := fmt.Sprintf("image_%d", i)
91
-		e.Log(action, id, from)
91
+
92
+		actor := events.Actor{
93
+			ID:         id,
94
+			Attributes: map[string]string{"image": from},
95
+		}
96
+		e.Log(action, events.ContainerEventType, actor)
92 97
 	}
93 98
 	time.Sleep(50 * time.Millisecond)
94 99
 	current, l, _ := e.Subscribe()
... ...
@@ -97,16 +109,21 @@ func TestLogEvents(t *testing.T) {
97 97
 		action := fmt.Sprintf("action_%d", num)
98 98
 		id := fmt.Sprintf("cont_%d", num)
99 99
 		from := fmt.Sprintf("image_%d", num)
100
-		e.Log(action, id, from)
100
+
101
+		actor := events.Actor{
102
+			ID:         id,
103
+			Attributes: map[string]string{"image": from},
104
+		}
105
+		e.Log(action, events.ContainerEventType, actor)
101 106
 	}
102 107
 	if len(e.events) != eventsLimit {
103 108
 		t.Fatalf("Must be %d events, got %d", eventsLimit, len(e.events))
104 109
 	}
105 110
 
106
-	var msgs []*jsonmessage.JSONMessage
111
+	var msgs []events.Message
107 112
 	for len(msgs) < 10 {
108 113
 		m := <-l
109
-		jm, ok := (m).(*jsonmessage.JSONMessage)
114
+		jm, ok := (m).(events.Message)
110 115
 		if !ok {
111 116
 			t.Fatalf("Unexpected type %T", m)
112 117
 		}
... ...
@@ -1,46 +1,76 @@
1 1
 package events
2 2
 
3 3
 import (
4
+	"github.com/docker/docker/api/types/events"
4 5
 	"github.com/docker/docker/api/types/filters"
5
-	"github.com/docker/docker/pkg/jsonmessage"
6 6
 	"github.com/docker/docker/reference"
7 7
 )
8 8
 
9 9
 // Filter can filter out docker events from a stream
10 10
 type Filter struct {
11
-	filter    filters.Args
12
-	getLabels func(id string) map[string]string
11
+	filter filters.Args
13 12
 }
14 13
 
15 14
 // NewFilter creates a new Filter
16
-func NewFilter(filter filters.Args, getLabels func(id string) map[string]string) *Filter {
17
-	return &Filter{filter: filter, getLabels: getLabels}
15
+func NewFilter(filter filters.Args) *Filter {
16
+	return &Filter{filter: filter}
18 17
 }
19 18
 
20 19
 // Include returns true when the event ev is included by the filters
21
-func (ef *Filter) Include(ev *jsonmessage.JSONMessage) bool {
22
-	return ef.filter.ExactMatch("event", ev.Status) &&
23
-		ef.filter.ExactMatch("container", ev.ID) &&
24
-		ef.isImageIncluded(ev.ID, ev.From) &&
25
-		ef.isLabelFieldIncluded(ev.ID)
20
+func (ef *Filter) Include(ev events.Message) bool {
21
+	return ef.filter.ExactMatch("event", ev.Action) &&
22
+		ef.filter.ExactMatch("type", ev.Type) &&
23
+		ef.matchContainer(ev) &&
24
+		ef.matchVolume(ev) &&
25
+		ef.matchNetwork(ev) &&
26
+		ef.matchImage(ev) &&
27
+		ef.matchLabels(ev.Actor.Attributes)
26 28
 }
27 29
 
28
-func (ef *Filter) isLabelFieldIncluded(id string) bool {
30
+func (ef *Filter) matchLabels(attributes map[string]string) bool {
29 31
 	if !ef.filter.Include("label") {
30 32
 		return true
31 33
 	}
32
-	return ef.filter.MatchKVList("label", ef.getLabels(id))
34
+	return ef.filter.MatchKVList("label", attributes)
33 35
 }
34 36
 
35
-// The image filter will be matched against both event.ID (for image events)
36
-// and event.From (for container events), so that any container that was created
37
+func (ef *Filter) matchContainer(ev events.Message) bool {
38
+	return ef.fuzzyMatchName(ev, events.ContainerEventType)
39
+}
40
+
41
+func (ef *Filter) matchVolume(ev events.Message) bool {
42
+	return ef.fuzzyMatchName(ev, events.VolumeEventType)
43
+}
44
+
45
+func (ef *Filter) matchNetwork(ev events.Message) bool {
46
+	return ef.fuzzyMatchName(ev, events.NetworkEventType)
47
+}
48
+
49
+func (ef *Filter) fuzzyMatchName(ev events.Message, eventType string) bool {
50
+	return ef.filter.FuzzyMatch(eventType, ev.Actor.ID) ||
51
+		ef.filter.FuzzyMatch(eventType, ev.Actor.Attributes["name"])
52
+}
53
+
54
+// matchImage matches against both event.Actor.ID (for image events)
55
+// and event.Actor.Attributes["image"] (for container events), so that any container that was created
37 56
 // from an image will be included in the image events. Also compare both
38 57
 // against the stripped repo name without any tags.
39
-func (ef *Filter) isImageIncluded(eventID string, eventFrom string) bool {
40
-	return ef.filter.ExactMatch("image", eventID) ||
41
-		ef.filter.ExactMatch("image", eventFrom) ||
42
-		ef.filter.ExactMatch("image", stripTag(eventID)) ||
43
-		ef.filter.ExactMatch("image", stripTag(eventFrom))
58
+func (ef *Filter) matchImage(ev events.Message) bool {
59
+	id := ev.Actor.ID
60
+	nameAttr := "image"
61
+	var imageName string
62
+
63
+	if ev.Type == events.ImageEventType {
64
+		nameAttr = "name"
65
+	}
66
+
67
+	if n, ok := ev.Actor.Attributes[nameAttr]; ok {
68
+		imageName = n
69
+	}
70
+	return ef.filter.ExactMatch("image", id) ||
71
+		ef.filter.ExactMatch("image", imageName) ||
72
+		ef.filter.ExactMatch("image", stripTag(id)) ||
73
+		ef.filter.ExactMatch("image", stripTag(imageName))
44 74
 }
45 75
 
46 76
 func stripTag(image string) string {
47 77
new file mode 100644
... ...
@@ -0,0 +1,36 @@
0
+package daemon
1
+
2
+import (
3
+	"testing"
4
+
5
+	containertypes "github.com/docker/docker/api/types/container"
6
+	"github.com/docker/docker/container"
7
+	"github.com/docker/docker/daemon/events"
8
+)
9
+
10
+func TestLogContainerCopyLabels(t *testing.T) {
11
+	e := events.New()
12
+	_, l, _ := e.Subscribe()
13
+	defer e.Evict(l)
14
+
15
+	container := &container.Container{
16
+		CommonContainer: container.CommonContainer{
17
+			ID:   "container_id",
18
+			Name: "container_name",
19
+			Config: &containertypes.Config{
20
+				Labels: map[string]string{
21
+					"node": "1",
22
+					"os":   "alpine",
23
+				},
24
+			},
25
+		},
26
+	}
27
+	daemon := &Daemon{
28
+		EventsService: e,
29
+	}
30
+	daemon.LogContainerEvent(container, "create")
31
+
32
+	if _, mutated := container.Config.Labels["image"]; mutated {
33
+		t.Fatalf("Expected to not mutate the container labels, got %q", container.Config.Labels)
34
+	}
35
+}
... ...
@@ -87,7 +87,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
87 87
 
88 88
 		untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
89 89
 
90
-		daemon.EventsService.Log("untag", imgID.String(), "")
90
+		daemon.LogImageEvent(imgID.String(), imgID.String(), "untag")
91 91
 		records = append(records, untaggedRecord)
92 92
 
93 93
 		// If has remaining references then untag finishes the remove
... ...
@@ -109,7 +109,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
109 109
 
110 110
 			untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
111 111
 
112
-			daemon.EventsService.Log("untag", imgID.String(), "")
112
+			daemon.LogImageEvent(imgID.String(), imgID.String(), "untag")
113 113
 			records = append(records, untaggedRecord)
114 114
 		}
115 115
 	}
... ...
@@ -174,7 +174,7 @@ func (daemon *Daemon) removeAllReferencesToImageID(imgID image.ID, records *[]ty
174 174
 
175 175
 		untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
176 176
 
177
-		daemon.EventsService.Log("untag", imgID.String(), "")
177
+		daemon.LogImageEvent(imgID.String(), imgID.String(), "untag")
178 178
 		*records = append(*records, untaggedRecord)
179 179
 	}
180 180
 
... ...
@@ -243,7 +243,7 @@ func (daemon *Daemon) imageDeleteHelper(imgID image.ID, records *[]types.ImageDe
243 243
 		return err
244 244
 	}
245 245
 
246
-	daemon.EventsService.Log("delete", imgID.String(), "")
246
+	daemon.LogImageEvent(imgID.String(), imgID.String(), "delete")
247 247
 	*records = append(*records, types.ImageDelete{Deleted: imgID.String()})
248 248
 	for _, removedLayer := range removedLayers {
249 249
 		*records = append(*records, types.ImageDelete{Deleted: removedLayer.ChainID.String()})
... ...
@@ -97,7 +97,7 @@ func (daemon *Daemon) ImportImage(src string, newRef reference.Named, msg string
97 97
 		}
98 98
 	}
99 99
 
100
-	daemon.EventsService.Log("import", id.String(), "")
100
+	daemon.LogImageEvent(id.String(), id.String(), "import")
101 101
 	outStream.Write(sf.FormatStatus("", id.String()))
102 102
 	return nil
103 103
 }
... ...
@@ -7,6 +7,8 @@ import (
7 7
 	"strings"
8 8
 
9 9
 	"github.com/docker/docker/api/types/network"
10
+	derr "github.com/docker/docker/errors"
11
+	"github.com/docker/docker/runconfig"
10 12
 	"github.com/docker/libnetwork"
11 13
 )
12 14
 
... ...
@@ -114,7 +116,13 @@ func (daemon *Daemon) CreateNetwork(name, driver string, ipam network.IPAM, opti
114 114
 
115 115
 	nwOptions = append(nwOptions, libnetwork.NetworkOptionIpam(ipam.Driver, "", v4Conf, v6Conf))
116 116
 	nwOptions = append(nwOptions, libnetwork.NetworkOptionDriverOpts(options))
117
-	return c.NewNetwork(driver, name, nwOptions...)
117
+	n, err := c.NewNetwork(driver, name, nwOptions...)
118
+	if err != nil {
119
+		return nil, err
120
+	}
121
+
122
+	daemon.LogNetworkEvent(n, "create")
123
+	return n, nil
118 124
 }
119 125
 
120 126
 func getIpamConfig(data []network.IPAMConfig) ([]*libnetwork.IpamConf, []*libnetwork.IpamConf, error) {
... ...
@@ -178,3 +186,21 @@ func (daemon *Daemon) GetNetworkDriverList() map[string]bool {
178 178
 
179 179
 	return pluginList
180 180
 }
181
+
182
+// DeleteNetwork destroys a network unless it's one of docker's predefined networks.
183
+func (daemon *Daemon) DeleteNetwork(networkID string) error {
184
+	nw, err := daemon.FindNetwork(networkID)
185
+	if err != nil {
186
+		return err
187
+	}
188
+
189
+	if runconfig.IsPreDefinedNetwork(nw.Name()) {
190
+		return derr.ErrorCodeCantDeletePredefinedNetwork.WithArgs(nw.Name())
191
+	}
192
+
193
+	if err := nw.Delete(); err != nil {
194
+		return err
195
+	}
196
+	daemon.LogNetworkEvent(nw, "destroy")
197
+	return nil
198
+}
... ...
@@ -156,7 +156,7 @@ func (daemon *Daemon) Cleanup(container *container.Container) {
156 156
 		daemon.unregisterExecCommand(container, eConfig)
157 157
 	}
158 158
 
159
-	if err := container.UnmountVolumes(false); err != nil {
159
+	if err := container.UnmountVolumes(false, daemon.LogVolumeEvent); err != nil {
160 160
 		logrus.Warnf("%s cleanup: Failed to umount volumes: %v", container.ID, err)
161 161
 	}
162 162
 }
... ...
@@ -54,5 +54,7 @@ func (daemon *Daemon) update(name string, hostConfig *container.HostConfig) erro
54 54
 		}
55 55
 	}
56 56
 
57
+	daemon.LogContainerEvent(container, "update")
58
+
57 59
 	return nil
58 60
 }
... ...
@@ -5,6 +5,7 @@ package daemon
5 5
 import (
6 6
 	"os"
7 7
 	"sort"
8
+	"strconv"
8 9
 
9 10
 	"github.com/docker/docker/container"
10 11
 	"github.com/docker/docker/daemon/execdriver"
... ...
@@ -30,6 +31,16 @@ func (daemon *Daemon) setupMounts(container *container.Container) ([]execdriver.
30 30
 				Writable:    m.RW,
31 31
 				Propagation: m.Propagation,
32 32
 			}
33
+			if m.Volume != nil {
34
+				attributes := map[string]string{
35
+					"driver":      m.Volume.DriverName(),
36
+					"container":   container.ID,
37
+					"destination": m.Destination,
38
+					"read/write":  strconv.FormatBool(m.RW),
39
+					"propagation": m.Propagation,
40
+				}
41
+				daemon.LogVolumeEvent(m.Volume.Name(), "mount", attributes)
42
+			}
33 43
 			mounts = append(mounts, mnt)
34 44
 		}
35 45
 	}
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"github.com/Sirupsen/logrus"
9 9
 	"github.com/docker/docker/api"
10 10
 	"github.com/docker/docker/api/types"
11
-	"github.com/docker/docker/daemon/events"
12 11
 	"github.com/docker/docker/distribution/metadata"
13 12
 	"github.com/docker/docker/distribution/xfer"
14 13
 	"github.com/docker/docker/image"
... ...
@@ -32,8 +31,8 @@ type ImagePullConfig struct {
32 32
 	// RegistryService is the registry service to use for TLS configuration
33 33
 	// and endpoint lookup.
34 34
 	RegistryService *registry.Service
35
-	// EventsService is the events service to use for logging.
36
-	EventsService *events.Events
35
+	// ImageEventLogger notifies events for a given image
36
+	ImageEventLogger func(id, name, action string)
37 37
 	// MetadataStore is the storage backend for distribution-specific
38 38
 	// metadata.
39 39
 	MetadataStore metadata.Store
... ...
@@ -161,7 +160,7 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
161 161
 			}
162 162
 		}
163 163
 
164
-		imagePullConfig.EventsService.Log("pull", ref.String(), "")
164
+		imagePullConfig.ImageEventLogger(ref.String(), repoInfo.Name(), "pull")
165 165
 		return nil
166 166
 	}
167 167
 
... ...
@@ -9,7 +9,6 @@ import (
9 9
 	"github.com/Sirupsen/logrus"
10 10
 	"github.com/docker/distribution/digest"
11 11
 	"github.com/docker/docker/api/types"
12
-	"github.com/docker/docker/daemon/events"
13 12
 	"github.com/docker/docker/distribution/metadata"
14 13
 	"github.com/docker/docker/distribution/xfer"
15 14
 	"github.com/docker/docker/image"
... ...
@@ -35,8 +34,8 @@ type ImagePushConfig struct {
35 35
 	// RegistryService is the registry service to use for TLS configuration
36 36
 	// and endpoint lookup.
37 37
 	RegistryService *registry.Service
38
-	// EventsService is the events service to use for logging.
39
-	EventsService *events.Events
38
+	// ImageEventLogger notifies events for a given image
39
+	ImageEventLogger func(id, name, action string)
40 40
 	// MetadataStore is the storage backend for distribution-specific
41 41
 	// metadata.
42 42
 	MetadataStore metadata.Store
... ...
@@ -156,7 +155,7 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
156 156
 			return err
157 157
 		}
158 158
 
159
-		imagePushConfig.EventsService.Log("push", repoInfo.Name(), "")
159
+		imagePushConfig.ImageEventLogger(ref.String(), repoInfo.Name(), "push")
160 160
 		return nil
161 161
 	}
162 162
 
... ...
@@ -12,6 +12,12 @@ parent = "mn_use_docker"
12 12
 
13 13
 The following list of features are deprecated.
14 14
 
15
+### Ambiguous event fields in API
16
+**Deprecated In Release: v1.10**
17
+
18
+The fields `ID`, `Status` and `From` in the events API have been deprecated in favor of a more rich structure.
19
+See the events API documentation for the new format.
20
+
15 21
 ### `-f` flag on `docker tag`
16 22
 **Deprecated In Release: v1.10**
17 23
 
... ...
@@ -2274,17 +2274,24 @@ Status Codes:
2274 2274
 
2275 2275
 `GET /events`
2276 2276
 
2277
-Get container events from docker, either in real time via streaming, or via
2278
-polling (using since).
2277
+Get container events from docker, either in real time via streaming, or via polling (using since).
2279 2278
 
2280 2279
 Docker containers report the following events:
2281 2280
 
2282
-    attach, commit, copy, create, destroy, die, exec_create, exec_start, export, kill, oom, pause, rename, resize, restart, start, stop, top, unpause
2281
+    attach, commit, copy, create, destroy, die, exec_create, exec_start, export, kill, oom, pause, rename, resize, restart, start, stop, top, unpause, update
2283 2282
 
2284
-and Docker images report:
2283
+Docker images report the following events:
2285 2284
 
2286 2285
     delete, import, pull, push, tag, untag
2287 2286
 
2287
+Docker volumes report the following events:
2288
+
2289
+    create, mount, unmount, destroy
2290
+
2291
+Docker networks report the following events:
2292
+
2293
+    create, connect, disconnect, destroy
2294
+
2288 2295
 **Example request**:
2289 2296
 
2290 2297
     GET /events?since=1374067924
... ...
@@ -2294,10 +2301,48 @@ and Docker images report:
2294 2294
     HTTP/1.1 200 OK
2295 2295
     Content-Type: application/json
2296 2296
 
2297
-    {"status":"pull","id":"busybox:latest","time":1442421700,"timeNano":1442421700598988358}
2298
-    {"status":"create","id":"5745704abe9caa5","from":"busybox","time":1442421716,"timeNano":1442421716853979870}
2299
-    {"status":"attach","id":"5745704abe9caa5","from":"busybox","time":1442421716,"timeNano":1442421716894759198}
2300
-    {"status":"start","id":"5745704abe9caa5","from":"busybox","time":1442421716,"timeNano":1442421716983607193}
2297
+    [
2298
+	    {
2299
+		"action": "pull",
2300
+		"type": "image", 
2301
+		"actor": {
2302
+			"id": "busybox:latest",
2303
+			"attributes": {}
2304
+		}
2305
+		"time": 1442421700,
2306
+		"timeNano": 1442421700598988358
2307
+	    },
2308
+            {
2309
+		"action": "create",
2310
+		"type": "container",
2311
+		"actor": {
2312
+			"id": "5745704abe9caa5",
2313
+			"attributes": {"image": "busybox"}
2314
+		}
2315
+		"time": 1442421716,
2316
+		"timeNano": 1442421716853979870
2317
+	    },
2318
+            {
2319
+		"action": "attach",
2320
+		"type": "container",
2321
+		"actor": {
2322
+			"id": "5745704abe9caa5",
2323
+			"attributes": {"image": "busybox"}
2324
+		}
2325
+		"time": 1442421716,
2326
+		"timeNano": 1442421716894759198
2327
+	    },
2328
+            {
2329
+		"action": "start",
2330
+		"type": "container",
2331
+		"actor": {
2332
+			"id": "5745704abe9caa5",
2333
+			"attributes": {"image": "busybox"}
2334
+		}
2335
+		"time": 1442421716,
2336
+		"timeNano": 1442421716983607193
2337
+	    }
2338
+    ]
2301 2339
 
2302 2340
 Query Parameters:
2303 2341
 
... ...
@@ -2308,6 +2353,9 @@ Query Parameters:
2308 2308
   -   `event=<string>`; -- event to filter
2309 2309
   -   `image=<string>`; -- image to filter
2310 2310
   -   `label=<string>`; -- image and container label to filter
2311
+  -   `type=<string>`; -- either `container` or `image` or `volume` or `network`
2312
+  -   `volume=<string>`; -- volume to filter
2313
+  -   `network=<string>`; -- network to filter
2311 2314
 
2312 2315
 Status Codes:
2313 2316
 
... ...
@@ -19,14 +19,22 @@ parent = "smn_cli"
19 19
       --since=""         Show all events created since timestamp
20 20
       --until=""         Stream events until this timestamp
21 21
 
22
-Docker containers will report the following events:
22
+Docker containers report the following events:
23 23
 
24
-    attach, commit, copy, create, destroy, die, exec_create, exec_start, export, kill, oom, pause, rename, resize, restart, start, stop, top, unpause
24
+    attach, commit, copy, create, destroy, die, exec_create, exec_start, export, kill, oom, pause, rename, resize, restart, start, stop, top, unpause, update
25 25
 
26
-and Docker images will report:
26
+Docker images report the following events:
27 27
 
28 28
     delete, import, pull, push, tag, untag
29 29
 
30
+Docker volumes report the following events:
31
+
32
+    create, mount, unmount, destroy
33
+
34
+Docker networks report the following events:
35
+
36
+    create, connect, disconnect, destroy
37
+
30 38
 The `--since` and `--until` parameters can be Unix timestamps, date formatted
31 39
 timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed
32 40
 relative to the client machine’s time. If you do not provide the --since option,
... ...
@@ -57,9 +65,12 @@ container container 588a23dac085 *AND* the event type is *start*
57 57
 The currently supported filters are:
58 58
 
59 59
 * container (`container=<name or id>`)
60
-* event (`event=<event type>`)
60
+* event (`event=<event action>`)
61 61
 * image (`image=<tag or id>`)
62 62
 * label (`label=<key>` or `label=<key>=<value>`)
63
+* type (`type=<container or image or volume or network>`)
64
+* volume (`volume=<name or id>`)
65
+* network (`network=<name or id>`)
63 66
 
64 67
 ## Examples
65 68
 
... ...
@@ -77,68 +88,78 @@ You'll need two shells for this example.
77 77
 
78 78
 **Shell 1: (Again .. now showing events):**
79 79
 
80
-    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) start
81
-    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
82
-    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
83
-    2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
84
-    2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
80
+    2015-05-12T11:51:30.999999999Z07:00 container start 4386fb97867d (image=ubuntu-1:14.04)
81
+    2015-05-12T11:51:30.999999999Z07:00 container die 4386fb97867d (image=ubuntu-1:14.04)
82
+    2015-05-12T15:52:12.999999999Z07:00 container stop 4386fb97867d (image=ubuntu-1:14.04)
83
+    2015-05-12T15:53:45.999999999Z07:00 container die 7805c1d35632 (image=redis:2.8)
84
+    2015-05-12T15:54:03.999999999Z07:00 container stop 7805c1d35632 (image=redis:2.8)
85 85
 
86 86
 **Show events in the past from a specified time:**
87 87
 
88 88
     $ docker events --since 1378216169
89
-    2014-03-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
90
-    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
91
-    2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
92
-    2014-03-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
89
+    2015-05-12T11:51:30.999999999Z07:00 container die 4386fb97867d (image=ubuntu-1:14.04)
90
+    2015-05-12T15:52:12.999999999Z07:00 container stop 4386fb97867d (image=ubuntu-1:14.04)
91
+    2015-05-12T15:53:45.999999999Z07:00 container die 7805c1d35632 (image=redis:2.8)
92
+    2015-05-12T15:54:03.999999999Z07:00 container stop 7805c1d35632 (image=redis:2.8)
93 93
 
94 94
     $ docker events --since '2013-09-03'
95
-    2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) start
96
-    2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
97
-    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
98
-    2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
99
-    2014-09-03T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
95
+    2015-05-12T11:51:30.999999999Z07:00 container start 4386fb97867d (image=ubuntu-1:14.04)
96
+    2015-05-12T11:51:30.999999999Z07:00 container die 4386fb97867d (image=ubuntu-1:14.04)
97
+    2015-05-12T15:52:12.999999999Z07:00 container stop 4386fb97867d (image=ubuntu-1:14.04)
98
+    2015-05-12T15:53:45.999999999Z07:00 container die 7805c1d35632 (image=redis:2.8)
99
+    2015-05-12T15:54:03.999999999Z07:00 container stop 7805c1d35632 (image=redis:2.8)
100 100
 
101 101
     $ docker events --since '2013-09-03T15:49:29'
102
-    2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
103
-    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
104
-    2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
105
-    2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
102
+    2015-05-12T11:51:30.999999999Z07:00 container die 4386fb97867d (image=ubuntu-1:14.04)
103
+    2015-05-12T15:52:12.999999999Z07:00 container stop 4386fb97867d (image=ubuntu-1:14.04)
104
+    2015-05-12T15:53:45.999999999Z07:00 container die 7805c1d35632 (image=redis:2.8)
105
+    2015-05-12T15:54:03.999999999Z07:00 container stop 7805c1d35632 (image=redis:2.8)
106 106
 
107 107
 This example outputs all events that were generated in the last 3 minutes,
108 108
 relative to the current time on the client machine:
109 109
 
110 110
     $ docker events --since '3m'
111
-    2015-05-12T11:51:30.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
112
-    2015-05-12T15:52:12.999999999Z07:00 4 4386fb97867d: (from ubuntu-1:14.04) stop
113
-    2015-05-12T15:53:45.999999999Z07:00  7805c1d35632: (from redis:2.8) die
114
-    2015-05-12T15:54:03.999999999Z07:00  7805c1d35632: (from redis:2.8) stop
111
+    2015-05-12T11:51:30.999999999Z07:00 container die 4386fb97867d (image=ubuntu-1:14.04)
112
+    2015-05-12T15:52:12.999999999Z07:00 container stop 4386fb97867d (image=ubuntu-1:14.04)
113
+    2015-05-12T15:53:45.999999999Z07:00 container die 7805c1d35632 (image=redis:2.8)
114
+    2015-05-12T15:54:03.999999999Z07:00 container stop 7805c1d35632 (image=redis:2.8)
115 115
 
116 116
 **Filter events:**
117 117
 
118 118
     $ docker events --filter 'event=stop'
119
-    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
120
-    2014-09-03T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
119
+    2014-05-10T17:42:14.999999999Z07:00 container stop 4386fb97867d (image=ubuntu-1:14.04)
120
+    2014-09-03T17:42:14.999999999Z07:00 container stop 7805c1d35632 (image=redis:2.8)
121 121
 
122 122
     $ docker events --filter 'image=ubuntu-1:14.04'
123
-    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) start
124
-    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
125
-    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
123
+    2014-05-10T17:42:14.999999999Z07:00 container start 4386fb97867d (image=ubuntu-1:14.04)
124
+    2014-05-10T17:42:14.999999999Z07:00 container die 4386fb97867d (image=ubuntu-1:14.04)
125
+    2014-05-10T17:42:14.999999999Z07:00 container stop 4386fb97867d (image=ubuntu-1:14.04)
126 126
 
127 127
     $ docker events --filter 'container=7805c1d35632'
128
-    2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
129
-    2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
128
+    2014-05-10T17:42:14.999999999Z07:00 container die 7805c1d35632 (image=redis:2.8)
129
+    2014-09-03T15:49:29.999999999Z07:00 container stop 7805c1d35632 (image= redis:2.8)
130 130
 
131 131
     $ docker events --filter 'container=7805c1d35632' --filter 'container=4386fb97867d'
132
-    2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
133
-    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
134
-    2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
135
-    2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
132
+    2014-09-03T15:49:29.999999999Z07:00 container die 4386fb97867d (image=ubuntu-1:14.04)
133
+    2014-05-10T17:42:14.999999999Z07:00 container stop 4386fb97867d (image=ubuntu-1:14.04)
134
+    2014-05-10T17:42:14.999999999Z07:00 container die 7805c1d35632 (image=redis:2.8)
135
+    2014-09-03T15:49:29.999999999Z07:00 container stop 7805c1d35632 (image=redis:2.8)
136 136
 
137 137
     $ docker events --filter 'container=7805c1d35632' --filter 'event=stop'
138
-    2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
138
+    2014-09-03T15:49:29.999999999Z07:00 container stop 7805c1d35632 (image=redis:2.8)
139 139
 
140 140
     $ docker events --filter 'container=container_1' --filter 'container=container_2'
141
-    2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
142
-    2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
143
-    2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
144
-    2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
141
+    2014-09-03T15:49:29.999999999Z07:00 container die 4386fb97867d (image=ubuntu-1:14.04)
142
+    2014-05-10T17:42:14.999999999Z07:00 container stop 4386fb97867d (image=ubuntu-1:14.04)
143
+    2014-05-10T17:42:14.999999999Z07:00 container die 7805c1d35632 (imager=redis:2.8)
144
+    2014-09-03T15:49:29.999999999Z07:00 container stop 7805c1d35632 (image=redis:2.8)
145
+
146
+    $ docker events --filter 'type=volume'
147
+    2015-12-23T21:05:28.136212689Z volume create test-event-volume-local (driver=local)
148
+    2015-12-23T21:05:28.383462717Z volume mount test-event-volume-local (read/write=true, container=562fe10671e9273da25eed36cdce26159085ac7ee6707105fd534866340a5025, destination=/foo, driver=local, propagation=rprivate)
149
+    2015-12-23T21:05:28.650314265Z volume unmount test-event-volume-local (container=562fe10671e9273da25eed36cdce26159085ac7ee6707105fd534866340a5025, driver=local)
150
+    2015-12-23T21:05:28.716218405Z volume destroy test-event-volume-local (driver=local)
151
+
152
+    $ docker events --filter 'type=network'
153
+    2015-12-23T21:38:24.705709133Z network create 8b111217944ba0ba844a65b13efcd57dc494932ee2527577758f939315ba2c5b (name=test-event-network-local, type=bridge)
154
+    2015-12-23T21:38:25.119625123Z network connect 8b111217944ba0ba844a65b13efcd57dc494932ee2527577758f939315ba2c5b (name=test-event-network-local, container=b4be644031a3d90b400f88ab3d4bdf4dc23adb250e696b6328b85441abe2c54e, type=bridge)
... ...
@@ -939,4 +939,13 @@ var (
939 939
 		Description:    "There was an error while trying to start a container",
940 940
 		HTTPStatusCode: http.StatusInternalServerError,
941 941
 	})
942
+
943
+	// ErrorCodeCantDeletePredefinedNetwork is generated when one of the predefined networks
944
+	// is attempted to be deleted.
945
+	ErrorCodeCantDeletePredefinedNetwork = errcode.Register(errGroup, errcode.ErrorDescriptor{
946
+		Value:          "CANT_DELETE_PREDEFINED_NETWORK",
947
+		Message:        "%s is a pre-defined network and cannot be removed",
948
+		Description:    "Engine's predefined networks cannot be deleted",
949
+		HTTPStatusCode: http.StatusForbidden,
950
+	})
942 951
 )
... ...
@@ -1,10 +1,16 @@
1 1
 package main
2 2
 
3 3
 import (
4
+	"encoding/json"
5
+	"io"
4 6
 	"net/http"
7
+	"net/url"
8
+	"strconv"
9
+	"strings"
5 10
 	"time"
6 11
 
7 12
 	"github.com/docker/docker/pkg/integration/checker"
13
+	"github.com/docker/docker/pkg/jsonmessage"
8 14
 	"github.com/go-check/check"
9 15
 )
10 16
 
... ...
@@ -28,3 +34,40 @@ func (s *DockerSuite) TestEventsApiEmptyOutput(c *check.C) {
28 28
 		c.Fatal("timeout waiting for events api to respond, should have responded immediately")
29 29
 	}
30 30
 }
31
+
32
+func (s *DockerSuite) TestEventsApiBackwardsCompatible(c *check.C) {
33
+	since := daemonTime(c).Unix()
34
+	ts := strconv.FormatInt(since, 10)
35
+
36
+	out, _ := dockerCmd(c, "run", "--name=foo", "-d", "busybox", "top")
37
+	containerID := strings.TrimSpace(out)
38
+	c.Assert(waitRun(containerID), checker.IsNil)
39
+
40
+	q := url.Values{}
41
+	q.Set("since", ts)
42
+
43
+	_, body, err := sockRequestRaw("GET", "/events?"+q.Encode(), nil, "")
44
+	c.Assert(err, checker.IsNil)
45
+	defer body.Close()
46
+
47
+	dec := json.NewDecoder(body)
48
+	var containerCreateEvent *jsonmessage.JSONMessage
49
+	for {
50
+		var event jsonmessage.JSONMessage
51
+		if err := dec.Decode(&event); err != nil {
52
+			if err == io.EOF {
53
+				break
54
+			}
55
+			c.Fatal(err)
56
+		}
57
+		if event.Status == "create" && event.ID == containerID {
58
+			containerCreateEvent = &event
59
+			break
60
+		}
61
+	}
62
+
63
+	c.Assert(containerCreateEvent, checker.Not(checker.IsNil))
64
+	c.Assert(containerCreateEvent.Status, checker.Equals, "create")
65
+	c.Assert(containerCreateEvent.ID, checker.Equals, containerID)
66
+	c.Assert(containerCreateEvent.From, checker.Equals, "busybox")
67
+}
... ...
@@ -2,7 +2,6 @@ package main
2 2
 
3 3
 import (
4 4
 	"archive/tar"
5
-	"bufio"
6 5
 	"bytes"
7 6
 	"encoding/json"
8 7
 	"fmt"
... ...
@@ -1863,104 +1862,6 @@ func (s *DockerSuite) TestBuildForceRm(c *check.C) {
1863 1863
 
1864 1864
 }
1865 1865
 
1866
-// Test that an infinite sleep during a build is killed if the client disconnects.
1867
-// This test is fairly hairy because there are lots of ways to race.
1868
-// Strategy:
1869
-// * Monitor the output of docker events starting from before
1870
-// * Run a 1-year-long sleep from a docker build.
1871
-// * When docker events sees container start, close the "docker build" command
1872
-// * Wait for docker events to emit a dying event.
1873
-func (s *DockerSuite) TestBuildCancellationKillsSleep(c *check.C) {
1874
-	testRequires(c, DaemonIsLinux)
1875
-	name := "testbuildcancellation"
1876
-
1877
-	// (Note: one year, will never finish)
1878
-	ctx, err := fakeContext("FROM busybox\nRUN sleep 31536000", nil)
1879
-	if err != nil {
1880
-		c.Fatal(err)
1881
-	}
1882
-	defer ctx.Close()
1883
-
1884
-	eventStart := make(chan struct{})
1885
-	eventDie := make(chan struct{})
1886
-
1887
-	observer, err := newEventObserver(c)
1888
-	c.Assert(err, checker.IsNil)
1889
-	err = observer.Start()
1890
-	c.Assert(err, checker.IsNil)
1891
-	defer observer.Stop()
1892
-
1893
-	buildCmd := exec.Command(dockerBinary, "build", "-t", name, ".")
1894
-	buildCmd.Dir = ctx.Dir
1895
-
1896
-	stdoutBuild, err := buildCmd.StdoutPipe()
1897
-	if err := buildCmd.Start(); err != nil {
1898
-		c.Fatalf("failed to run build: %s", err)
1899
-	}
1900
-
1901
-	matchCID := regexp.MustCompile("Running in (.+)")
1902
-	scanner := bufio.NewScanner(stdoutBuild)
1903
-
1904
-	outputBuffer := new(bytes.Buffer)
1905
-	var buildID string
1906
-	for scanner.Scan() {
1907
-		line := scanner.Text()
1908
-		outputBuffer.WriteString(line)
1909
-		outputBuffer.WriteString("\n")
1910
-		if matches := matchCID.FindStringSubmatch(line); len(matches) > 0 {
1911
-			buildID = matches[1]
1912
-			break
1913
-		}
1914
-	}
1915
-
1916
-	if buildID == "" {
1917
-		c.Fatalf("Unable to find build container id in build output:\n%s", outputBuffer.String())
1918
-	}
1919
-
1920
-	matchStart := regexp.MustCompile(buildID + `.* start\z`)
1921
-	matchDie := regexp.MustCompile(buildID + `.* die\z`)
1922
-
1923
-	matcher := func(text string) {
1924
-		switch {
1925
-		case matchStart.MatchString(text):
1926
-			close(eventStart)
1927
-		case matchDie.MatchString(text):
1928
-			close(eventDie)
1929
-		}
1930
-	}
1931
-	go observer.Match(matcher)
1932
-
1933
-	select {
1934
-	case <-time.After(10 * time.Second):
1935
-		c.Fatal(observer.TimeoutError(buildID, "start"))
1936
-	case <-eventStart:
1937
-		// Proceeds from here when we see the container fly past in the
1938
-		// output of "docker events".
1939
-		// Now we know the container is running.
1940
-	}
1941
-
1942
-	// Send a kill to the `docker build` command.
1943
-	// Causes the underlying build to be cancelled due to socket close.
1944
-	if err := buildCmd.Process.Kill(); err != nil {
1945
-		c.Fatalf("error killing build command: %s", err)
1946
-	}
1947
-
1948
-	// Get the exit status of `docker build`, check it exited because killed.
1949
-	if err := buildCmd.Wait(); err != nil && !isKilled(err) {
1950
-		c.Fatalf("wait failed during build run: %T %s", err, err)
1951
-	}
1952
-
1953
-	select {
1954
-	case <-time.After(10 * time.Second):
1955
-		// If we don't get here in a timely fashion, it wasn't killed.
1956
-		c.Fatal(observer.TimeoutError(buildID, "die"))
1957
-	case <-eventDie:
1958
-		// We saw the container shut down in the `docker events` stream,
1959
-		// as expected.
1960
-	}
1961
-
1962
-}
1963
-
1964 1866
 func (s *DockerSuite) TestBuildRm(c *check.C) {
1965 1867
 	testRequires(c, DaemonIsLinux)
1966 1868
 	name := "testbuildrm"
... ...
@@ -6489,33 +6390,26 @@ func (s *DockerSuite) TestBuildNoNamedVolume(c *check.C) {
6489 6489
 func (s *DockerSuite) TestBuildTagEvent(c *check.C) {
6490 6490
 	testRequires(c, DaemonIsLinux)
6491 6491
 
6492
-	observer, err := newEventObserver(c, "--filter", "event=tag")
6493
-	c.Assert(err, check.IsNil)
6494
-	err = observer.Start()
6495
-	c.Assert(err, check.IsNil)
6496
-	defer observer.Stop()
6492
+	since := daemonTime(c).Unix()
6497 6493
 
6498 6494
 	dockerFile := `FROM busybox
6499 6495
 	RUN echo events
6500 6496
 	`
6501
-	_, err = buildImage("test", dockerFile, false)
6497
+	_, err := buildImage("test", dockerFile, false)
6502 6498
 	c.Assert(err, check.IsNil)
6503 6499
 
6504
-	matchTag := regexp.MustCompile("test:latest")
6505
-	eventTag := make(chan bool)
6506
-	matcher := func(text string) {
6507
-		if matchTag.MatchString(text) {
6508
-			close(eventTag)
6500
+	out, _ := dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "type=image")
6501
+	events := strings.Split(strings.TrimSpace(out), "\n")
6502
+	actions := eventActionsByIDAndType(c, events, "test:latest", "image")
6503
+	var foundTag bool
6504
+	for _, a := range actions {
6505
+		if a == "tag" {
6506
+			foundTag = true
6507
+			break
6509 6508
 		}
6510 6509
 	}
6511
-	go observer.Match(matcher)
6512 6510
 
6513
-	select {
6514
-	case <-time.After(10 * time.Second):
6515
-		c.Fatal(observer.TimeoutError("test:latest", "tag"))
6516
-	case <-eventTag:
6517
-		// We saw the tag event as expected.
6518
-	}
6511
+	c.Assert(foundTag, checker.True, check.Commentf("No tag event found:\n%s", out))
6519 6512
 }
6520 6513
 
6521 6514
 // #15780
... ...
@@ -3,12 +3,16 @@
3 3
 package main
4 4
 
5 5
 import (
6
+	"bufio"
7
+	"bytes"
6 8
 	"encoding/json"
7 9
 	"io/ioutil"
8 10
 	"os"
9 11
 	"os/exec"
10 12
 	"path/filepath"
13
+	"regexp"
11 14
 	"strings"
15
+	"time"
12 16
 
13 17
 	"github.com/docker/docker/pkg/integration/checker"
14 18
 	"github.com/docker/go-units"
... ...
@@ -115,5 +119,89 @@ func (s *DockerSuite) TestBuildAddChangeOwnership(c *check.C) {
115 115
 	if _, err := buildImageFromContext(name, ctx, true); err != nil {
116 116
 		c.Fatalf("build failed to complete for TestBuildAddChangeOwnership: %v", err)
117 117
 	}
118
+}
119
+
120
+// Test that an infinite sleep during a build is killed if the client disconnects.
121
+// This test is fairly hairy because there are lots of ways to race.
122
+// Strategy:
123
+// * Monitor the output of docker events starting from before
124
+// * Run a 1-year-long sleep from a docker build.
125
+// * When docker events sees container start, close the "docker build" command
126
+// * Wait for docker events to emit a dying event.
127
+func (s *DockerSuite) TestBuildCancellationKillsSleep(c *check.C) {
128
+	testRequires(c, DaemonIsLinux)
129
+	name := "testbuildcancellation"
130
+
131
+	observer, err := newEventObserver(c)
132
+	c.Assert(err, checker.IsNil)
133
+	err = observer.Start()
134
+	c.Assert(err, checker.IsNil)
135
+	defer observer.Stop()
118 136
 
137
+	// (Note: one year, will never finish)
138
+	ctx, err := fakeContext("FROM busybox\nRUN sleep 31536000", nil)
139
+	if err != nil {
140
+		c.Fatal(err)
141
+	}
142
+	defer ctx.Close()
143
+
144
+	buildCmd := exec.Command(dockerBinary, "build", "-t", name, ".")
145
+	buildCmd.Dir = ctx.Dir
146
+
147
+	stdoutBuild, err := buildCmd.StdoutPipe()
148
+	if err := buildCmd.Start(); err != nil {
149
+		c.Fatalf("failed to run build: %s", err)
150
+	}
151
+
152
+	matchCID := regexp.MustCompile("Running in (.+)")
153
+	scanner := bufio.NewScanner(stdoutBuild)
154
+
155
+	outputBuffer := new(bytes.Buffer)
156
+	var buildID string
157
+	for scanner.Scan() {
158
+		line := scanner.Text()
159
+		outputBuffer.WriteString(line)
160
+		outputBuffer.WriteString("\n")
161
+		if matches := matchCID.FindStringSubmatch(line); len(matches) > 0 {
162
+			buildID = matches[1]
163
+			break
164
+		}
165
+	}
166
+
167
+	if buildID == "" {
168
+		c.Fatalf("Unable to find build container id in build output:\n%s", outputBuffer.String())
169
+	}
170
+
171
+	testActions := map[string]chan bool{
172
+		"start": make(chan bool),
173
+		"die":   make(chan bool),
174
+	}
175
+
176
+	matcher := matchEventLine(buildID, "container", testActions)
177
+	go observer.Match(matcher)
178
+
179
+	select {
180
+	case <-time.After(10 * time.Second):
181
+		observer.CheckEventError(c, buildID, "start", matcher)
182
+	case <-testActions["start"]:
183
+		// ignore, done
184
+	}
185
+
186
+	// Send a kill to the `docker build` command.
187
+	// Causes the underlying build to be cancelled due to socket close.
188
+	if err := buildCmd.Process.Kill(); err != nil {
189
+		c.Fatalf("error killing build command: %s", err)
190
+	}
191
+
192
+	// Get the exit status of `docker build`, check it exited because killed.
193
+	if err := buildCmd.Wait(); err != nil && !isKilled(err) {
194
+		c.Fatalf("wait failed during build run: %T %s", err, err)
195
+	}
196
+
197
+	select {
198
+	case <-time.After(10 * time.Second):
199
+		observer.CheckEventError(c, buildID, "die", matcher)
200
+	case <-testActions["die"]:
201
+		// ignore, done
202
+	}
119 203
 }
... ...
@@ -7,7 +7,6 @@ import (
7 7
 	"net/http"
8 8
 	"os"
9 9
 	"os/exec"
10
-	"regexp"
11 10
 	"strconv"
12 11
 	"strings"
13 12
 	"sync"
... ...
@@ -67,22 +66,31 @@ func (s *DockerSuite) TestEventsUntag(c *check.C) {
67 67
 }
68 68
 
69 69
 func (s *DockerSuite) TestEventsContainerFailStartDie(c *check.C) {
70
-
71 70
 	out, _ := dockerCmd(c, "images", "-q")
72 71
 	image := strings.Split(out, "\n")[0]
73 72
 	_, _, err := dockerCmdWithError("run", "--name", "testeventdie", image, "blerg")
74 73
 	c.Assert(err, checker.NotNil, check.Commentf("Container run with command blerg should have failed, but it did not, out=%s", out))
75 74
 
76 75
 	out, _ = dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
77
-	events := strings.Split(out, "\n")
78
-	c.Assert(len(events), checker.GreaterThan, 1) //Missing expected event
79
-
80
-	startEvent := strings.Fields(events[len(events)-3])
81
-	dieEvent := strings.Fields(events[len(events)-2])
82
-
83
-	c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
84
-	c.Assert(dieEvent[len(dieEvent)-1], checker.Equals, "die", check.Commentf("event should be die, not %#v", dieEvent))
76
+	events := strings.Split(strings.TrimSpace(out), "\n")
85 77
 
78
+	nEvents := len(events)
79
+	c.Assert(nEvents, checker.GreaterOrEqualThan, 1) //Missing expected event
80
+
81
+	actions := eventActionsByIDAndType(c, events, "testeventdie", "container")
82
+
83
+	var startEvent bool
84
+	var dieEvent bool
85
+	for _, a := range actions {
86
+		switch a {
87
+		case "start":
88
+			startEvent = true
89
+		case "die":
90
+			dieEvent = true
91
+		}
92
+	}
93
+	c.Assert(startEvent, checker.True, check.Commentf("Start event not found: %v\n%v", actions, events))
94
+	c.Assert(dieEvent, checker.True, check.Commentf("Die event not found: %v\n%v", actions, events))
86 95
 }
87 96
 
88 97
 func (s *DockerSuite) TestEventsLimit(c *check.C) {
... ...
@@ -114,65 +122,44 @@ func (s *DockerSuite) TestEventsLimit(c *check.C) {
114 114
 
115 115
 func (s *DockerSuite) TestEventsContainerEvents(c *check.C) {
116 116
 	testRequires(c, DaemonIsLinux)
117
-	dockerCmd(c, "run", "--rm", "busybox", "true")
117
+	containerID, _ := dockerCmd(c, "run", "--rm", "--name", "container-events-test", "busybox", "true")
118
+	containerID = strings.TrimSpace(containerID)
119
+
118 120
 	out, _ := dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
119 121
 	events := strings.Split(out, "\n")
120 122
 	events = events[:len(events)-1]
121
-	c.Assert(len(events), checker.GreaterOrEqualThan, 5) //Missing expected event
122
-	createEvent := strings.Fields(events[len(events)-5])
123
-	attachEvent := strings.Fields(events[len(events)-4])
124
-	startEvent := strings.Fields(events[len(events)-3])
125
-	dieEvent := strings.Fields(events[len(events)-2])
126
-	destroyEvent := strings.Fields(events[len(events)-1])
127
-	c.Assert(createEvent[len(createEvent)-1], checker.Equals, "create", check.Commentf("event should be create, not %#v", createEvent))
128
-	c.Assert(attachEvent[len(attachEvent)-1], checker.Equals, "attach", check.Commentf("event should be attach, not %#v", attachEvent))
129
-	c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
130
-	c.Assert(dieEvent[len(dieEvent)-1], checker.Equals, "die", check.Commentf("event should be die, not %#v", dieEvent))
131
-	c.Assert(destroyEvent[len(destroyEvent)-1], checker.Equals, "destroy", check.Commentf("event should be destroy, not %#v", destroyEvent))
132 123
 
124
+	nEvents := len(events)
125
+	c.Assert(nEvents, checker.GreaterOrEqualThan, 5) //Missing expected event
126
+	containerEvents := eventActionsByIDAndType(c, events, "container-events-test", "container")
127
+	c.Assert(containerEvents, checker.HasLen, 5, check.Commentf("events: %v", events))
128
+
129
+	c.Assert(containerEvents[0], checker.Equals, "create", check.Commentf(out))
130
+	c.Assert(containerEvents[1], checker.Equals, "attach", check.Commentf(out))
131
+	c.Assert(containerEvents[2], checker.Equals, "start", check.Commentf(out))
132
+	c.Assert(containerEvents[3], checker.Equals, "die", check.Commentf(out))
133
+	c.Assert(containerEvents[4], checker.Equals, "destroy", check.Commentf(out))
133 134
 }
134 135
 
135 136
 func (s *DockerSuite) TestEventsContainerEventsSinceUnixEpoch(c *check.C) {
136 137
 	testRequires(c, DaemonIsLinux)
137
-	dockerCmd(c, "run", "--rm", "busybox", "true")
138
+	dockerCmd(c, "run", "--rm", "--name", "since-epoch-test", "busybox", "true")
138 139
 	timeBeginning := time.Unix(0, 0).Format(time.RFC3339Nano)
139 140
 	timeBeginning = strings.Replace(timeBeginning, "Z", ".000000000Z", -1)
140
-	out, _ := dockerCmd(c, "events", fmt.Sprintf("--since='%s'", timeBeginning),
141
-		fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
141
+	out, _ := dockerCmd(c, "events", fmt.Sprintf("--since='%s'", timeBeginning), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
142 142
 	events := strings.Split(out, "\n")
143 143
 	events = events[:len(events)-1]
144
-	c.Assert(len(events), checker.GreaterOrEqualThan, 5) //Missing expected event
145
-	createEvent := strings.Fields(events[len(events)-5])
146
-	attachEvent := strings.Fields(events[len(events)-4])
147
-	startEvent := strings.Fields(events[len(events)-3])
148
-	dieEvent := strings.Fields(events[len(events)-2])
149
-	destroyEvent := strings.Fields(events[len(events)-1])
150
-	c.Assert(createEvent[len(createEvent)-1], checker.Equals, "create", check.Commentf("event should be create, not %#v", createEvent))
151
-	c.Assert(attachEvent[len(attachEvent)-1], checker.Equals, "attach", check.Commentf("event should be attach, not %#v", attachEvent))
152
-	c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
153
-	c.Assert(dieEvent[len(dieEvent)-1], checker.Equals, "die", check.Commentf("event should be die, not %#v", dieEvent))
154
-	c.Assert(destroyEvent[len(destroyEvent)-1], checker.Equals, "destroy", check.Commentf("event should be destroy, not %#v", destroyEvent))
155
-
156
-}
157 144
 
158
-func (s *DockerSuite) TestEventsImageUntagDelete(c *check.C) {
159
-	testRequires(c, DaemonIsLinux)
160
-	name := "testimageevents"
161
-	_, err := buildImage(name,
162
-		`FROM scratch
163
-		MAINTAINER "docker"`,
164
-		true)
165
-	c.Assert(err, checker.IsNil)
166
-	c.Assert(deleteImages(name), checker.IsNil)
167
-	out, _ := dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
168
-	events := strings.Split(out, "\n")
169
-
170
-	events = events[:len(events)-1]
171
-	c.Assert(len(events), checker.GreaterOrEqualThan, 2) //Missing expected event
172
-	untagEvent := strings.Fields(events[len(events)-2])
173
-	deleteEvent := strings.Fields(events[len(events)-1])
174
-	c.Assert(untagEvent[len(untagEvent)-1], checker.Equals, "untag", check.Commentf("untag should be untag, not %#v", untagEvent))
175
-	c.Assert(deleteEvent[len(deleteEvent)-1], checker.Equals, "delete", check.Commentf("untag should be delete, not %#v", untagEvent))
145
+	nEvents := len(events)
146
+	c.Assert(nEvents, checker.GreaterOrEqualThan, 5) //Missing expected event
147
+	containerEvents := eventActionsByIDAndType(c, events, "since-epoch-test", "container")
148
+	c.Assert(containerEvents, checker.HasLen, 5, check.Commentf("events: %v", events))
149
+
150
+	c.Assert(containerEvents[0], checker.Equals, "create", check.Commentf(out))
151
+	c.Assert(containerEvents[1], checker.Equals, "attach", check.Commentf(out))
152
+	c.Assert(containerEvents[2], checker.Equals, "start", check.Commentf(out))
153
+	c.Assert(containerEvents[3], checker.Equals, "die", check.Commentf(out))
154
+	c.Assert(containerEvents[4], checker.Equals, "destroy", check.Commentf(out))
176 155
 }
177 156
 
178 157
 func (s *DockerSuite) TestEventsImageTag(c *check.C) {
... ...
@@ -189,10 +176,10 @@ func (s *DockerSuite) TestEventsImageTag(c *check.C) {
189 189
 	events := strings.Split(strings.TrimSpace(out), "\n")
190 190
 	c.Assert(events, checker.HasLen, 1, check.Commentf("was expecting 1 event. out=%s", out))
191 191
 	event := strings.TrimSpace(events[0])
192
-	expectedStr := image + ": tag"
193
-
194
-	c.Assert(event, checker.HasSuffix, expectedStr, check.Commentf("wrong event format. expected='%s' got=%s", expectedStr, event))
195 192
 
193
+	matches := parseEventText(event)
194
+	c.Assert(matchEventID(matches, image), checker.True, check.Commentf("matches: %v\nout:\n%s", matches, out))
195
+	c.Assert(matches["action"], checker.Equals, "tag")
196 196
 }
197 197
 
198 198
 func (s *DockerSuite) TestEventsImagePull(c *check.C) {
... ...
@@ -208,68 +195,45 @@ func (s *DockerSuite) TestEventsImagePull(c *check.C) {
208 208
 
209 209
 	events := strings.Split(strings.TrimSpace(out), "\n")
210 210
 	event := strings.TrimSpace(events[len(events)-1])
211
-
212
-	c.Assert(event, checker.HasSuffix, "hello-world:latest: pull", check.Commentf("Missing pull event - got:%q", event))
211
+	matches := parseEventText(event)
212
+	c.Assert(matches["id"], checker.Equals, "hello-world:latest")
213
+	c.Assert(matches["action"], checker.Equals, "pull")
213 214
 
214 215
 }
215 216
 
216 217
 func (s *DockerSuite) TestEventsImageImport(c *check.C) {
217 218
 	testRequires(c, DaemonIsLinux)
218 219
 
219
-	observer, err := newEventObserver(c)
220
-	c.Assert(err, checker.IsNil)
221
-
222
-	err = observer.Start()
223
-	c.Assert(err, checker.IsNil)
224
-	defer observer.Stop()
225
-
226 220
 	out, _ := dockerCmd(c, "run", "-d", "busybox", "true")
227 221
 	cleanedContainerID := strings.TrimSpace(out)
228 222
 
229
-	out, _, err = runCommandPipelineWithOutput(
223
+	since := daemonTime(c).Unix()
224
+	out, _, err := runCommandPipelineWithOutput(
230 225
 		exec.Command(dockerBinary, "export", cleanedContainerID),
231 226
 		exec.Command(dockerBinary, "import", "-"),
232 227
 	)
233 228
 	c.Assert(err, checker.IsNil, check.Commentf("import failed with output: %q", out))
234 229
 	imageRef := strings.TrimSpace(out)
235 230
 
236
-	eventImport := make(chan bool)
237
-	matchImport := regexp.MustCompile(imageRef + `: import\z`)
238
-	matcher := func(text string) {
239
-		if matchImport.MatchString(text) {
240
-			close(eventImport)
241
-		}
242
-	}
243
-	go observer.Match(matcher)
244
-
245
-	select {
246
-	case <-time.After(5 * time.Second):
247
-		c.Fatal(observer.TimeoutError(imageRef, "import"))
248
-	case <-eventImport:
249
-		// ignore, done
250
-	}
231
+	out, _ = dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "event=import")
232
+	events := strings.Split(strings.TrimSpace(out), "\n")
233
+	c.Assert(events, checker.HasLen, 1)
234
+	matches := parseEventText(events[0])
235
+	c.Assert(matches["id"], checker.Equals, imageRef, check.Commentf("matches: %v\nout:\n%s\n", matches, out))
236
+	c.Assert(matches["action"], checker.Equals, "import", check.Commentf("matches: %v\nout:\n%s\n", matches, out))
251 237
 }
252 238
 
253 239
 func (s *DockerSuite) TestEventsFilters(c *check.C) {
254 240
 	testRequires(c, DaemonIsLinux)
255
-	parseEvents := func(out, match string) {
256
-		events := strings.Split(out, "\n")
257
-		events = events[:len(events)-1]
258
-		for _, event := range events {
259
-			eventFields := strings.Fields(event)
260
-			eventName := eventFields[len(eventFields)-1]
261
-			c.Assert(eventName, checker.Matches, match)
262
-		}
263
-	}
264 241
 
265 242
 	since := daemonTime(c).Unix()
266 243
 	dockerCmd(c, "run", "--rm", "busybox", "true")
267 244
 	dockerCmd(c, "run", "--rm", "busybox", "true")
268 245
 	out, _ := dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "event=die")
269
-	parseEvents(out, "die")
246
+	parseEvents(c, out, "die")
270 247
 
271 248
 	out, _ = dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "event=die", "--filter", "event=start")
272
-	parseEvents(out, "((die)|(start))")
249
+	parseEvents(c, out, "die|start")
273 250
 
274 251
 	// make sure we at least got 2 start events
275 252
 	count := strings.Count(out, "start")
... ...
@@ -355,7 +319,8 @@ func (s *DockerSuite) TestEventsFilterImageLabels(c *check.C) {
355 355
 		"events",
356 356
 		fmt.Sprintf("--since=%d", since),
357 357
 		fmt.Sprintf("--until=%d", daemonTime(c).Unix()),
358
-		"--filter", fmt.Sprintf("label=%s", label))
358
+		"--filter", fmt.Sprintf("label=%s", label),
359
+		"--filter", "type=image")
359 360
 
360 361
 	events := strings.Split(strings.TrimSpace(out), "\n")
361 362
 
... ...
@@ -385,15 +350,9 @@ func (s *DockerSuite) TestEventsFilterContainer(c *check.C) {
385 385
 			return fmt.Errorf("expected 4 events, got %v", events)
386 386
 		}
387 387
 		for _, event := range events {
388
-			e := strings.Fields(event)
389
-			if len(e) < 3 {
390
-				return fmt.Errorf("got malformed event: %s", event)
391
-			}
392
-
393
-			// Check the id
394
-			parsedID := strings.TrimSuffix(e[1], ":")
395
-			if parsedID != id {
396
-				return fmt.Errorf("expected event for container id %s: %s - parsed container id: %s", id, event, parsedID)
388
+			matches := parseEventText(event)
389
+			if !matchEventID(matches, id) {
390
+				return fmt.Errorf("expected event for container id %s: %s - parsed container id: %s", id, event, matches["id"])
397 391
 			}
398 392
 		}
399 393
 		return nil
... ...
@@ -412,72 +371,6 @@ func (s *DockerSuite) TestEventsFilterContainer(c *check.C) {
412 412
 	}
413 413
 }
414 414
 
415
-func (s *DockerSuite) TestEventsStreaming(c *check.C) {
416
-	testRequires(c, DaemonIsLinux)
417
-
418
-	eventCreate := make(chan struct{})
419
-	eventStart := make(chan struct{})
420
-	eventDie := make(chan struct{})
421
-	eventDestroy := make(chan struct{})
422
-
423
-	observer, err := newEventObserver(c)
424
-	c.Assert(err, checker.IsNil)
425
-	err = observer.Start()
426
-	c.Assert(err, checker.IsNil)
427
-	defer observer.Stop()
428
-
429
-	out, _ := dockerCmd(c, "run", "-d", "busybox:latest", "true")
430
-	containerID := strings.TrimSpace(out)
431
-	matchCreate := regexp.MustCompile(containerID + `: \(from busybox:latest\) create\z`)
432
-	matchStart := regexp.MustCompile(containerID + `: \(from busybox:latest\) start\z`)
433
-	matchDie := regexp.MustCompile(containerID + `: \(from busybox:latest\) die\z`)
434
-	matchDestroy := regexp.MustCompile(containerID + `: \(from busybox:latest\) destroy\z`)
435
-
436
-	matcher := func(text string) {
437
-		switch {
438
-		case matchCreate.MatchString(text):
439
-			close(eventCreate)
440
-		case matchStart.MatchString(text):
441
-			close(eventStart)
442
-		case matchDie.MatchString(text):
443
-			close(eventDie)
444
-		case matchDestroy.MatchString(text):
445
-			close(eventDestroy)
446
-		}
447
-	}
448
-	go observer.Match(matcher)
449
-
450
-	select {
451
-	case <-time.After(5 * time.Second):
452
-		c.Fatal(observer.TimeoutError(containerID, "create"))
453
-	case <-eventCreate:
454
-		// ignore, done
455
-	}
456
-
457
-	select {
458
-	case <-time.After(5 * time.Second):
459
-		c.Fatal(observer.TimeoutError(containerID, "start"))
460
-	case <-eventStart:
461
-		// ignore, done
462
-	}
463
-
464
-	select {
465
-	case <-time.After(5 * time.Second):
466
-		c.Fatal(observer.TimeoutError(containerID, "die"))
467
-	case <-eventDie:
468
-		// ignore, done
469
-	}
470
-
471
-	dockerCmd(c, "rm", containerID)
472
-
473
-	select {
474
-	case <-time.After(5 * time.Second):
475
-		c.Fatal(observer.TimeoutError(containerID, "destroy"))
476
-	case <-eventDestroy:
477
-		// ignore, done
478
-	}
479
-}
480
-
481 415
 func (s *DockerSuite) TestEventsCommit(c *check.C) {
482 416
 	testRequires(c, DaemonIsLinux)
483 417
 	since := daemonTime(c).Unix()
... ...
@@ -490,7 +383,7 @@ func (s *DockerSuite) TestEventsCommit(c *check.C) {
490 490
 	dockerCmd(c, "stop", cID)
491 491
 
492 492
 	out, _ = dockerCmd(c, "events", "--since=0", "-f", "container="+cID, "--until="+strconv.Itoa(int(since)))
493
-	c.Assert(out, checker.Contains, " commit\n", check.Commentf("Missing 'commit' log event"))
493
+	c.Assert(out, checker.Contains, "commit", check.Commentf("Missing 'commit' log event"))
494 494
 }
495 495
 
496 496
 func (s *DockerSuite) TestEventsCopy(c *check.C) {
... ...
@@ -515,12 +408,12 @@ func (s *DockerSuite) TestEventsCopy(c *check.C) {
515 515
 	dockerCmd(c, "cp", "cptest:/tmp/file", tempFile.Name())
516 516
 
517 517
 	out, _ := dockerCmd(c, "events", "--since=0", "-f", "container=cptest", "--until="+strconv.Itoa(int(since)))
518
-	c.Assert(out, checker.Contains, " archive-path\n", check.Commentf("Missing 'archive-path' log event\n"))
518
+	c.Assert(out, checker.Contains, "archive-path", check.Commentf("Missing 'archive-path' log event\n"))
519 519
 
520 520
 	dockerCmd(c, "cp", tempFile.Name(), "cptest:/tmp/filecopy")
521 521
 
522 522
 	out, _ = dockerCmd(c, "events", "--since=0", "-f", "container=cptest", "--until="+strconv.Itoa(int(since)))
523
-	c.Assert(out, checker.Contains, " extract-to-dir\n", check.Commentf("Missing 'extract-to-dir' log event"))
523
+	c.Assert(out, checker.Contains, "extract-to-dir", check.Commentf("Missing 'extract-to-dir' log event"))
524 524
 }
525 525
 
526 526
 func (s *DockerSuite) TestEventsResize(c *check.C) {
... ...
@@ -539,7 +432,7 @@ func (s *DockerSuite) TestEventsResize(c *check.C) {
539 539
 	dockerCmd(c, "stop", cID)
540 540
 
541 541
 	out, _ = dockerCmd(c, "events", "--since=0", "-f", "container="+cID, "--until="+strconv.Itoa(int(since)))
542
-	c.Assert(out, checker.Contains, " resize\n", check.Commentf("Missing 'resize' log event"))
542
+	c.Assert(out, checker.Contains, "resize", check.Commentf("Missing 'resize' log event"))
543 543
 }
544 544
 
545 545
 func (s *DockerSuite) TestEventsAttach(c *check.C) {
... ...
@@ -571,7 +464,7 @@ func (s *DockerSuite) TestEventsAttach(c *check.C) {
571 571
 	dockerCmd(c, "stop", cID)
572 572
 
573 573
 	out, _ = dockerCmd(c, "events", "--since=0", "-f", "container="+cID, "--until="+strconv.Itoa(int(since)))
574
-	c.Assert(out, checker.Contains, " attach\n", check.Commentf("Missing 'attach' log event"))
574
+	c.Assert(out, checker.Contains, "attach", check.Commentf("Missing 'attach' log event"))
575 575
 }
576 576
 
577 577
 func (s *DockerSuite) TestEventsRename(c *check.C) {
... ...
@@ -582,7 +475,7 @@ func (s *DockerSuite) TestEventsRename(c *check.C) {
582 582
 	dockerCmd(c, "rename", "oldName", "newName")
583 583
 
584 584
 	out, _ := dockerCmd(c, "events", "--since=0", "-f", "container=newName", "--until="+strconv.Itoa(int(since)))
585
-	c.Assert(out, checker.Contains, " rename\n", check.Commentf("Missing 'rename' log event\n"))
585
+	c.Assert(out, checker.Contains, "rename", check.Commentf("Missing 'rename' log event\n"))
586 586
 }
587 587
 
588 588
 func (s *DockerSuite) TestEventsTop(c *check.C) {
... ...
@@ -597,7 +490,7 @@ func (s *DockerSuite) TestEventsTop(c *check.C) {
597 597
 	dockerCmd(c, "stop", cID)
598 598
 
599 599
 	out, _ = dockerCmd(c, "events", "--since=0", "-f", "container="+cID, "--until="+strconv.Itoa(int(since)))
600
-	c.Assert(out, checker.Contains, " top\n", check.Commentf("Missing 'top' log event"))
600
+	c.Assert(out, checker.Contains, " top", check.Commentf("Missing 'top' log event"))
601 601
 }
602 602
 
603 603
 // #13753
... ...
@@ -624,5 +517,71 @@ func (s *DockerRegistrySuite) TestEventsImageFilterPush(c *check.C) {
624 624
 	dockerCmd(c, "push", repoName)
625 625
 
626 626
 	out, _ = dockerCmd(c, "events", "--since=0", "-f", "image="+repoName, "-f", "event=push", "--until="+strconv.Itoa(int(since)))
627
-	c.Assert(out, checker.Contains, repoName+": push\n", check.Commentf("Missing 'push' log event"))
627
+	c.Assert(out, checker.Contains, repoName, check.Commentf("Missing 'push' log event for %s", repoName))
628
+}
629
+
630
+func (s *DockerSuite) TestEventsFilterType(c *check.C) {
631
+	testRequires(c, DaemonIsLinux)
632
+	since := daemonTime(c).Unix()
633
+	name := "labelfiltertest"
634
+	label := "io.docker.testing=image"
635
+
636
+	// Build a test image.
637
+	_, err := buildImage(name, fmt.Sprintf(`
638
+		FROM busybox:latest
639
+		LABEL %s`, label), true)
640
+	c.Assert(err, checker.IsNil, check.Commentf("Couldn't create image"))
641
+
642
+	dockerCmd(c, "tag", name, "labelfiltertest:tag1")
643
+	dockerCmd(c, "tag", name, "labelfiltertest:tag2")
644
+	dockerCmd(c, "tag", "busybox:latest", "labelfiltertest:tag3")
645
+
646
+	out, _ := dockerCmd(
647
+		c,
648
+		"events",
649
+		fmt.Sprintf("--since=%d", since),
650
+		fmt.Sprintf("--until=%d", daemonTime(c).Unix()),
651
+		"--filter", fmt.Sprintf("label=%s", label),
652
+		"--filter", "type=image")
653
+
654
+	events := strings.Split(strings.TrimSpace(out), "\n")
655
+
656
+	// 2 events from the "docker tag" command, another one is from "docker build"
657
+	c.Assert(events, checker.HasLen, 3, check.Commentf("Events == %s", events))
658
+	for _, e := range events {
659
+		c.Assert(e, checker.Contains, "labelfiltertest")
660
+	}
661
+
662
+	out, _ = dockerCmd(
663
+		c,
664
+		"events",
665
+		fmt.Sprintf("--since=%d", since),
666
+		fmt.Sprintf("--until=%d", daemonTime(c).Unix()),
667
+		"--filter", fmt.Sprintf("label=%s", label),
668
+		"--filter", "type=container")
669
+	events = strings.Split(strings.TrimSpace(out), "\n")
670
+
671
+	// Events generated by the container that builds the image
672
+	c.Assert(events, checker.HasLen, 3, check.Commentf("Events == %s", events))
673
+
674
+	out, _ = dockerCmd(
675
+		c,
676
+		"events",
677
+		fmt.Sprintf("--since=%d", since),
678
+		fmt.Sprintf("--until=%d", daemonTime(c).Unix()),
679
+		"--filter", "type=network")
680
+	events = strings.Split(strings.TrimSpace(out), "\n")
681
+	c.Assert(len(events), checker.GreaterOrEqualThan, 1, check.Commentf("Events == %s", events))
682
+}
683
+
684
+func (s *DockerSuite) TestEventsFilterImageInContainerAction(c *check.C) {
685
+	testRequires(c, DaemonIsLinux)
686
+
687
+	since := daemonTime(c).Unix()
688
+	dockerCmd(c, "run", "--name", "test-container", "-d", "busybox", "true")
689
+	waitRun("test-container")
690
+
691
+	out, _ := dockerCmd(c, "events", "--filter", "image=busybox", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
692
+	events := strings.Split(strings.TrimSpace(out), "\n")
693
+	c.Assert(len(events), checker.GreaterThan, 1, check.Commentf(out))
628 694
 }
... ...
@@ -65,18 +65,14 @@ func (s *DockerSuite) TestEventsOOMDisableFalse(c *check.C) {
65 65
 
66 66
 	out, _ := dockerCmd(c, "events", "--since=0", "-f", "container=oomFalse", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
67 67
 	events := strings.Split(strings.TrimSuffix(out, "\n"), "\n")
68
-	c.Assert(len(events), checker.GreaterOrEqualThan, 5) //Missing expected event
69
-
70
-	createEvent := strings.Fields(events[len(events)-5])
71
-	attachEvent := strings.Fields(events[len(events)-4])
72
-	startEvent := strings.Fields(events[len(events)-3])
73
-	oomEvent := strings.Fields(events[len(events)-2])
74
-	dieEvent := strings.Fields(events[len(events)-1])
75
-	c.Assert(createEvent[len(createEvent)-1], checker.Equals, "create", check.Commentf("event should be create, not %#v", createEvent))
76
-	c.Assert(attachEvent[len(attachEvent)-1], checker.Equals, "attach", check.Commentf("event should be attach, not %#v", attachEvent))
77
-	c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
78
-	c.Assert(oomEvent[len(oomEvent)-1], checker.Equals, "oom", check.Commentf("event should be oom, not %#v", oomEvent))
79
-	c.Assert(dieEvent[len(dieEvent)-1], checker.Equals, "die", check.Commentf("event should be die, not %#v", dieEvent))
68
+	nEvents := len(events)
69
+
70
+	c.Assert(nEvents, checker.GreaterOrEqualThan, 5) //Missing expected event
71
+	c.Assert(parseEventAction(c, events[nEvents-5]), checker.Equals, "create")
72
+	c.Assert(parseEventAction(c, events[nEvents-4]), checker.Equals, "attach")
73
+	c.Assert(parseEventAction(c, events[nEvents-3]), checker.Equals, "start")
74
+	c.Assert(parseEventAction(c, events[nEvents-2]), checker.Equals, "oom")
75
+	c.Assert(parseEventAction(c, events[nEvents-1]), checker.Equals, "die")
80 76
 }
81 77
 
82 78
 func (s *DockerSuite) TestEventsOOMDisableTrue(c *check.C) {
... ...
@@ -98,19 +94,252 @@ func (s *DockerSuite) TestEventsOOMDisableTrue(c *check.C) {
98 98
 
99 99
 		out, _ := dockerCmd(c, "events", "--since=0", "-f", "container=oomTrue", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
100 100
 		events := strings.Split(strings.TrimSuffix(out, "\n"), "\n")
101
-		c.Assert(len(events), checker.GreaterOrEqualThan, 4) //Missing expected event
102
-
103
-		createEvent := strings.Fields(events[len(events)-4])
104
-		attachEvent := strings.Fields(events[len(events)-3])
105
-		startEvent := strings.Fields(events[len(events)-2])
106
-		oomEvent := strings.Fields(events[len(events)-1])
101
+		nEvents := len(events)
102
+		c.Assert(nEvents, checker.GreaterOrEqualThan, 4) //Missing expected event
107 103
 
108
-		c.Assert(createEvent[len(createEvent)-1], checker.Equals, "create", check.Commentf("event should be create, not %#v", createEvent))
109
-		c.Assert(attachEvent[len(attachEvent)-1], checker.Equals, "attach", check.Commentf("event should be attach, not %#v", attachEvent))
110
-		c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
111
-		c.Assert(oomEvent[len(oomEvent)-1], checker.Equals, "oom", check.Commentf("event should be oom, not %#v", oomEvent))
104
+		c.Assert(parseEventAction(c, events[nEvents-4]), checker.Equals, "create")
105
+		c.Assert(parseEventAction(c, events[nEvents-3]), checker.Equals, "attach")
106
+		c.Assert(parseEventAction(c, events[nEvents-2]), checker.Equals, "start")
107
+		c.Assert(parseEventAction(c, events[nEvents-1]), checker.Equals, "oom")
112 108
 
113 109
 		out, _ = dockerCmd(c, "inspect", "-f", "{{.State.Status}}", "oomTrue")
114 110
 		c.Assert(strings.TrimSpace(out), checker.Equals, "running", check.Commentf("container should be still running"))
115 111
 	}
116 112
 }
113
+
114
+// #18453
115
+func (s *DockerSuite) TestEventsContainerFilterByName(c *check.C) {
116
+	testRequires(c, DaemonIsLinux)
117
+	cOut, _ := dockerCmd(c, "run", "--name=foo", "-d", "busybox", "top")
118
+	c1 := strings.TrimSpace(cOut)
119
+	waitRun("foo")
120
+	cOut, _ = dockerCmd(c, "run", "--name=bar", "-d", "busybox", "top")
121
+	c2 := strings.TrimSpace(cOut)
122
+	waitRun("bar")
123
+	out, _ := dockerCmd(c, "events", "-f", "container=foo", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
124
+	c.Assert(out, checker.Contains, c1, check.Commentf(out))
125
+	c.Assert(out, checker.Not(checker.Contains), c2, check.Commentf(out))
126
+}
127
+
128
+// #18453
129
+func (s *DockerSuite) TestEventsContainerFilterBeforeCreate(c *check.C) {
130
+	testRequires(c, DaemonIsLinux)
131
+	var (
132
+		out string
133
+		ch  chan struct{}
134
+	)
135
+	ch = make(chan struct{})
136
+
137
+	// calculate the time it takes to create and start a container and sleep 2 seconds
138
+	// this is to make sure the docker event will recevie the event of container
139
+	since := daemonTime(c).Unix()
140
+	id, _ := dockerCmd(c, "run", "-d", "busybox", "top")
141
+	cID := strings.TrimSpace(id)
142
+	waitRun(cID)
143
+	time.Sleep(2 * time.Second)
144
+	duration := daemonTime(c).Unix() - since
145
+
146
+	go func() {
147
+		out, _ = dockerCmd(c, "events", "-f", "container=foo", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()+2*duration))
148
+		close(ch)
149
+	}()
150
+	// Sleep 2 second to wait docker event to start
151
+	time.Sleep(2 * time.Second)
152
+	id, _ = dockerCmd(c, "run", "--name=foo", "-d", "busybox", "top")
153
+	cID = strings.TrimSpace(id)
154
+	waitRun(cID)
155
+	<-ch
156
+	c.Assert(out, checker.Contains, cID, check.Commentf("Missing event of container (foo)"))
157
+}
158
+
159
+func (s *DockerSuite) TestVolumeEvents(c *check.C) {
160
+	testRequires(c, DaemonIsLinux)
161
+
162
+	since := daemonTime(c).Unix()
163
+
164
+	// Observe create/mount volume actions
165
+	dockerCmd(c, "volume", "create", "--name", "test-event-volume-local")
166
+	dockerCmd(c, "run", "--name", "test-volume-container", "--volume", "test-event-volume-local:/foo", "-d", "busybox", "true")
167
+	waitRun("test-volume-container")
168
+
169
+	// Observe unmount/destroy volume actions
170
+	dockerCmd(c, "rm", "-f", "test-volume-container")
171
+	dockerCmd(c, "volume", "rm", "test-event-volume-local")
172
+
173
+	out, _ := dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
174
+	events := strings.Split(strings.TrimSpace(out), "\n")
175
+	c.Assert(len(events), checker.GreaterThan, 4)
176
+
177
+	volumeEvents := eventActionsByIDAndType(c, events, "test-event-volume-local", "volume")
178
+	c.Assert(volumeEvents, checker.HasLen, 4)
179
+	c.Assert(volumeEvents[0], checker.Equals, "create")
180
+	c.Assert(volumeEvents[1], checker.Equals, "mount")
181
+	c.Assert(volumeEvents[2], checker.Equals, "unmount")
182
+	c.Assert(volumeEvents[3], checker.Equals, "destroy")
183
+}
184
+
185
+func (s *DockerSuite) TestNetworkEvents(c *check.C) {
186
+	testRequires(c, DaemonIsLinux)
187
+
188
+	since := daemonTime(c).Unix()
189
+
190
+	// Observe create/connect network actions
191
+	dockerCmd(c, "network", "create", "test-event-network-local")
192
+	dockerCmd(c, "run", "--name", "test-network-container", "--net", "test-event-network-local", "-d", "busybox", "true")
193
+	waitRun("test-network-container")
194
+
195
+	// Observe disconnect/destroy network actions
196
+	dockerCmd(c, "rm", "-f", "test-network-container")
197
+	dockerCmd(c, "network", "rm", "test-event-network-local")
198
+
199
+	out, _ := dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
200
+	events := strings.Split(strings.TrimSpace(out), "\n")
201
+	c.Assert(len(events), checker.GreaterThan, 4)
202
+
203
+	netEvents := eventActionsByIDAndType(c, events, "test-event-network-local", "network")
204
+	c.Assert(netEvents, checker.HasLen, 4)
205
+	c.Assert(netEvents[0], checker.Equals, "create")
206
+	c.Assert(netEvents[1], checker.Equals, "connect")
207
+	c.Assert(netEvents[2], checker.Equals, "disconnect")
208
+	c.Assert(netEvents[3], checker.Equals, "destroy")
209
+}
210
+
211
+func (s *DockerSuite) TestEventsStreaming(c *check.C) {
212
+	testRequires(c, DaemonIsLinux)
213
+
214
+	observer, err := newEventObserver(c)
215
+	c.Assert(err, checker.IsNil)
216
+	err = observer.Start()
217
+	c.Assert(err, checker.IsNil)
218
+	defer observer.Stop()
219
+
220
+	out, _ := dockerCmd(c, "run", "-d", "busybox:latest", "true")
221
+	containerID := strings.TrimSpace(out)
222
+
223
+	testActions := map[string]chan bool{
224
+		"create":  make(chan bool),
225
+		"start":   make(chan bool),
226
+		"die":     make(chan bool),
227
+		"destroy": make(chan bool),
228
+	}
229
+
230
+	matcher := matchEventLine(containerID, "container", testActions)
231
+	go observer.Match(matcher)
232
+
233
+	select {
234
+	case <-time.After(5 * time.Second):
235
+		observer.CheckEventError(c, containerID, "create", matcher)
236
+	case <-testActions["create"]:
237
+		// ignore, done
238
+	}
239
+
240
+	select {
241
+	case <-time.After(5 * time.Second):
242
+		observer.CheckEventError(c, containerID, "start", matcher)
243
+	case <-testActions["start"]:
244
+		// ignore, done
245
+	}
246
+
247
+	select {
248
+	case <-time.After(5 * time.Second):
249
+		observer.CheckEventError(c, containerID, "die", matcher)
250
+	case <-testActions["die"]:
251
+		// ignore, done
252
+	}
253
+
254
+	dockerCmd(c, "rm", containerID)
255
+
256
+	select {
257
+	case <-time.After(5 * time.Second):
258
+		observer.CheckEventError(c, containerID, "destroy", matcher)
259
+	case <-testActions["destroy"]:
260
+		// ignore, done
261
+	}
262
+}
263
+
264
+func (s *DockerSuite) TestEventsImageUntagDelete(c *check.C) {
265
+	testRequires(c, DaemonIsLinux)
266
+
267
+	observer, err := newEventObserver(c)
268
+	c.Assert(err, checker.IsNil)
269
+	err = observer.Start()
270
+	c.Assert(err, checker.IsNil)
271
+	defer observer.Stop()
272
+
273
+	name := "testimageevents"
274
+	imageID, err := buildImage(name,
275
+		`FROM scratch
276
+		MAINTAINER "docker"`,
277
+		true)
278
+	c.Assert(err, checker.IsNil)
279
+	c.Assert(deleteImages(name), checker.IsNil)
280
+
281
+	testActions := map[string]chan bool{
282
+		"untag":  make(chan bool),
283
+		"delete": make(chan bool),
284
+	}
285
+
286
+	matcher := matchEventLine(imageID, "image", testActions)
287
+	go observer.Match(matcher)
288
+
289
+	select {
290
+	case <-time.After(10 * time.Second):
291
+		observer.CheckEventError(c, imageID, "untag", matcher)
292
+	case <-testActions["untag"]:
293
+		// ignore, done
294
+	}
295
+
296
+	select {
297
+	case <-time.After(10 * time.Second):
298
+		observer.CheckEventError(c, imageID, "delete", matcher)
299
+	case <-testActions["delete"]:
300
+		// ignore, done
301
+	}
302
+}
303
+
304
+func (s *DockerSuite) TestEventsFilterVolumeAndNetworkType(c *check.C) {
305
+	testRequires(c, DaemonIsLinux)
306
+
307
+	since := daemonTime(c).Unix()
308
+
309
+	dockerCmd(c, "network", "create", "test-event-network-type")
310
+	dockerCmd(c, "volume", "create", "--name", "test-event-volume-type")
311
+
312
+	out, _ := dockerCmd(c, "events", "--filter", "type=volume", "--filter", "type=network", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
313
+	events := strings.Split(strings.TrimSpace(out), "\n")
314
+	c.Assert(len(events), checker.GreaterOrEqualThan, 2, check.Commentf(out))
315
+
316
+	networkActions := eventActionsByIDAndType(c, events, "test-event-network-type", "network")
317
+	volumeActions := eventActionsByIDAndType(c, events, "test-event-volume-type", "volume")
318
+
319
+	c.Assert(volumeActions[0], checker.Equals, "create")
320
+	c.Assert(networkActions[0], checker.Equals, "create")
321
+}
322
+
323
+func (s *DockerSuite) TestEventsFilterVolumeID(c *check.C) {
324
+	testRequires(c, DaemonIsLinux)
325
+
326
+	since := daemonTime(c).Unix()
327
+
328
+	dockerCmd(c, "volume", "create", "--name", "test-event-volume-id")
329
+	out, _ := dockerCmd(c, "events", "--filter", "volume=test-event-volume-id", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
330
+	events := strings.Split(strings.TrimSpace(out), "\n")
331
+	c.Assert(events, checker.HasLen, 1)
332
+
333
+	c.Assert(events[0], checker.Contains, "test-event-volume-id")
334
+	c.Assert(events[0], checker.Contains, "driver=local")
335
+}
336
+
337
+func (s *DockerSuite) TestEventsFilterNetworkID(c *check.C) {
338
+	testRequires(c, DaemonIsLinux)
339
+
340
+	since := daemonTime(c).Unix()
341
+
342
+	dockerCmd(c, "network", "create", "test-event-network-local")
343
+	out, _ := dockerCmd(c, "events", "--filter", "network=test-event-network-local", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
344
+	events := strings.Split(strings.TrimSpace(out), "\n")
345
+	c.Assert(events, checker.HasLen, 1)
346
+
347
+	c.Assert(events[0], checker.Contains, "test-event-network-local")
348
+	c.Assert(events[0], checker.Contains, "type=bridge")
349
+}
... ...
@@ -23,15 +23,11 @@ func (s *DockerSuite) TestPause(c *check.C) {
23 23
 	dockerCmd(c, "unpause", name)
24 24
 
25 25
 	out, _ := dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
26
-	events := strings.Split(out, "\n")
27
-	c.Assert(len(events) > 1, checker.Equals, true)
28
-
29
-	pauseEvent := strings.Fields(events[len(events)-3])
30
-	unpauseEvent := strings.Fields(events[len(events)-2])
31
-
32
-	c.Assert(pauseEvent[len(pauseEvent)-1], checker.Equals, "pause")
33
-	c.Assert(unpauseEvent[len(unpauseEvent)-1], checker.Equals, "unpause")
26
+	events := strings.Split(strings.TrimSpace(out), "\n")
27
+	actions := eventActionsByIDAndType(c, events, name, "container")
34 28
 
29
+	c.Assert(actions[len(actions)-2], checker.Equals, "pause")
30
+	c.Assert(actions[len(actions)-1], checker.Equals, "unpause")
35 31
 }
36 32
 
37 33
 func (s *DockerSuite) TestPauseMultipleContainers(c *check.C) {
... ...
@@ -53,21 +49,12 @@ func (s *DockerSuite) TestPauseMultipleContainers(c *check.C) {
53 53
 	dockerCmd(c, append([]string{"unpause"}, containers...)...)
54 54
 
55 55
 	out, _ := dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
56
-	events := strings.Split(out, "\n")
57
-	c.Assert(len(events) > len(containers)*3-2, checker.Equals, true)
56
+	events := strings.Split(strings.TrimSpace(out), "\n")
58 57
 
59
-	pauseEvents := make([][]string, len(containers))
60
-	unpauseEvents := make([][]string, len(containers))
61
-	for i := range containers {
62
-		pauseEvents[i] = strings.Fields(events[len(events)-len(containers)*2-1+i])
63
-		unpauseEvents[i] = strings.Fields(events[len(events)-len(containers)-1+i])
64
-	}
58
+	for _, name := range containers {
59
+		actions := eventActionsByIDAndType(c, events, name, "container")
65 60
 
66
-	for _, pauseEvent := range pauseEvents {
67
-		c.Assert(pauseEvent[len(pauseEvent)-1], checker.Equals, "pause")
68
-	}
69
-	for _, unpauseEvent := range unpauseEvents {
70
-		c.Assert(unpauseEvent[len(unpauseEvent)-1], checker.Equals, "unpause")
61
+		c.Assert(actions[len(actions)-2], checker.Equals, "pause")
62
+		c.Assert(actions[len(actions)-1], checker.Equals, "unpause")
71 63
 	}
72
-
73 64
 }
... ...
@@ -6,27 +6,50 @@ import (
6 6
 	"fmt"
7 7
 	"io"
8 8
 	"os/exec"
9
+	"regexp"
9 10
 	"strconv"
11
+	"strings"
10 12
 
13
+	"github.com/Sirupsen/logrus"
14
+	"github.com/docker/docker/pkg/integration/checker"
11 15
 	"github.com/go-check/check"
12 16
 )
13 17
 
18
+var (
19
+	reTimestamp  = `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{9}(:?(:?(:?-|\+)\d{2}:\d{2})|Z)`
20
+	reEventType  = `(?P<eventType>\w+)`
21
+	reAction     = `(?P<action>\w+)`
22
+	reID         = `(?P<id>[^\s]+)`
23
+	reAttributes = `(\s\((?P<attributes>[^\)]+)\))?`
24
+	reString     = fmt.Sprintf(`\A%s\s%s\s%s\s%s%s\z`, reTimestamp, reEventType, reAction, reID, reAttributes)
25
+
26
+	// eventCliRegexp is a regular expression that matches all possible event outputs in the cli
27
+	eventCliRegexp = regexp.MustCompile(reString)
28
+)
29
+
14 30
 // eventMatcher is a function that tries to match an event input.
15
-type eventMatcher func(text string)
31
+type eventMatcher func(text string) bool
16 32
 
17 33
 // eventObserver runs an events commands and observes its output.
18 34
 type eventObserver struct {
19
-	buffer  *bytes.Buffer
20
-	command *exec.Cmd
21
-	stdout  io.Reader
35
+	buffer             *bytes.Buffer
36
+	command            *exec.Cmd
37
+	scanner            *bufio.Scanner
38
+	startTime          string
39
+	disconnectionError error
22 40
 }
23 41
 
24 42
 // newEventObserver creates the observer and initializes the command
25 43
 // without running it. Users must call `eventObserver.Start` to start the command.
26 44
 func newEventObserver(c *check.C, args ...string) (*eventObserver, error) {
27 45
 	since := daemonTime(c).Unix()
46
+	return newEventObserverWithBacklog(c, since, args...)
47
+}
28 48
 
29
-	cmdArgs := []string{"events", "--since", strconv.FormatInt(since, 10)}
49
+// newEventObserverWithBacklog creates a new observer changing the start time of the backlog to return.
50
+func newEventObserverWithBacklog(c *check.C, since int64, args ...string) (*eventObserver, error) {
51
+	startTime := strconv.FormatInt(since, 10)
52
+	cmdArgs := []string{"events", "--since", startTime}
30 53
 	if len(args) > 0 {
31 54
 		cmdArgs = append(cmdArgs, args...)
32 55
 	}
... ...
@@ -37,9 +60,10 @@ func newEventObserver(c *check.C, args ...string) (*eventObserver, error) {
37 37
 	}
38 38
 
39 39
 	return &eventObserver{
40
-		buffer:  new(bytes.Buffer),
41
-		command: eventsCmd,
42
-		stdout:  stdout,
40
+		buffer:    new(bytes.Buffer),
41
+		command:   eventsCmd,
42
+		scanner:   bufio.NewScanner(stdout),
43
+		startTime: startTime,
43 44
 	}, nil
44 45
 }
45 46
 
... ...
@@ -51,28 +75,144 @@ func (e *eventObserver) Start() error {
51 51
 // Stop stops the events command.
52 52
 func (e *eventObserver) Stop() {
53 53
 	e.command.Process.Kill()
54
+	e.command.Process.Release()
54 55
 }
55 56
 
56 57
 // Match tries to match the events output with a given matcher.
57 58
 func (e *eventObserver) Match(match eventMatcher) {
58
-	scanner := bufio.NewScanner(e.stdout)
59
-
60
-	for scanner.Scan() {
61
-		text := scanner.Text()
59
+	for e.scanner.Scan() {
60
+		text := e.scanner.Text()
62 61
 		e.buffer.WriteString(text)
63 62
 		e.buffer.WriteString("\n")
64 63
 
65 64
 		match(text)
66 65
 	}
66
+
67
+	err := e.scanner.Err()
68
+	if err == nil {
69
+		err = io.EOF
70
+	}
71
+
72
+	logrus.Debug("EventObserver scanner loop finished: %v", err)
73
+	e.disconnectionError = err
74
+}
75
+
76
+func (e *eventObserver) CheckEventError(c *check.C, id, event string, match eventMatcher) {
77
+	var foundEvent bool
78
+	scannerOut := e.buffer.String()
79
+
80
+	if e.disconnectionError != nil {
81
+		until := strconv.FormatInt(daemonTime(c).Unix(), 10)
82
+		out, _ := dockerCmd(c, "events", "--since", e.startTime, "--until", until)
83
+		events := strings.Split(strings.TrimSpace(out), "\n")
84
+		for _, e := range events {
85
+			if match(e) {
86
+				foundEvent = true
87
+				break
88
+			}
89
+		}
90
+		scannerOut = out
91
+	}
92
+	if !foundEvent {
93
+		c.Fatalf("failed to observe event `%s` for %s. Disconnection error: %v\nout:\n%v", event, id, e.disconnectionError, scannerOut)
94
+	}
67 95
 }
68 96
 
69
-// TimeoutError generates an error for a given containerID and event type.
70
-// It attaches the events command output to the error.
71
-func (e *eventObserver) TimeoutError(id, event string) error {
72
-	return fmt.Errorf("failed to observe event `%s` for %s\n%v", event, id, e.output())
97
+// matchEventLine matches a text with the event regular expression.
98
+// It returns the action and true if the regular expression matches with the given id and event type.
99
+// It returns an empty string and false if there is no match.
100
+func matchEventLine(id, eventType string, actions map[string]chan bool) eventMatcher {
101
+	return func(text string) bool {
102
+		matches := parseEventText(text)
103
+		if len(matches) == 0 {
104
+			return false
105
+		}
106
+
107
+		if matchIDAndEventType(matches, id, eventType) {
108
+			if ch, ok := actions[matches["action"]]; ok {
109
+				close(ch)
110
+				return true
111
+			}
112
+		}
113
+		return false
114
+	}
73 115
 }
74 116
 
75
-// output returns the events command output read until now by the Match goroutine.
76
-func (e *eventObserver) output() string {
77
-	return e.buffer.String()
117
+// parseEventText parses a line of events coming from the cli and returns
118
+// the matchers in a map.
119
+func parseEventText(text string) map[string]string {
120
+	matches := eventCliRegexp.FindAllStringSubmatch(text, -1)
121
+	md := map[string]string{}
122
+	if len(matches) == 0 {
123
+		return md
124
+	}
125
+
126
+	names := eventCliRegexp.SubexpNames()
127
+	for i, n := range matches[0] {
128
+		md[names[i]] = n
129
+	}
130
+	return md
131
+}
132
+
133
+// parseEventAction parses an event text and returns the action.
134
+// It fails if the text is not in the event format.
135
+func parseEventAction(c *check.C, text string) string {
136
+	matches := parseEventText(text)
137
+	return matches["action"]
138
+}
139
+
140
+// eventActionsByIDAndType returns the actions for a given id and type.
141
+// It fails if the text is not in the event format.
142
+func eventActionsByIDAndType(c *check.C, events []string, id, eventType string) []string {
143
+	var filtered []string
144
+	for _, event := range events {
145
+		matches := parseEventText(event)
146
+		c.Assert(matches, checker.Not(checker.IsNil))
147
+		if matchIDAndEventType(matches, id, eventType) {
148
+			filtered = append(filtered, matches["action"])
149
+		}
150
+	}
151
+	return filtered
152
+}
153
+
154
+// matchIDAndEventType returns true if an event matches a given id and type.
155
+// It also resolves names in the event attributes if the id doesn't match.
156
+func matchIDAndEventType(matches map[string]string, id, eventType string) bool {
157
+	return matchEventID(matches, id) && matches["eventType"] == eventType
158
+}
159
+
160
+func matchEventID(matches map[string]string, id string) bool {
161
+	matchID := matches["id"] == id || strings.HasPrefix(matches["id"], id)
162
+	if !matchID && matches["attributes"] != "" {
163
+		// try matching a name in the attributes
164
+		attributes := map[string]string{}
165
+		for _, a := range strings.Split(matches["attributes"], ", ") {
166
+			kv := strings.Split(a, "=")
167
+			attributes[kv[0]] = kv[1]
168
+		}
169
+		matchID = attributes["name"] == id
170
+	}
171
+	return matchID
172
+}
173
+
174
+func parseEvents(c *check.C, out, match string) {
175
+	events := strings.Split(strings.TrimSpace(out), "\n")
176
+	for _, event := range events {
177
+		matches := parseEventText(event)
178
+		matched, err := regexp.MatchString(match, matches["action"])
179
+		c.Assert(err, checker.IsNil)
180
+		c.Assert(matched, checker.True, check.Commentf("Matcher: %s did not match %s", match, matches["action"]))
181
+	}
182
+}
183
+
184
+func parseEventsWithID(c *check.C, out, match, id string) {
185
+	events := strings.Split(strings.TrimSpace(out), "\n")
186
+	for _, event := range events {
187
+		matches := parseEventText(event)
188
+		c.Assert(matchEventID(matches, id), checker.True)
189
+
190
+		matched, err := regexp.MatchString(match, matches["action"])
191
+		c.Assert(err, checker.IsNil)
192
+		c.Assert(matched, checker.True, check.Commentf("Matcher: %s did not match %s", match, matches["action"]))
193
+	}
78 194
 }