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  }