1package daemon // import "github.com/docker/docker/daemon"
2
3import (
4	"context"
5	"strconv"
6	"strings"
7	"time"
8
9	"github.com/docker/docker/api/types/events"
10	"github.com/docker/docker/api/types/filters"
11	"github.com/docker/docker/container"
12	daemonevents "github.com/docker/docker/daemon/events"
13	"github.com/docker/libnetwork"
14	swarmapi "github.com/docker/swarmkit/api"
15	gogotypes "github.com/gogo/protobuf/types"
16	"github.com/sirupsen/logrus"
17)
18
19var (
20	clusterEventAction = map[swarmapi.WatchActionKind]string{
21		swarmapi.WatchActionKindCreate: "create",
22		swarmapi.WatchActionKindUpdate: "update",
23		swarmapi.WatchActionKindRemove: "remove",
24	}
25)
26
27// LogContainerEvent generates an event related to a container with only the default attributes.
28func (daemon *Daemon) LogContainerEvent(container *container.Container, action string) {
29	daemon.LogContainerEventWithAttributes(container, action, map[string]string{})
30}
31
32// LogContainerEventWithAttributes generates an event related to a container with specific given attributes.
33func (daemon *Daemon) LogContainerEventWithAttributes(container *container.Container, action string, attributes map[string]string) {
34	copyAttributes(attributes, container.Config.Labels)
35	if container.Config.Image != "" {
36		attributes["image"] = container.Config.Image
37	}
38	attributes["name"] = strings.TrimLeft(container.Name, "/")
39
40	actor := events.Actor{
41		ID:         container.ID,
42		Attributes: attributes,
43	}
44	daemon.EventsService.Log(action, events.ContainerEventType, actor)
45}
46
47// LogPluginEvent generates an event related to a plugin with only the default attributes.
48func (daemon *Daemon) LogPluginEvent(pluginID, refName, action string) {
49	daemon.LogPluginEventWithAttributes(pluginID, refName, action, map[string]string{})
50}
51
52// LogPluginEventWithAttributes generates an event related to a plugin with specific given attributes.
53func (daemon *Daemon) LogPluginEventWithAttributes(pluginID, refName, action string, attributes map[string]string) {
54	attributes["name"] = refName
55	actor := events.Actor{
56		ID:         pluginID,
57		Attributes: attributes,
58	}
59	daemon.EventsService.Log(action, events.PluginEventType, actor)
60}
61
62// LogVolumeEvent generates an event related to a volume.
63func (daemon *Daemon) LogVolumeEvent(volumeID, action string, attributes map[string]string) {
64	actor := events.Actor{
65		ID:         volumeID,
66		Attributes: attributes,
67	}
68	daemon.EventsService.Log(action, events.VolumeEventType, actor)
69}
70
71// LogNetworkEvent generates an event related to a network with only the default attributes.
72func (daemon *Daemon) LogNetworkEvent(nw libnetwork.Network, action string) {
73	daemon.LogNetworkEventWithAttributes(nw, action, map[string]string{})
74}
75
76// LogNetworkEventWithAttributes generates an event related to a network with specific given attributes.
77func (daemon *Daemon) LogNetworkEventWithAttributes(nw libnetwork.Network, action string, attributes map[string]string) {
78	attributes["name"] = nw.Name()
79	attributes["type"] = nw.Type()
80	actor := events.Actor{
81		ID:         nw.ID(),
82		Attributes: attributes,
83	}
84	daemon.EventsService.Log(action, events.NetworkEventType, actor)
85}
86
87// LogDaemonEventWithAttributes generates an event related to the daemon itself with specific given attributes.
88func (daemon *Daemon) LogDaemonEventWithAttributes(action string, attributes map[string]string) {
89	if daemon.EventsService != nil {
90		if info, err := daemon.SystemInfo(); err == nil && info.Name != "" {
91			attributes["name"] = info.Name
92		}
93		actor := events.Actor{
94			ID:         daemon.ID,
95			Attributes: attributes,
96		}
97		daemon.EventsService.Log(action, events.DaemonEventType, actor)
98	}
99}
100
101// SubscribeToEvents returns the currently record of events, a channel to stream new events from, and a function to cancel the stream of events.
102func (daemon *Daemon) SubscribeToEvents(since, until time.Time, filter filters.Args) ([]events.Message, chan interface{}) {
103	ef := daemonevents.NewFilter(filter)
104	return daemon.EventsService.SubscribeTopic(since, until, ef)
105}
106
107// UnsubscribeFromEvents stops the event subscription for a client by closing the
108// channel where the daemon sends events to.
109func (daemon *Daemon) UnsubscribeFromEvents(listener chan interface{}) {
110	daemon.EventsService.Evict(listener)
111}
112
113// copyAttributes guarantees that labels are not mutated by event triggers.
114func copyAttributes(attributes, labels map[string]string) {
115	if labels == nil {
116		return
117	}
118	for k, v := range labels {
119		attributes[k] = v
120	}
121}
122
123// ProcessClusterNotifications gets changes from store and add them to event list
124func (daemon *Daemon) ProcessClusterNotifications(ctx context.Context, watchStream chan *swarmapi.WatchMessage) {
125	for {
126		select {
127		case <-ctx.Done():
128			return
129		case message, ok := <-watchStream:
130			if !ok {
131				logrus.Debug("cluster event channel has stopped")
132				return
133			}
134			daemon.generateClusterEvent(message)
135		}
136	}
137}
138
139func (daemon *Daemon) generateClusterEvent(msg *swarmapi.WatchMessage) {
140	for _, event := range msg.Events {
141		if event.Object == nil {
142			logrus.Errorf("event without object: %v", event)
143			continue
144		}
145		switch v := event.Object.GetObject().(type) {
146		case *swarmapi.Object_Node:
147			daemon.logNodeEvent(event.Action, v.Node, event.OldObject.GetNode())
148		case *swarmapi.Object_Service:
149			daemon.logServiceEvent(event.Action, v.Service, event.OldObject.GetService())
150		case *swarmapi.Object_Network:
151			daemon.logNetworkEvent(event.Action, v.Network, event.OldObject.GetNetwork())
152		case *swarmapi.Object_Secret:
153			daemon.logSecretEvent(event.Action, v.Secret, event.OldObject.GetSecret())
154		case *swarmapi.Object_Config:
155			daemon.logConfigEvent(event.Action, v.Config, event.OldObject.GetConfig())
156		default:
157			logrus.Warnf("unrecognized event: %v", event)
158		}
159	}
160}
161
162func (daemon *Daemon) logNetworkEvent(action swarmapi.WatchActionKind, net *swarmapi.Network, oldNet *swarmapi.Network) {
163	attributes := map[string]string{
164		"name": net.Spec.Annotations.Name,
165	}
166	eventTime := eventTimestamp(net.Meta, action)
167	daemon.logClusterEvent(action, net.ID, "network", attributes, eventTime)
168}
169
170func (daemon *Daemon) logSecretEvent(action swarmapi.WatchActionKind, secret *swarmapi.Secret, oldSecret *swarmapi.Secret) {
171	attributes := map[string]string{
172		"name": secret.Spec.Annotations.Name,
173	}
174	eventTime := eventTimestamp(secret.Meta, action)
175	daemon.logClusterEvent(action, secret.ID, "secret", attributes, eventTime)
176}
177
178func (daemon *Daemon) logConfigEvent(action swarmapi.WatchActionKind, config *swarmapi.Config, oldConfig *swarmapi.Config) {
179	attributes := map[string]string{
180		"name": config.Spec.Annotations.Name,
181	}
182	eventTime := eventTimestamp(config.Meta, action)
183	daemon.logClusterEvent(action, config.ID, "config", attributes, eventTime)
184}
185
186func (daemon *Daemon) logNodeEvent(action swarmapi.WatchActionKind, node *swarmapi.Node, oldNode *swarmapi.Node) {
187	name := node.Spec.Annotations.Name
188	if name == "" && node.Description != nil {
189		name = node.Description.Hostname
190	}
191	attributes := map[string]string{
192		"name": name,
193	}
194	eventTime := eventTimestamp(node.Meta, action)
195	// In an update event, display the changes in attributes
196	if action == swarmapi.WatchActionKindUpdate && oldNode != nil {
197		if node.Spec.Availability != oldNode.Spec.Availability {
198			attributes["availability.old"] = strings.ToLower(oldNode.Spec.Availability.String())
199			attributes["availability.new"] = strings.ToLower(node.Spec.Availability.String())
200		}
201		if node.Role != oldNode.Role {
202			attributes["role.old"] = strings.ToLower(oldNode.Role.String())
203			attributes["role.new"] = strings.ToLower(node.Role.String())
204		}
205		if node.Status.State != oldNode.Status.State {
206			attributes["state.old"] = strings.ToLower(oldNode.Status.State.String())
207			attributes["state.new"] = strings.ToLower(node.Status.State.String())
208		}
209		// This handles change within manager role
210		if node.ManagerStatus != nil && oldNode.ManagerStatus != nil {
211			// leader change
212			if node.ManagerStatus.Leader != oldNode.ManagerStatus.Leader {
213				if node.ManagerStatus.Leader {
214					attributes["leader.old"] = "false"
215					attributes["leader.new"] = "true"
216				} else {
217					attributes["leader.old"] = "true"
218					attributes["leader.new"] = "false"
219				}
220			}
221			if node.ManagerStatus.Reachability != oldNode.ManagerStatus.Reachability {
222				attributes["reachability.old"] = strings.ToLower(oldNode.ManagerStatus.Reachability.String())
223				attributes["reachability.new"] = strings.ToLower(node.ManagerStatus.Reachability.String())
224			}
225		}
226	}
227
228	daemon.logClusterEvent(action, node.ID, "node", attributes, eventTime)
229}
230
231func (daemon *Daemon) logServiceEvent(action swarmapi.WatchActionKind, service *swarmapi.Service, oldService *swarmapi.Service) {
232	attributes := map[string]string{
233		"name": service.Spec.Annotations.Name,
234	}
235	eventTime := eventTimestamp(service.Meta, action)
236
237	if action == swarmapi.WatchActionKindUpdate && oldService != nil {
238		// check image
239		if x, ok := service.Spec.Task.GetRuntime().(*swarmapi.TaskSpec_Container); ok {
240			containerSpec := x.Container
241			if y, ok := oldService.Spec.Task.GetRuntime().(*swarmapi.TaskSpec_Container); ok {
242				oldContainerSpec := y.Container
243				if containerSpec.Image != oldContainerSpec.Image {
244					attributes["image.old"] = oldContainerSpec.Image
245					attributes["image.new"] = containerSpec.Image
246				}
247			} else {
248				// This should not happen.
249				logrus.Errorf("service %s runtime changed from %T to %T", service.Spec.Annotations.Name, oldService.Spec.Task.GetRuntime(), service.Spec.Task.GetRuntime())
250			}
251		}
252		// check replicated count change
253		if x, ok := service.Spec.GetMode().(*swarmapi.ServiceSpec_Replicated); ok {
254			replicas := x.Replicated.Replicas
255			if y, ok := oldService.Spec.GetMode().(*swarmapi.ServiceSpec_Replicated); ok {
256				oldReplicas := y.Replicated.Replicas
257				if replicas != oldReplicas {
258					attributes["replicas.old"] = strconv.FormatUint(oldReplicas, 10)
259					attributes["replicas.new"] = strconv.FormatUint(replicas, 10)
260				}
261			} else {
262				// This should not happen.
263				logrus.Errorf("service %s mode changed from %T to %T", service.Spec.Annotations.Name, oldService.Spec.GetMode(), service.Spec.GetMode())
264			}
265		}
266		if service.UpdateStatus != nil {
267			if oldService.UpdateStatus == nil {
268				attributes["updatestate.new"] = strings.ToLower(service.UpdateStatus.State.String())
269			} else if service.UpdateStatus.State != oldService.UpdateStatus.State {
270				attributes["updatestate.old"] = strings.ToLower(oldService.UpdateStatus.State.String())
271				attributes["updatestate.new"] = strings.ToLower(service.UpdateStatus.State.String())
272			}
273		}
274	}
275	daemon.logClusterEvent(action, service.ID, "service", attributes, eventTime)
276}
277
278func (daemon *Daemon) logClusterEvent(action swarmapi.WatchActionKind, id, eventType string, attributes map[string]string, eventTime time.Time) {
279	actor := events.Actor{
280		ID:         id,
281		Attributes: attributes,
282	}
283
284	jm := events.Message{
285		Action:   clusterEventAction[action],
286		Type:     eventType,
287		Actor:    actor,
288		Scope:    "swarm",
289		Time:     eventTime.UTC().Unix(),
290		TimeNano: eventTime.UTC().UnixNano(),
291	}
292	daemon.EventsService.PublishMessage(jm)
293}
294
295func eventTimestamp(meta swarmapi.Meta, action swarmapi.WatchActionKind) time.Time {
296	var eventTime time.Time
297	switch action {
298	case swarmapi.WatchActionKindCreate:
299		eventTime, _ = gogotypes.TimestampFromProto(meta.CreatedAt)
300	case swarmapi.WatchActionKindUpdate:
301		eventTime, _ = gogotypes.TimestampFromProto(meta.UpdatedAt)
302	case swarmapi.WatchActionKindRemove:
303		// There is no timestamp from store message for remove operations.
304		// Use current time.
305		eventTime = time.Now()
306	}
307	return eventTime
308}
309