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