events.go 11 KB

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