2015-11-02 18:25:26 -05:00
package daemon
2015-11-12 14:55:17 -05:00
import (
2017-04-02 18:21:56 -04:00
"context"
"strconv"
2015-12-21 17:55:23 -05:00
"strings"
2016-04-11 14:52:34 -04:00
"time"
2015-12-21 17:55:23 -05:00
2017-04-02 18:21:56 -04:00
"github.com/Sirupsen/logrus"
2016-09-06 14:18:12 -04:00
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
2015-11-12 14:55:17 -05:00
"github.com/docker/docker/container"
2016-04-11 14:52:34 -04:00
daemonevents "github.com/docker/docker/daemon/events"
2015-12-21 17:55:23 -05:00
"github.com/docker/libnetwork"
2017-04-02 18:21:56 -04:00
swarmapi "github.com/docker/swarmkit/api"
gogotypes "github.com/gogo/protobuf/types"
)
var (
clusterEventAction = map [ swarmapi . WatchActionKind ] string {
swarmapi . WatchActionKindCreate : "create" ,
swarmapi . WatchActionKindUpdate : "update" ,
swarmapi . WatchActionKindRemove : "remove" ,
}
2015-11-12 14:55:17 -05:00
)
2016-01-07 17:14:05 -05:00
// LogContainerEvent generates an event related to a container with only the default attributes.
2015-11-12 14:55:17 -05:00
func ( daemon * Daemon ) LogContainerEvent ( container * container . Container , action string ) {
2016-01-07 17:14:05 -05:00
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 )
2015-12-21 17:55:23 -05:00
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 )
}
2016-04-26 03:36:14 -04:00
// LogImageEvent generates an event related to an image with only the default attributes.
2015-12-21 17:55:23 -05:00
func ( daemon * Daemon ) LogImageEvent ( imageID , refName , action string ) {
2016-01-07 17:14:05 -05:00
daemon . LogImageEventWithAttributes ( imageID , refName , action , map [ string ] string { } )
}
2016-04-26 03:36:14 -04:00
// LogImageEventWithAttributes generates an event related to an image with specific given attributes.
2016-01-07 17:14:05 -05:00
func ( daemon * Daemon ) LogImageEventWithAttributes ( imageID , refName , action string , attributes map [ string ] string ) {
2015-12-21 17:55:23 -05:00
img , err := daemon . GetImage ( imageID )
if err == nil && img . Config != nil {
// image has not been removed yet.
// it could be missing if the event is `delete`.
2016-01-07 17:14:05 -05:00
copyAttributes ( attributes , img . Config . Labels )
2015-12-21 17:55:23 -05:00
}
if refName != "" {
attributes [ "name" ] = refName
}
actor := events . Actor {
ID : imageID ,
Attributes : attributes ,
}
daemon . EventsService . Log ( action , events . ImageEventType , actor )
}
2016-07-18 11:02:12 -04:00
// 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 )
}
2015-12-21 17:55:23 -05:00
// 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 {
2015-12-21 23:35:30 -05:00
ID : nw . ID ( ) ,
2015-12-21 17:55:23 -05:00
Attributes : attributes ,
}
daemon . EventsService . Log ( action , events . NetworkEventType , actor )
}
2016-05-08 19:11:34 -04:00
// 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 {
2016-05-08 19:15:33 -04:00
if info , err := daemon . SystemInfo ( ) ; err == nil && info . Name != "" {
attributes [ "name" ] = info . Name
}
2016-05-08 19:11:34 -04:00
actor := events . Actor {
ID : daemon . ID ,
Attributes : attributes ,
}
daemon . EventsService . Log ( action , events . DaemonEventType , actor )
}
}
2016-04-11 14:52:34 -04:00
// 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 )
}
2015-12-21 17:55:23 -05:00
// copyAttributes guarantees that labels are not mutated by event triggers.
2016-01-07 17:14:05 -05:00
func copyAttributes ( attributes , labels map [ string ] string ) {
2015-12-21 17:55:23 -05:00
if labels == nil {
2016-01-07 17:14:05 -05:00
return
2015-12-21 17:55:23 -05:00
}
for k , v := range labels {
attributes [ k ] = v
}
2015-11-02 18:25:26 -05:00
}
2017-04-02 18:21:56 -04:00
// 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 ( ) )
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 ) 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
}