| package daemon // import "github.com/docker/docker/daemon" |
| |
| import ( |
| "context" |
| "strconv" |
| "strings" |
| "time" |
| |
| "github.com/docker/docker/api/types/events" |
| "github.com/docker/docker/api/types/filters" |
| "github.com/docker/docker/container" |
| daemonevents "github.com/docker/docker/daemon/events" |
| "github.com/docker/libnetwork" |
| swarmapi "github.com/docker/swarmkit/api" |
| gogotypes "github.com/gogo/protobuf/types" |
| "github.com/sirupsen/logrus" |
| ) |
| |
| var ( |
| clusterEventAction = map[swarmapi.WatchActionKind]string{ |
| swarmapi.WatchActionKindCreate: "create", |
| swarmapi.WatchActionKindUpdate: "update", |
| swarmapi.WatchActionKindRemove: "remove", |
| } |
| ) |
| |
| // LogContainerEvent generates an event related to a container with only the default attributes. |
| func (daemon *Daemon) LogContainerEvent(container *container.Container, action string) { |
| daemon.LogContainerEventWithAttributes(container, action, map[string]string{}) |
| } |
| |
| // LogContainerEventWithAttributes generates an event related to a container with specific given attributes. |
| func (daemon *Daemon) LogContainerEventWithAttributes(container *container.Container, action string, attributes map[string]string) { |
| copyAttributes(attributes, container.Config.Labels) |
| if container.Config.Image != "" { |
| attributes["image"] = container.Config.Image |
| } |
| attributes["name"] = strings.TrimLeft(container.Name, "/") |
| |
| actor := events.Actor{ |
| ID: container.ID, |
| Attributes: attributes, |
| } |
| daemon.EventsService.Log(action, events.ContainerEventType, actor) |
| } |
| |
| // LogPluginEvent generates an event related to a plugin with only the default attributes. |
| func (daemon *Daemon) LogPluginEvent(pluginID, refName, action string) { |
| daemon.LogPluginEventWithAttributes(pluginID, refName, action, map[string]string{}) |
| } |
| |
| // LogPluginEventWithAttributes generates an event related to a plugin with specific given attributes. |
| func (daemon *Daemon) LogPluginEventWithAttributes(pluginID, refName, action string, attributes map[string]string) { |
| attributes["name"] = refName |
| actor := events.Actor{ |
| ID: pluginID, |
| Attributes: attributes, |
| } |
| daemon.EventsService.Log(action, events.PluginEventType, actor) |
| } |
| |
| // LogVolumeEvent generates an event related to a volume. |
| func (daemon *Daemon) LogVolumeEvent(volumeID, action string, attributes map[string]string) { |
| actor := events.Actor{ |
| ID: volumeID, |
| Attributes: attributes, |
| } |
| daemon.EventsService.Log(action, events.VolumeEventType, actor) |
| } |
| |
| // LogNetworkEvent generates an event related to a network with only the default attributes. |
| func (daemon *Daemon) LogNetworkEvent(nw libnetwork.Network, action string) { |
| daemon.LogNetworkEventWithAttributes(nw, action, map[string]string{}) |
| } |
| |
| // LogNetworkEventWithAttributes generates an event related to a network with specific given attributes. |
| func (daemon *Daemon) LogNetworkEventWithAttributes(nw libnetwork.Network, action string, attributes map[string]string) { |
| attributes["name"] = nw.Name() |
| attributes["type"] = nw.Type() |
| actor := events.Actor{ |
| ID: nw.ID(), |
| Attributes: attributes, |
| } |
| daemon.EventsService.Log(action, events.NetworkEventType, actor) |
| } |
| |
| // LogDaemonEventWithAttributes generates an event related to the daemon itself with specific given attributes. |
| func (daemon *Daemon) LogDaemonEventWithAttributes(action string, attributes map[string]string) { |
| if daemon.EventsService != nil { |
| if info := daemon.SystemInfo(); info.Name != "" { |
| attributes["name"] = info.Name |
| } |
| actor := events.Actor{ |
| ID: daemon.ID, |
| Attributes: attributes, |
| } |
| daemon.EventsService.Log(action, events.DaemonEventType, actor) |
| } |
| } |
| |
| // SubscribeToEvents returns the currently record of events, a channel to stream new events from, and a function to cancel the stream of events. |
| func (daemon *Daemon) SubscribeToEvents(since, until time.Time, filter filters.Args) ([]events.Message, chan interface{}) { |
| ef := daemonevents.NewFilter(filter) |
| return daemon.EventsService.SubscribeTopic(since, until, ef) |
| } |
| |
| // UnsubscribeFromEvents stops the event subscription for a client by closing the |
| // channel where the daemon sends events to. |
| func (daemon *Daemon) UnsubscribeFromEvents(listener chan interface{}) { |
| daemon.EventsService.Evict(listener) |
| } |
| |
| // copyAttributes guarantees that labels are not mutated by event triggers. |
| func copyAttributes(attributes, labels map[string]string) { |
| if labels == nil { |
| return |
| } |
| for k, v := range labels { |
| attributes[k] = v |
| } |
| } |
| |
| // ProcessClusterNotifications gets changes from store and add them to event list |
| func (daemon *Daemon) ProcessClusterNotifications(ctx context.Context, watchStream chan *swarmapi.WatchMessage) { |
| for { |
| select { |
| case <-ctx.Done(): |
| return |
| case message, ok := <-watchStream: |
| if !ok { |
| logrus.Debug("cluster event channel has stopped") |
| return |
| } |
| daemon.generateClusterEvent(message) |
| } |
| } |
| } |
| |
| func (daemon *Daemon) generateClusterEvent(msg *swarmapi.WatchMessage) { |
| for _, event := range msg.Events { |
| if event.Object == nil { |
| logrus.Errorf("event without object: %v", event) |
| continue |
| } |
| switch v := event.Object.GetObject().(type) { |
| case *swarmapi.Object_Node: |
| daemon.logNodeEvent(event.Action, v.Node, event.OldObject.GetNode()) |
| case *swarmapi.Object_Service: |
| daemon.logServiceEvent(event.Action, v.Service, event.OldObject.GetService()) |
| case *swarmapi.Object_Network: |
| daemon.logNetworkEvent(event.Action, v.Network, event.OldObject.GetNetwork()) |
| case *swarmapi.Object_Secret: |
| daemon.logSecretEvent(event.Action, v.Secret, event.OldObject.GetSecret()) |
| case *swarmapi.Object_Config: |
| daemon.logConfigEvent(event.Action, v.Config, event.OldObject.GetConfig()) |
| default: |
| logrus.Warnf("unrecognized event: %v", event) |
| } |
| } |
| } |
| |
| func (daemon *Daemon) logNetworkEvent(action swarmapi.WatchActionKind, net *swarmapi.Network, oldNet *swarmapi.Network) { |
| attributes := map[string]string{ |
| "name": net.Spec.Annotations.Name, |
| } |
| eventTime := eventTimestamp(net.Meta, action) |
| daemon.logClusterEvent(action, net.ID, "network", attributes, eventTime) |
| } |
| |
| func (daemon *Daemon) logSecretEvent(action swarmapi.WatchActionKind, secret *swarmapi.Secret, oldSecret *swarmapi.Secret) { |
| attributes := map[string]string{ |
| "name": secret.Spec.Annotations.Name, |
| } |
| eventTime := eventTimestamp(secret.Meta, action) |
| daemon.logClusterEvent(action, secret.ID, "secret", attributes, eventTime) |
| } |
| |
| func (daemon *Daemon) logConfigEvent(action swarmapi.WatchActionKind, config *swarmapi.Config, oldConfig *swarmapi.Config) { |
| attributes := map[string]string{ |
| "name": config.Spec.Annotations.Name, |
| } |
| eventTime := eventTimestamp(config.Meta, action) |
| daemon.logClusterEvent(action, config.ID, "config", attributes, eventTime) |
| } |
| |
| func (daemon *Daemon) logNodeEvent(action swarmapi.WatchActionKind, node *swarmapi.Node, oldNode *swarmapi.Node) { |
| name := node.Spec.Annotations.Name |
| if name == "" && node.Description != nil { |
| name = node.Description.Hostname |
| } |
| attributes := map[string]string{ |
| "name": name, |
| } |
| eventTime := eventTimestamp(node.Meta, action) |
| // In an update event, display the changes in attributes |
| if action == swarmapi.WatchActionKindUpdate && oldNode != nil { |
| if node.Spec.Availability != oldNode.Spec.Availability { |
| attributes["availability.old"] = strings.ToLower(oldNode.Spec.Availability.String()) |
| attributes["availability.new"] = strings.ToLower(node.Spec.Availability.String()) |
| } |
| if node.Role != oldNode.Role { |
| attributes["role.old"] = strings.ToLower(oldNode.Role.String()) |
| attributes["role.new"] = strings.ToLower(node.Role.String()) |
| } |
| if node.Status.State != oldNode.Status.State { |
| attributes["state.old"] = strings.ToLower(oldNode.Status.State.String()) |
| attributes["state.new"] = strings.ToLower(node.Status.State.String()) |
| } |
| // This handles change within manager role |
| if node.ManagerStatus != nil && oldNode.ManagerStatus != nil { |
| // leader change |
| if node.ManagerStatus.Leader != oldNode.ManagerStatus.Leader { |
| if node.ManagerStatus.Leader { |
| attributes["leader.old"] = "false" |
| attributes["leader.new"] = "true" |
| } else { |
| attributes["leader.old"] = "true" |
| attributes["leader.new"] = "false" |
| } |
| } |
| if node.ManagerStatus.Reachability != oldNode.ManagerStatus.Reachability { |
| attributes["reachability.old"] = strings.ToLower(oldNode.ManagerStatus.Reachability.String()) |
| attributes["reachability.new"] = strings.ToLower(node.ManagerStatus.Reachability.String()) |
| } |
| } |
| } |
| |
| daemon.logClusterEvent(action, node.ID, "node", attributes, eventTime) |
| } |
| |
| func (daemon *Daemon) logServiceEvent(action swarmapi.WatchActionKind, service *swarmapi.Service, oldService *swarmapi.Service) { |
| attributes := map[string]string{ |
| "name": service.Spec.Annotations.Name, |
| } |
| eventTime := eventTimestamp(service.Meta, action) |
| |
| if action == swarmapi.WatchActionKindUpdate && oldService != nil { |
| // check image |
| if x, ok := service.Spec.Task.GetRuntime().(*swarmapi.TaskSpec_Container); ok { |
| containerSpec := x.Container |
| if y, ok := oldService.Spec.Task.GetRuntime().(*swarmapi.TaskSpec_Container); ok { |
| oldContainerSpec := y.Container |
| if containerSpec.Image != oldContainerSpec.Image { |
| attributes["image.old"] = oldContainerSpec.Image |
| attributes["image.new"] = containerSpec.Image |
| } |
| } else { |
| // This should not happen. |
| logrus.Errorf("service %s runtime changed from %T to %T", service.Spec.Annotations.Name, oldService.Spec.Task.GetRuntime(), service.Spec.Task.GetRuntime()) |
| } |
| } |
| // check replicated count change |
| if x, ok := service.Spec.GetMode().(*swarmapi.ServiceSpec_Replicated); ok { |
| replicas := x.Replicated.Replicas |
| if y, ok := oldService.Spec.GetMode().(*swarmapi.ServiceSpec_Replicated); ok { |
| oldReplicas := y.Replicated.Replicas |
| if replicas != oldReplicas { |
| attributes["replicas.old"] = strconv.FormatUint(oldReplicas, 10) |
| attributes["replicas.new"] = strconv.FormatUint(replicas, 10) |
| } |
| } else { |
| // This should not happen. |
| logrus.Errorf("service %s mode changed from %T to %T", service.Spec.Annotations.Name, oldService.Spec.GetMode(), service.Spec.GetMode()) |
| } |
| } |
| if service.UpdateStatus != nil { |
| if oldService.UpdateStatus == nil { |
| attributes["updatestate.new"] = strings.ToLower(service.UpdateStatus.State.String()) |
| } else if service.UpdateStatus.State != oldService.UpdateStatus.State { |
| attributes["updatestate.old"] = strings.ToLower(oldService.UpdateStatus.State.String()) |
| attributes["updatestate.new"] = strings.ToLower(service.UpdateStatus.State.String()) |
| } |
| } |
| } |
| daemon.logClusterEvent(action, service.ID, "service", attributes, eventTime) |
| } |
| |
| func (daemon *Daemon) logClusterEvent(action swarmapi.WatchActionKind, id, eventType string, attributes map[string]string, eventTime time.Time) { |
| actor := events.Actor{ |
| ID: id, |
| Attributes: attributes, |
| } |
| |
| jm := events.Message{ |
| Action: clusterEventAction[action], |
| Type: eventType, |
| Actor: actor, |
| Scope: "swarm", |
| Time: eventTime.UTC().Unix(), |
| TimeNano: eventTime.UTC().UnixNano(), |
| } |
| daemon.EventsService.PublishMessage(jm) |
| } |
| |
| func eventTimestamp(meta swarmapi.Meta, action swarmapi.WatchActionKind) time.Time { |
| var eventTime time.Time |
| switch action { |
| case swarmapi.WatchActionKindCreate: |
| eventTime, _ = gogotypes.TimestampFromProto(meta.CreatedAt) |
| case swarmapi.WatchActionKindUpdate: |
| eventTime, _ = gogotypes.TimestampFromProto(meta.UpdatedAt) |
| case swarmapi.WatchActionKindRemove: |
| // There is no timestamp from store message for remove operations. |
| // Use current time. |
| eventTime = time.Now() |
| } |
| return eventTime |
| } |