diff --git a/daemon/events.go b/daemon/events.go index 40e19fb8c9..6a0f5a6bbe 100644 --- a/daemon/events.go +++ b/daemon/events.go @@ -241,15 +241,25 @@ func (daemon *Daemon) logServiceEvent(action swarmapi.WatchActionKind, service * daemon.logClusterEvent(action, service.ID, events.ServiceEventType, eventTime, attributes) } -var clusterEventAction = map[swarmapi.WatchActionKind]events.Action{ - swarmapi.WatchActionKindCreate: events.ActionCreate, - swarmapi.WatchActionKindUpdate: events.ActionUpdate, - swarmapi.WatchActionKindRemove: events.ActionRemove, -} - func (daemon *Daemon) logClusterEvent(action swarmapi.WatchActionKind, id string, eventType events.Type, eventTime time.Time, attributes map[string]string) { + var eventAction events.Action + switch action { + case swarmapi.WatchActionKindCreate: + eventAction = events.ActionCreate + case swarmapi.WatchActionKindUpdate: + eventAction = events.ActionUpdate + case swarmapi.WatchActionKindRemove: + eventAction = events.ActionRemove + case swarmapi.WatchActionKindUnknown: + // Unknown action kind - skip publishing invalid event + return + default: + // Unknown action kind - skip publishing invalid event + return + } + daemon.EventsService.PublishMessage(events.Message{ - Action: clusterEventAction[action], + Action: eventAction, Type: eventType, Actor: events.Actor{ ID: id, @@ -262,18 +272,21 @@ func (daemon *Daemon) logClusterEvent(action swarmapi.WatchActionKind, id string } func eventTimestamp(meta swarmapi.Meta, action swarmapi.WatchActionKind) time.Time { - var eventTime time.Time switch action { case swarmapi.WatchActionKindCreate: - eventTime, _ = gogotypes.TimestampFromProto(meta.CreatedAt) + eventTime, _ := gogotypes.TimestampFromProto(meta.CreatedAt) + return eventTime case swarmapi.WatchActionKindUpdate: - eventTime, _ = gogotypes.TimestampFromProto(meta.UpdatedAt) + eventTime, _ := gogotypes.TimestampFromProto(meta.UpdatedAt) + return eventTime case swarmapi.WatchActionKindRemove: // There is no timestamp from store message for remove operations. // Use current time. - eventTime = time.Now() + return time.Now() + case swarmapi.WatchActionKindUnknown: + return time.Now() default: - // TODO(thaJeztah): make switch exhaustive: anything to do for swarmapi.WatchActionKindUnknown or "other" ? + // For any unexpected action kinds, use current time as fallback. + return time.Now() } - return eventTime } diff --git a/daemon/events_test.go b/daemon/events_test.go index 07d497346d..98205a1500 100644 --- a/daemon/events_test.go +++ b/daemon/events_test.go @@ -4,10 +4,12 @@ import ( "testing" "time" + gogotypes "github.com/gogo/protobuf/types" containertypes "github.com/moby/moby/api/types/container" eventtypes "github.com/moby/moby/api/types/events" "github.com/moby/moby/v2/daemon/container" "github.com/moby/moby/v2/daemon/events" + swarmapi "github.com/moby/swarmkit/v2/api" ) func TestLogContainerEventCopyLabels(t *testing.T) { @@ -87,3 +89,74 @@ func validateTestAttributes(t *testing.T, l chan any, expectedAttributesToTest m t.Fatal("LogEvent test timed out") } } + +func TestEventTimestamp(t *testing.T) { + now := time.Now() + createdAt := &gogotypes.Timestamp{Seconds: now.Unix(), Nanos: int32(now.Nanosecond())} + updatedAt := &gogotypes.Timestamp{Seconds: now.Add(time.Hour).Unix(), Nanos: int32(now.Add(time.Hour).Nanosecond())} + + tests := []struct { + doc string + meta swarmapi.Meta + action swarmapi.WatchActionKind + check func(t *testing.T, result time.Time) + }{ + { + doc: "Create action uses CreatedAt timestamp", + meta: swarmapi.Meta{CreatedAt: createdAt}, + action: swarmapi.WatchActionKindCreate, + check: func(t *testing.T, result time.Time) { + if result.Unix() != now.Unix() { + t.Errorf("expected CreatedAt timestamp, got %v", result) + } + }, + }, + { + doc: "Update action uses UpdatedAt timestamp", + meta: swarmapi.Meta{UpdatedAt: updatedAt}, + action: swarmapi.WatchActionKindUpdate, + check: func(t *testing.T, result time.Time) { + if result.Unix() != now.Add(time.Hour).Unix() { + t.Errorf("expected UpdatedAt timestamp, got %v", result) + } + }, + }, + { + doc: "Remove action uses current time", + meta: swarmapi.Meta{}, + action: swarmapi.WatchActionKindRemove, + check: func(t *testing.T, result time.Time) { + if result.IsZero() { + t.Error("expected non-zero timestamp for Remove action") + } + }, + }, + { + doc: "Unknown action returns valid timestamp", + meta: swarmapi.Meta{}, + action: swarmapi.WatchActionKindUnknown, + check: func(t *testing.T, result time.Time) { + if result.IsZero() { + t.Error("expected non-zero timestamp for Unknown action") + } + }, + }, + { + doc: "Invalid action falls back to current time", + meta: swarmapi.Meta{}, + action: swarmapi.WatchActionKind(123456789), + check: func(t *testing.T, result time.Time) { + if result.IsZero() { + t.Error("expected non-zero timestamp for invalid action") + } + }, + }, + } + + for _, tc := range tests { + t.Run(tc.doc, func(t *testing.T) { + result := eventTimestamp(tc.meta, tc.action) + tc.check(t, result) + }) + } +}