Browse code

support cluster events

Signed-off-by: Dong Chen <dongluo.chen@docker.com>

Dong Chen authored on 2017/04/03 07:21:56
Showing 10 changed files
... ...
@@ -13,6 +13,12 @@ const (
13 13
 	PluginEventType = "plugin"
14 14
 	// VolumeEventType is the event type that volumes generate
15 15
 	VolumeEventType = "volume"
16
+	// ServiceEventType is the event type that services generate
17
+	ServiceEventType = "service"
18
+	// NodeEventType is the event type that nodes generate
19
+	NodeEventType = "node"
20
+	// SecretEventType is the event type that secrets generate
21
+	SecretEventType = "secret"
16 22
 )
17 23
 
18 24
 // Actor describes something that generates events,
... ...
@@ -36,6 +42,8 @@ type Message struct {
36 36
 	Type   string
37 37
 	Action string
38 38
 	Actor  Actor
39
+	// Engine events are local scope. Cluster events are swarm scope.
40
+	Scope string `json:"scope,omitempty"`
39 41
 
40 42
 	Time     int64 `json:"time,omitempty"`
41 43
 	TimeNano int64 `json:"timeNano,omitempty"`
... ...
@@ -1,6 +1,7 @@
1 1
 package main
2 2
 
3 3
 import (
4
+	"context"
4 5
 	"crypto/tls"
5 6
 	"fmt"
6 7
 	"os"
... ...
@@ -44,6 +45,7 @@ import (
44 44
 	"github.com/docker/docker/registry"
45 45
 	"github.com/docker/docker/runconfig"
46 46
 	"github.com/docker/go-connections/tlsconfig"
47
+	swarmapi "github.com/docker/swarmkit/api"
47 48
 	"github.com/spf13/pflag"
48 49
 )
49 50
 
... ...
@@ -227,6 +229,10 @@ func (cli *DaemonCli) start(opts daemonOptions) (err error) {
227 227
 
228 228
 	name, _ := os.Hostname()
229 229
 
230
+	// Use a buffered channel to pass changes from store watch API to daemon
231
+	// A buffer allows store watch API and daemon processing to not wait for each other
232
+	watchStream := make(chan *swarmapi.WatchMessage, 32)
233
+
230 234
 	c, err := cluster.New(cluster.Config{
231 235
 		Root:                   cli.Config.Root,
232 236
 		Name:                   name,
... ...
@@ -234,6 +240,7 @@ func (cli *DaemonCli) start(opts daemonOptions) (err error) {
234 234
 		NetworkSubnetsProvider: d,
235 235
 		DefaultAdvertiseAddr:   cli.Config.SwarmDefaultAdvertiseAddr,
236 236
 		RuntimeRoot:            cli.getSwarmRunRoot(),
237
+		WatchStream:            watchStream,
237 238
 	})
238 239
 	if err != nil {
239 240
 		logrus.Fatalf("Error creating cluster component: %v", err)
... ...
@@ -261,6 +268,11 @@ func (cli *DaemonCli) start(opts daemonOptions) (err error) {
261 261
 
262 262
 	initRouter(api, d, c)
263 263
 
264
+	// process cluster change notifications
265
+	watchCtx, cancel := context.WithCancel(context.Background())
266
+	defer cancel()
267
+	go d.ProcessClusterNotifications(watchCtx, watchStream)
268
+
264 269
 	cli.setupConfigReloadTrap()
265 270
 
266 271
 	// The serve API routine never exits unless an error occurs
... ...
@@ -105,6 +105,9 @@ type Config struct {
105 105
 
106 106
 	// path to store runtime state, such as the swarm control socket
107 107
 	RuntimeRoot string
108
+
109
+	// WatchStream is a channel to pass watch API notifications to daemon
110
+	WatchStream chan *swarmapi.WatchMessage
108 111
 }
109 112
 
110 113
 // Cluster provides capabilities to participate in a cluster as a worker or a
... ...
@@ -118,6 +121,7 @@ type Cluster struct {
118 118
 	config       Config
119 119
 	configEvent  chan lncluster.ConfigEventType // todo: make this array and goroutine safe
120 120
 	attachers    map[string]*attacher
121
+	watchStream  chan *swarmapi.WatchMessage
121 122
 }
122 123
 
123 124
 // attacher manages the in-memory attachment state of a container
... ...
@@ -151,6 +155,7 @@ func New(config Config) (*Cluster, error) {
151 151
 		configEvent: make(chan lncluster.ConfigEventType, 10),
152 152
 		runtimeRoot: config.RuntimeRoot,
153 153
 		attachers:   make(map[string]*attacher),
154
+		watchStream: config.WatchStream,
154 155
 	}
155 156
 	return c, nil
156 157
 }
... ...
@@ -159,6 +159,8 @@ func (n *nodeRunner) handleControlSocketChange(ctx context.Context, node *swarmn
159 159
 			} else {
160 160
 				n.controlClient = swarmapi.NewControlClient(conn)
161 161
 				n.logsClient = swarmapi.NewLogsClient(conn)
162
+				// push store changes to daemon
163
+				go n.watchClusterEvents(ctx, conn)
162 164
 			}
163 165
 		}
164 166
 		n.grpcConn = conn
... ...
@@ -167,6 +169,48 @@ func (n *nodeRunner) handleControlSocketChange(ctx context.Context, node *swarmn
167 167
 	}
168 168
 }
169 169
 
170
+func (n *nodeRunner) watchClusterEvents(ctx context.Context, conn *grpc.ClientConn) {
171
+	client := swarmapi.NewWatchClient(conn)
172
+	watch, err := client.Watch(ctx, &swarmapi.WatchRequest{
173
+		Entries: []*swarmapi.WatchRequest_WatchEntry{
174
+			{
175
+				Kind:   "node",
176
+				Action: swarmapi.WatchActionKindCreate | swarmapi.WatchActionKindUpdate | swarmapi.WatchActionKindRemove,
177
+			},
178
+			{
179
+				Kind:   "service",
180
+				Action: swarmapi.WatchActionKindCreate | swarmapi.WatchActionKindUpdate | swarmapi.WatchActionKindRemove,
181
+			},
182
+			{
183
+				Kind:   "network",
184
+				Action: swarmapi.WatchActionKindCreate | swarmapi.WatchActionKindUpdate | swarmapi.WatchActionKindRemove,
185
+			},
186
+			{
187
+				Kind:   "secret",
188
+				Action: swarmapi.WatchActionKindCreate | swarmapi.WatchActionKindUpdate | swarmapi.WatchActionKindRemove,
189
+			},
190
+		},
191
+		IncludeOldObject: true,
192
+	})
193
+	if err != nil {
194
+		logrus.WithError(err).Error("failed to watch cluster store")
195
+		return
196
+	}
197
+	for {
198
+		msg, err := watch.Recv()
199
+		if err != nil {
200
+			// store watch is broken
201
+			logrus.WithError(err).Error("failed to receive changes from store watch API")
202
+			return
203
+		}
204
+		select {
205
+		case <-ctx.Done():
206
+			return
207
+		case n.cluster.watchStream <- msg:
208
+		}
209
+	}
210
+}
211
+
170 212
 func (n *nodeRunner) handleReadyEvent(ctx context.Context, node *swarmnode.Node, ready chan struct{}) {
171 213
 	select {
172 214
 	case <-node.Ready():
... ...
@@ -1,14 +1,27 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
+	"context"
5
+	"strconv"
4 6
 	"strings"
5 7
 	"time"
6 8
 
9
+	"github.com/Sirupsen/logrus"
7 10
 	"github.com/docker/docker/api/types/events"
8 11
 	"github.com/docker/docker/api/types/filters"
9 12
 	"github.com/docker/docker/container"
10 13
 	daemonevents "github.com/docker/docker/daemon/events"
11 14
 	"github.com/docker/libnetwork"
15
+	swarmapi "github.com/docker/swarmkit/api"
16
+	gogotypes "github.com/gogo/protobuf/types"
17
+)
18
+
19
+var (
20
+	clusterEventAction = map[swarmapi.WatchActionKind]string{
21
+		swarmapi.WatchActionKindCreate: "create",
22
+		swarmapi.WatchActionKindUpdate: "update",
23
+		swarmapi.WatchActionKindRemove: "remove",
24
+	}
12 25
 )
13 26
 
14 27
 // LogContainerEvent generates an event related to a container with only the default attributes.
... ...
@@ -130,3 +143,180 @@ func copyAttributes(attributes, labels map[string]string) {
130 130
 		attributes[k] = v
131 131
 	}
132 132
 }
133
+
134
+// ProcessClusterNotifications gets changes from store and add them to event list
135
+func (daemon *Daemon) ProcessClusterNotifications(ctx context.Context, watchStream chan *swarmapi.WatchMessage) {
136
+	for {
137
+		select {
138
+		case <-ctx.Done():
139
+			return
140
+		case message, ok := <-watchStream:
141
+			if !ok {
142
+				logrus.Debug("cluster event channel has stopped")
143
+				return
144
+			}
145
+			daemon.generateClusterEvent(message)
146
+		}
147
+	}
148
+}
149
+
150
+func (daemon *Daemon) generateClusterEvent(msg *swarmapi.WatchMessage) {
151
+	for _, event := range msg.Events {
152
+		if event.Object == nil {
153
+			logrus.Errorf("event without object: %v", event)
154
+			continue
155
+		}
156
+		switch v := event.Object.GetObject().(type) {
157
+		case *swarmapi.Object_Node:
158
+			daemon.logNodeEvent(event.Action, v.Node, event.OldObject.GetNode())
159
+		case *swarmapi.Object_Service:
160
+			daemon.logServiceEvent(event.Action, v.Service, event.OldObject.GetService())
161
+		case *swarmapi.Object_Network:
162
+			daemon.logNetworkEvent(event.Action, v.Network, event.OldObject.GetNetwork())
163
+		case *swarmapi.Object_Secret:
164
+			daemon.logSecretEvent(event.Action, v.Secret, event.OldObject.GetSecret())
165
+		default:
166
+			logrus.Warnf("unrecognized event: %v", event)
167
+		}
168
+	}
169
+}
170
+
171
+func (daemon *Daemon) logNetworkEvent(action swarmapi.WatchActionKind, net *swarmapi.Network, oldNet *swarmapi.Network) {
172
+	attributes := map[string]string{
173
+		"name": net.Spec.Annotations.Name,
174
+	}
175
+	eventTime := eventTimestamp(net.Meta, action)
176
+	daemon.logClusterEvent(action, net.ID, "network", attributes, eventTime)
177
+}
178
+
179
+func (daemon *Daemon) logSecretEvent(action swarmapi.WatchActionKind, secret *swarmapi.Secret, oldSecret *swarmapi.Secret) {
180
+	attributes := map[string]string{
181
+		"name": secret.Spec.Annotations.Name,
182
+	}
183
+	eventTime := eventTimestamp(secret.Meta, action)
184
+	daemon.logClusterEvent(action, secret.ID, "secret", attributes, eventTime)
185
+}
186
+
187
+func (daemon *Daemon) logNodeEvent(action swarmapi.WatchActionKind, node *swarmapi.Node, oldNode *swarmapi.Node) {
188
+	name := node.Spec.Annotations.Name
189
+	if name == "" && node.Description != nil {
190
+		name = node.Description.Hostname
191
+	}
192
+	attributes := map[string]string{
193
+		"name": name,
194
+	}
195
+	eventTime := eventTimestamp(node.Meta, action)
196
+	// In an update event, display the changes in attributes
197
+	if action == swarmapi.WatchActionKindUpdate && oldNode != nil {
198
+		if node.Spec.Availability != oldNode.Spec.Availability {
199
+			attributes["availability.old"] = strings.ToLower(oldNode.Spec.Availability.String())
200
+			attributes["availability.new"] = strings.ToLower(node.Spec.Availability.String())
201
+		}
202
+		if node.Role != oldNode.Role {
203
+			attributes["role.old"] = strings.ToLower(oldNode.Role.String())
204
+			attributes["role.new"] = strings.ToLower(node.Role.String())
205
+		}
206
+		if node.Status.State != oldNode.Status.State {
207
+			attributes["state.old"] = strings.ToLower(oldNode.Status.State.String())
208
+			attributes["state.new"] = strings.ToLower(node.Status.State.String())
209
+		}
210
+		// This handles change within manager role
211
+		if node.ManagerStatus != nil && oldNode.ManagerStatus != nil {
212
+			// leader change
213
+			if node.ManagerStatus.Leader != oldNode.ManagerStatus.Leader {
214
+				if node.ManagerStatus.Leader {
215
+					attributes["leader.old"] = "false"
216
+					attributes["leader.new"] = "true"
217
+				} else {
218
+					attributes["leader.old"] = "true"
219
+					attributes["leader.new"] = "false"
220
+				}
221
+			}
222
+			if node.ManagerStatus.Reachability != oldNode.ManagerStatus.Reachability {
223
+				attributes["reachability.old"] = strings.ToLower(oldNode.ManagerStatus.Reachability.String())
224
+				attributes["reachability.new"] = strings.ToLower(node.ManagerStatus.Reachability.String())
225
+			}
226
+		}
227
+	}
228
+
229
+	daemon.logClusterEvent(action, node.ID, "node", attributes, eventTime)
230
+}
231
+
232
+func (daemon *Daemon) logServiceEvent(action swarmapi.WatchActionKind, service *swarmapi.Service, oldService *swarmapi.Service) {
233
+	attributes := map[string]string{
234
+		"name": service.Spec.Annotations.Name,
235
+	}
236
+	eventTime := eventTimestamp(service.Meta, action)
237
+
238
+	if action == swarmapi.WatchActionKindUpdate && oldService != nil {
239
+		// check image
240
+		if x, ok := service.Spec.Task.GetRuntime().(*swarmapi.TaskSpec_Container); ok {
241
+			containerSpec := x.Container
242
+			if y, ok := oldService.Spec.Task.GetRuntime().(*swarmapi.TaskSpec_Container); ok {
243
+				oldContainerSpec := y.Container
244
+				if containerSpec.Image != oldContainerSpec.Image {
245
+					attributes["image.old"] = oldContainerSpec.Image
246
+					attributes["image.new"] = containerSpec.Image
247
+				}
248
+			} else {
249
+				// This should not happen.
250
+				logrus.Errorf("service %s runtime changed from %T to %T", service.Spec.Annotations.Name, oldService.Spec.Task.GetRuntime(), service.Spec.Task.GetRuntime())
251
+			}
252
+		}
253
+		// check replicated count change
254
+		if x, ok := service.Spec.GetMode().(*swarmapi.ServiceSpec_Replicated); ok {
255
+			replicas := x.Replicated.Replicas
256
+			if y, ok := oldService.Spec.GetMode().(*swarmapi.ServiceSpec_Replicated); ok {
257
+				oldReplicas := y.Replicated.Replicas
258
+				if replicas != oldReplicas {
259
+					attributes["replicas.old"] = strconv.FormatUint(oldReplicas, 10)
260
+					attributes["replicas.new"] = strconv.FormatUint(replicas, 10)
261
+				}
262
+			} else {
263
+				// This should not happen.
264
+				logrus.Errorf("service %s mode changed from %T to %T", service.Spec.Annotations.Name, oldService.Spec.GetMode(), service.Spec.GetMode())
265
+			}
266
+		}
267
+		if service.UpdateStatus != nil {
268
+			if oldService.UpdateStatus == nil {
269
+				attributes["updatestate.new"] = strings.ToLower(service.UpdateStatus.State.String())
270
+			} else if service.UpdateStatus.State != oldService.UpdateStatus.State {
271
+				attributes["updatestate.old"] = strings.ToLower(oldService.UpdateStatus.State.String())
272
+				attributes["updatestate.new"] = strings.ToLower(service.UpdateStatus.State.String())
273
+			}
274
+		}
275
+	}
276
+	daemon.logClusterEvent(action, service.ID, "service", attributes, eventTime)
277
+}
278
+
279
+func (daemon *Daemon) logClusterEvent(action swarmapi.WatchActionKind, id, eventType string, attributes map[string]string, eventTime time.Time) {
280
+	actor := events.Actor{
281
+		ID:         id,
282
+		Attributes: attributes,
283
+	}
284
+
285
+	jm := events.Message{
286
+		Action:   clusterEventAction[action],
287
+		Type:     eventType,
288
+		Actor:    actor,
289
+		Scope:    "swarm",
290
+		Time:     eventTime.UTC().Unix(),
291
+		TimeNano: eventTime.UTC().UnixNano(),
292
+	}
293
+	daemon.EventsService.PublishMessage(jm)
294
+}
295
+
296
+func eventTimestamp(meta swarmapi.Meta, action swarmapi.WatchActionKind) time.Time {
297
+	var eventTime time.Time
298
+	switch action {
299
+	case swarmapi.WatchActionKindCreate:
300
+		eventTime, _ = gogotypes.TimestampFromProto(meta.CreatedAt)
301
+	case swarmapi.WatchActionKindUpdate:
302
+		eventTime, _ = gogotypes.TimestampFromProto(meta.UpdatedAt)
303
+	case swarmapi.WatchActionKindRemove:
304
+		// There is no timestamp from store message for remove operations.
305
+		// Use current time.
306
+		eventTime = time.Now()
307
+	}
308
+	return eventTime
309
+}
... ...
@@ -9,7 +9,7 @@ import (
9 9
 )
10 10
 
11 11
 const (
12
-	eventsLimit = 64
12
+	eventsLimit = 256
13 13
 	bufferSize  = 1024
14 14
 )
15 15
 
... ...
@@ -78,15 +78,14 @@ func (e *Events) Evict(l chan interface{}) {
78 78
 	e.pub.Evict(l)
79 79
 }
80 80
 
81
-// Log broadcasts event to listeners. Each listener has 100 milliseconds to
82
-// receive the event or it will be skipped.
81
+// Log creates a local scope message and publishes it
83 82
 func (e *Events) Log(action, eventType string, actor eventtypes.Actor) {
84
-	eventsCounter.Inc()
85 83
 	now := time.Now().UTC()
86 84
 	jm := eventtypes.Message{
87 85
 		Action:   action,
88 86
 		Type:     eventType,
89 87
 		Actor:    actor,
88
+		Scope:    "local",
90 89
 		Time:     now.Unix(),
91 90
 		TimeNano: now.UnixNano(),
92 91
 	}
... ...
@@ -102,6 +101,14 @@ func (e *Events) Log(action, eventType string, actor eventtypes.Actor) {
102 102
 		jm.Status = action
103 103
 	}
104 104
 
105
+	e.PublishMessage(jm)
106
+}
107
+
108
+// PublishMessage broadcasts event to listeners. Each listener has 100 milliseconds to
109
+// receive the event or it will be skipped.
110
+func (e *Events) PublishMessage(jm eventtypes.Message) {
111
+	eventsCounter.Inc()
112
+
105 113
 	e.mu.Lock()
106 114
 	if len(e.events) == cap(e.events) {
107 115
 		// discard oldest event
... ...
@@ -139,17 +139,17 @@ func TestLogEvents(t *testing.T) {
139 139
 		t.Fatalf("First action is %s, must be action_16", first.Status)
140 140
 	}
141 141
 	last := current[len(current)-1]
142
-	if last.Status != "action_79" {
143
-		t.Fatalf("Last action is %s, must be action_79", last.Status)
142
+	if last.Status != "action_271" {
143
+		t.Fatalf("Last action is %s, must be action_271", last.Status)
144 144
 	}
145 145
 
146 146
 	firstC := msgs[0]
147
-	if firstC.Status != "action_80" {
148
-		t.Fatalf("First action is %s, must be action_80", firstC.Status)
147
+	if firstC.Status != "action_272" {
148
+		t.Fatalf("First action is %s, must be action_272", firstC.Status)
149 149
 	}
150 150
 	lastC := msgs[len(msgs)-1]
151
-	if lastC.Status != "action_89" {
152
-		t.Fatalf("Last action is %s, must be action_89", lastC.Status)
151
+	if lastC.Status != "action_281" {
152
+		t.Fatalf("Last action is %s, must be action_281", lastC.Status)
153 153
 	}
154 154
 }
155 155
 
... ...
@@ -20,6 +20,7 @@ func NewFilter(filter filters.Args) *Filter {
20 20
 func (ef *Filter) Include(ev events.Message) bool {
21 21
 	return ef.matchEvent(ev) &&
22 22
 		ef.filter.ExactMatch("type", ev.Type) &&
23
+		ef.matchScope(ev.Scope) &&
23 24
 		ef.matchDaemon(ev) &&
24 25
 		ef.matchContainer(ev) &&
25 26
 		ef.matchPlugin(ev) &&
... ...
@@ -47,6 +48,13 @@ func (ef *Filter) filterContains(field string, values map[string]struct{}) bool
47 47
 	return false
48 48
 }
49 49
 
50
+func (ef *Filter) matchScope(scope string) bool {
51
+	if !ef.filter.Include("scope") {
52
+		return true
53
+	}
54
+	return ef.filter.ExactMatch("scope", scope)
55
+}
56
+
50 57
 func (ef *Filter) matchLabels(attributes map[string]string) bool {
51 58
 	if !ef.filter.Include("label") {
52 59
 		return true
... ...
@@ -74,6 +82,18 @@ func (ef *Filter) matchNetwork(ev events.Message) bool {
74 74
 	return ef.fuzzyMatchName(ev, events.NetworkEventType)
75 75
 }
76 76
 
77
+func (ef *Filter) matchService(ev events.Message) bool {
78
+	return ef.fuzzyMatchName(ev, events.ServiceEventType)
79
+}
80
+
81
+func (ef *Filter) matchNode(ev events.Message) bool {
82
+	return ef.fuzzyMatchName(ev, events.NodeEventType)
83
+}
84
+
85
+func (ef *Filter) matchSecret(ev events.Message) bool {
86
+	return ef.fuzzyMatchName(ev, events.SecretEventType)
87
+}
88
+
77 89
 func (ef *Filter) fuzzyMatchName(ev events.Message, eventType string) bool {
78 90
 	return ef.filter.FuzzyMatch(eventType, ev.Actor.ID) ||
79 91
 		ef.filter.FuzzyMatch(eventType, ev.Actor.Attributes["name"])
... ...
@@ -119,7 +119,7 @@ func (s *DockerSuite) TestEventsLimit(c *check.C) {
119 119
 	out, _ := dockerCmd(c, "events", "--since=0", "--until", daemonUnixTime(c))
120 120
 	events := strings.Split(out, "\n")
121 121
 	nEvents := len(events) - 1
122
-	c.Assert(nEvents, checker.Equals, 64, check.Commentf("events should be limited to 64, but received %d", nEvents))
122
+	c.Assert(nEvents, checker.Equals, 256, check.Commentf("events should be limited to 256, but received %d", nEvents))
123 123
 }
124 124
 
125 125
 func (s *DockerSuite) TestEventsContainerEvents(c *check.C) {
... ...
@@ -2001,3 +2001,220 @@ func (s *DockerSwarmSuite) TestSwarmJoinLeave(c *check.C) {
2001 2001
 		c.Assert(err, checker.IsNil)
2002 2002
 	}
2003 2003
 }
2004
+
2005
+func (s *DockerSwarmSuite) TestSwarmClusterEventsSource(c *check.C) {
2006
+	d1 := s.AddDaemon(c, true, true)
2007
+	d2 := s.AddDaemon(c, true, true)
2008
+	d3 := s.AddDaemon(c, true, false)
2009
+
2010
+	// create a network
2011
+	out, err := d1.Cmd("network", "create", "--attachable", "-d", "overlay", "foo")
2012
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2013
+	networkID := strings.TrimSpace(out)
2014
+	c.Assert(networkID, checker.Not(checker.Equals), "")
2015
+
2016
+	until := daemonUnixTime(c)
2017
+	// d1 is a manager
2018
+	out, err = d1.Cmd("events", "--since=0", "--until", until, "-f scope=swarm")
2019
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2020
+	c.Assert(strings.TrimSpace(out), checker.Contains, "network create "+networkID)
2021
+
2022
+	// d2 is a manager
2023
+	out, err = d2.Cmd("events", "--since=0", "--until", until, "-f scope=swarm")
2024
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2025
+	c.Assert(strings.TrimSpace(out), checker.Contains, "network create "+networkID)
2026
+
2027
+	// d3 is a worker, not able to get cluster events
2028
+	out, err = d3.Cmd("events", "--since=0", "--until", until, "-f scope=swarm")
2029
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2030
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), "network create ")
2031
+}
2032
+
2033
+func (s *DockerSwarmSuite) TestSwarmClusterEventsScope(c *check.C) {
2034
+	d := s.AddDaemon(c, true, true)
2035
+
2036
+	// create a service
2037
+	out, err := d.Cmd("service", "create", "--name", "test", "--detach=false", "busybox", "top")
2038
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2039
+	serviceID := strings.Split(out, "\n")[0]
2040
+
2041
+	until := daemonUnixTime(c)
2042
+	// scope swarm filters cluster events
2043
+	out, err = d.Cmd("events", "--since=0", "--until", until, "-f scope=swarm")
2044
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2045
+	c.Assert(strings.TrimSpace(out), checker.Contains, "service create "+serviceID)
2046
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), "container create ")
2047
+
2048
+	// without scope all events are returned
2049
+	out, err = d.Cmd("events", "--since=0", "--until", until)
2050
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2051
+	c.Assert(strings.TrimSpace(out), checker.Contains, "service create "+serviceID)
2052
+	c.Assert(strings.TrimSpace(out), checker.Contains, "container create ")
2053
+
2054
+	// scope local only show non-cluster events
2055
+	out, err = d.Cmd("events", "--since=0", "--until", until, "-f scope=local")
2056
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2057
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Contains), "service create ")
2058
+	c.Assert(strings.TrimSpace(out), checker.Contains, "container create ")
2059
+}
2060
+
2061
+func (s *DockerSwarmSuite) TestSwarmClusterEventsType(c *check.C) {
2062
+	d := s.AddDaemon(c, true, true)
2063
+
2064
+	// create a service
2065
+	out, err := d.Cmd("service", "create", "--name", "test", "--detach=false", "busybox", "top")
2066
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2067
+	serviceID := strings.Split(out, "\n")[0]
2068
+
2069
+	// create a network
2070
+	out, err = d.Cmd("network", "create", "--attachable", "-d", "overlay", "foo")
2071
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2072
+	networkID := strings.TrimSpace(out)
2073
+	c.Assert(networkID, checker.Not(checker.Equals), "")
2074
+
2075
+	until := daemonUnixTime(c)
2076
+	// filter by service
2077
+	out, err = d.Cmd("events", "--since=0", "--until", until, "-f type=service")
2078
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2079
+	c.Assert(out, checker.Contains, "service create "+serviceID)
2080
+	c.Assert(out, checker.Not(checker.Contains), "network create")
2081
+
2082
+	// filter by network
2083
+	out, err = d.Cmd("events", "--since=0", "--until", until, "-f type=network")
2084
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2085
+	c.Assert(out, checker.Contains, "network create "+networkID)
2086
+	c.Assert(out, checker.Not(checker.Contains), "service create")
2087
+}
2088
+
2089
+func (s *DockerSwarmSuite) TestSwarmClusterEventsService(c *check.C) {
2090
+	d := s.AddDaemon(c, true, true)
2091
+
2092
+	// create a service
2093
+	out, err := d.Cmd("service", "create", "--name", "test", "--detach=false", "busybox", "top")
2094
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2095
+	serviceID := strings.Split(out, "\n")[0]
2096
+
2097
+	t1 := daemonUnixTime(c)
2098
+	// validate service create event
2099
+	out, err = d.Cmd("events", "--since=0", "--until", t1, "-f scope=swarm")
2100
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2101
+	c.Assert(out, checker.Contains, "service create "+serviceID)
2102
+
2103
+	out, err = d.Cmd("service", "update", "--force", "--detach=false", "test")
2104
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2105
+
2106
+	t2 := daemonUnixTime(c)
2107
+	out, err = d.Cmd("events", "--since", t1, "--until", t2, "-f scope=swarm")
2108
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2109
+	c.Assert(out, checker.Contains, "service update "+serviceID)
2110
+	c.Assert(out, checker.Contains, "updatestate.new=updating")
2111
+	c.Assert(out, checker.Contains, "updatestate.new=completed, updatestate.old=updating")
2112
+
2113
+	// scale service
2114
+	out, err = d.Cmd("service", "scale", "test=3")
2115
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2116
+
2117
+	t3 := daemonUnixTime(c)
2118
+	out, err = d.Cmd("events", "--since", t2, "--until", t3, "-f scope=swarm")
2119
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2120
+	c.Assert(out, checker.Contains, "service update "+serviceID)
2121
+	c.Assert(out, checker.Contains, "replicas.new=3, replicas.old=1")
2122
+
2123
+	// remove service
2124
+	out, err = d.Cmd("service", "rm", "test")
2125
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2126
+
2127
+	t4 := daemonUnixTime(c)
2128
+	out, err = d.Cmd("events", "--since", t3, "--until", t4, "-f scope=swarm")
2129
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2130
+	c.Assert(out, checker.Contains, "service remove "+serviceID)
2131
+}
2132
+
2133
+func (s *DockerSwarmSuite) TestSwarmClusterEventsNode(c *check.C) {
2134
+	d1 := s.AddDaemon(c, true, true)
2135
+	s.AddDaemon(c, true, true)
2136
+	d3 := s.AddDaemon(c, true, true)
2137
+
2138
+	d3ID := d3.NodeID
2139
+	t1 := daemonUnixTime(c)
2140
+	out, err := d1.Cmd("events", "--since=0", "--until", t1, "-f scope=swarm")
2141
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2142
+	c.Assert(out, checker.Contains, "node create "+d3ID)
2143
+
2144
+	out, err = d1.Cmd("node", "update", "--availability=pause", d3ID)
2145
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2146
+
2147
+	t2 := daemonUnixTime(c)
2148
+	// filter by type
2149
+	out, err = d1.Cmd("events", "--since", t1, "--until", t2, "-f type=node")
2150
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2151
+	c.Assert(out, checker.Contains, "node update "+d3ID)
2152
+	c.Assert(out, checker.Contains, "availability.new=pause, availability.old=active")
2153
+
2154
+	out, err = d1.Cmd("node", "demote", d3ID)
2155
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2156
+
2157
+	t3 := daemonUnixTime(c)
2158
+	// filter by type and scope
2159
+	out, err = d1.Cmd("events", "--since", t2, "--until", t3, "-f type=node", "-f scope=swarm")
2160
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2161
+	c.Assert(out, checker.Contains, "node update "+d3ID)
2162
+
2163
+	out, err = d1.Cmd("node", "rm", "-f", d3ID)
2164
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2165
+
2166
+	t4 := daemonUnixTime(c)
2167
+	// filter by type and scope
2168
+	out, err = d1.Cmd("events", "--since", t3, "--until", t4, "-f type=node", "-f scope=swarm")
2169
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2170
+	c.Assert(out, checker.Contains, "node remove "+d3ID)
2171
+}
2172
+
2173
+func (s *DockerSwarmSuite) TestSwarmClusterEventsNetwork(c *check.C) {
2174
+	d := s.AddDaemon(c, true, true)
2175
+
2176
+	// create a network
2177
+	out, err := d.Cmd("network", "create", "--attachable", "-d", "overlay", "foo")
2178
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2179
+	networkID := strings.TrimSpace(out)
2180
+
2181
+	t1 := daemonUnixTime(c)
2182
+	out, err = d.Cmd("events", "--since=0", "--until", t1, "-f scope=swarm")
2183
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2184
+	c.Assert(out, checker.Contains, "network create "+networkID)
2185
+
2186
+	// remove network
2187
+	out, err = d.Cmd("network", "rm", "foo")
2188
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2189
+
2190
+	t2 := daemonUnixTime(c)
2191
+	// filtered by network
2192
+	out, err = d.Cmd("events", "--since", t1, "--until", t2, "-f type=network")
2193
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2194
+	c.Assert(out, checker.Contains, "network remove "+networkID)
2195
+}
2196
+
2197
+func (s *DockerSwarmSuite) TestSwarmClusterEventsSecret(c *check.C) {
2198
+	d := s.AddDaemon(c, true, true)
2199
+
2200
+	testName := "test_secret"
2201
+	id := d.CreateSecret(c, swarm.SecretSpec{
2202
+		Annotations: swarm.Annotations{
2203
+			Name: testName,
2204
+		},
2205
+		Data: []byte("TESTINGDATA"),
2206
+	})
2207
+	c.Assert(id, checker.Not(checker.Equals), "", check.Commentf("secrets: %s", id))
2208
+
2209
+	t1 := daemonUnixTime(c)
2210
+	out, err := d.Cmd("events", "--since=0", "--until", t1, "-f scope=swarm")
2211
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2212
+	c.Assert(out, checker.Contains, "secret create "+id)
2213
+
2214
+	d.DeleteSecret(c, id)
2215
+	t2 := daemonUnixTime(c)
2216
+	// filtered by secret
2217
+	out, err = d.Cmd("events", "--since", t1, "--until", t2, "-f type=secret")
2218
+	c.Assert(err, checker.IsNil, check.Commentf(out))
2219
+	c.Assert(out, checker.Contains, "secret remove "+id)
2220
+}