Files
podman/libpod/events/journal_linux.go
Paul Holzinger 74fcd9fef3 podman events allow future time for --until
The podman events aren't read until the given timestamp if the
timestamp is in the future. It just reads all events until now
and exits afterwards.
This does not make sense and does not match docker. The correct
behavior is to read all events until the given time is reached.

This fixes a bug where the wrong event log file path was used
when running first time with a new storage location.
Fixes #8694

This also fixes the events api endpoint which only exited when
an error occurred. Otherwise it just hung after reading all events.

Signed-off-by: Paul Holzinger <paul.holzinger@web.de>
2020-12-11 23:15:09 +01:00

228 lines
6.1 KiB
Go

// +build systemd
package events
import (
"context"
"encoding/json"
"strconv"
"time"
"github.com/containers/podman/v2/pkg/util"
"github.com/coreos/go-systemd/v22/journal"
"github.com/coreos/go-systemd/v22/sdjournal"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// DefaultEventerType is journald when systemd is available
const DefaultEventerType = Journald
// EventJournalD is the journald implementation of an eventer
type EventJournalD struct {
options EventerOptions
}
// newEventJournalD creates a new journald Eventer
func newEventJournalD(options EventerOptions) (Eventer, error) {
return EventJournalD{options}, nil
}
// Write to journald
func (e EventJournalD) Write(ee Event) error {
m := make(map[string]string)
m["SYSLOG_IDENTIFIER"] = "podman"
m["PODMAN_EVENT"] = ee.Status.String()
m["PODMAN_TYPE"] = ee.Type.String()
m["PODMAN_TIME"] = ee.Time.Format(time.RFC3339Nano)
// Add specialized information based on the podman type
switch ee.Type {
case Image:
m["PODMAN_NAME"] = ee.Name
m["PODMAN_ID"] = ee.ID
case Container, Pod:
m["PODMAN_IMAGE"] = ee.Image
m["PODMAN_NAME"] = ee.Name
m["PODMAN_ID"] = ee.ID
if ee.ContainerExitCode != 0 {
m["PODMAN_EXIT_CODE"] = strconv.Itoa(ee.ContainerExitCode)
}
// If we have container labels, we need to convert them to a string so they
// can be recorded with the event
if len(ee.Details.Attributes) > 0 {
b, err := json.Marshal(ee.Details.Attributes)
if err != nil {
return err
}
m["PODMAN_LABELS"] = string(b)
}
case Network:
m["PODMAN_ID"] = ee.ID
m["PODMAN_NETWORK_NAME"] = ee.Network
case Volume:
m["PODMAN_NAME"] = ee.Name
}
return journal.Send(string(ee.ToHumanReadable()), journal.PriInfo, m)
}
// Read reads events from the journal and sends qualified events to the event channel
func (e EventJournalD) Read(ctx context.Context, options ReadOptions) error {
defer close(options.EventChannel)
eventOptions, err := generateEventOptions(options.Filters, options.Since, options.Until)
if err != nil {
return errors.Wrapf(err, "failed to generate event options")
}
var untilTime time.Time
if len(options.Until) > 0 {
untilTime, err = util.ParseInputTime(options.Until)
if err != nil {
return err
}
}
j, err := sdjournal.NewJournal()
if err != nil {
return err
}
// match only podman journal entries
podmanJournal := sdjournal.Match{Field: "SYSLOG_IDENTIFIER", Value: "podman"}
if err := j.AddMatch(podmanJournal.String()); err != nil {
return errors.Wrap(err, "failed to add journal filter for event log")
}
if len(options.Since) == 0 && len(options.Until) == 0 && options.Stream {
if err := j.SeekTail(); err != nil {
return errors.Wrap(err, "failed to seek end of journal")
}
// After SeekTail calling Next moves to a random entry.
// To prevent this we have to call Previous first.
// see: https://bugs.freedesktop.org/show_bug.cgi?id=64614
if _, err := j.Previous(); err != nil {
return errors.Wrap(err, "failed to move journal cursor to previous entry")
}
}
// the api requires a next|prev before getting a cursor
if _, err := j.Next(); err != nil {
return errors.Wrap(err, "failed to move journal cursor to next entry")
}
prevCursor, err := j.GetCursor()
if err != nil {
return errors.Wrap(err, "failed to get journal cursor")
}
for {
select {
case <-ctx.Done():
// the consumer has cancelled
return nil
default:
// fallthrough
}
if _, err := j.Next(); err != nil {
return errors.Wrap(err, "failed to move journal cursor to next entry")
}
newCursor, err := j.GetCursor()
if err != nil {
return errors.Wrap(err, "failed to get journal cursor")
}
if prevCursor == newCursor {
if !options.Stream || (len(options.Until) > 0 && time.Now().After(untilTime)) {
break
}
t := sdjournal.IndefiniteWait
if len(options.Until) > 0 {
t = time.Until(untilTime)
}
_ = j.Wait(t)
continue
}
prevCursor = newCursor
entry, err := j.GetEntry()
if err != nil {
return errors.Wrap(err, "failed to read journal entry")
}
newEvent, err := newEventFromJournalEntry(entry)
if err != nil {
// We can't decode this event.
// Don't fail hard - that would make events unusable.
// Instead, log and continue.
if errors.Cause(err) != ErrEventTypeBlank {
logrus.Errorf("Unable to decode event: %v", err)
}
continue
}
include := true
for _, filter := range eventOptions {
include = include && filter(newEvent)
}
if include {
options.EventChannel <- newEvent
}
}
return nil
}
func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) { //nolint
newEvent := Event{}
eventType, err := StringToType(entry.Fields["PODMAN_TYPE"])
if err != nil {
return nil, err
}
eventTime, err := time.Parse(time.RFC3339Nano, entry.Fields["PODMAN_TIME"])
if err != nil {
return nil, err
}
eventStatus, err := StringToStatus(entry.Fields["PODMAN_EVENT"])
if err != nil {
return nil, err
}
newEvent.Type = eventType
newEvent.Time = eventTime
newEvent.Status = eventStatus
newEvent.Name = entry.Fields["PODMAN_NAME"]
switch eventType {
case Container, Pod:
newEvent.ID = entry.Fields["PODMAN_ID"]
newEvent.Image = entry.Fields["PODMAN_IMAGE"]
if code, ok := entry.Fields["PODMAN_EXIT_CODE"]; ok {
intCode, err := strconv.Atoi(code)
if err != nil {
logrus.Errorf("Error parsing event exit code %s", code)
} else {
newEvent.ContainerExitCode = intCode
}
}
// we need to check for the presence of labels recorded to a container event
if stringLabels, ok := entry.Fields["PODMAN_LABELS"]; ok && len(stringLabels) > 0 {
labels := make(map[string]string, 0)
if err := json.Unmarshal([]byte(stringLabels), &labels); err != nil {
return nil, err
}
// if we have labels, add them to the event
if len(labels) > 0 {
newEvent.Details = Details{Attributes: labels}
}
}
case Network:
newEvent.ID = entry.Fields["PODMAN_ID"]
newEvent.Network = entry.Fields["PODMAN_NETWORK_NAME"]
case Image:
newEvent.ID = entry.Fields["PODMAN_ID"]
}
return &newEvent, nil
}
// String returns a string representation of the logger
func (e EventJournalD) String() string {
return Journald.String()
}