Skip to content

Commit

Permalink
Merge pull request #14705 from jakecorrenti/show-health-status-event
Browse files Browse the repository at this point in the history
Show Health Status events
  • Loading branch information
openshift-ci[bot] authored Jun 27, 2022
2 parents ed2afb2 + 0c1a3b7 commit 278afae
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 10 deletions.
21 changes: 17 additions & 4 deletions libpod/container_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,9 +277,13 @@ func (c *Container) ExecStart(sessionID string) error {
return c.save()
}

func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *define.TerminalSize) error {
return c.execStartAndAttach(sessionID, streams, newSize, false)
}

// ExecStartAndAttach starts and attaches to an exec session in a container.
// newSize resizes the tty to this size before the process is started, must be nil if the exec session has no tty
func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *define.TerminalSize) error {
func (c *Container) execStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *define.TerminalSize, isHealthcheck bool) error {
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()
Expand Down Expand Up @@ -315,7 +319,12 @@ func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachS
return err
}

c.newContainerEvent(events.Exec)
if isHealthcheck {
c.newContainerEvent(events.HealthStatus)
} else {
c.newContainerEvent(events.Exec)
}

logrus.Debugf("Successfully started exec session %s in container %s", session.ID(), c.ID())

var lastErr error
Expand Down Expand Up @@ -743,10 +752,14 @@ func (c *Container) ExecResize(sessionID string, newSize define.TerminalSize) er
return c.ociRuntime.ExecAttachResize(c, sessionID, newSize)
}

func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan define.TerminalSize) (int, error) {
return c.exec(config, streams, resize, false)
}

// Exec emulates the old Libpod exec API, providing a single call to create,
// run, and remove an exec session. Returns exit code and error. Exit code is
// not guaranteed to be set sanely if error is not nil.
func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan define.TerminalSize) (int, error) {
func (c *Container) exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan define.TerminalSize, isHealthcheck bool) (int, error) {
sessionID, err := c.ExecCreate(config)
if err != nil {
return -1, err
Expand Down Expand Up @@ -780,7 +793,7 @@ func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resi
}()
}

if err := c.ExecStartAndAttach(sessionID, streams, size); err != nil {
if err := c.execStartAndAttach(sessionID, streams, size, isHealthcheck); err != nil {
return -1, err
}

Expand Down
10 changes: 10 additions & 0 deletions libpod/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ func (c *Container) newContainerEvent(status events.Status) {
Attributes: c.Labels(),
}

// if the current event is a HealthStatus event, we need to get the current
// status of the container to pass to the event
if status == events.HealthStatus {
containerHealthStatus, err := c.healthCheckStatus()
if err != nil {
e.HealthStatus = fmt.Sprintf("%v", err)
}
e.HealthStatus = containerHealthStatus
}

if err := c.runtime.eventer.Write(e); err != nil {
logrus.Errorf("Unable to write pod event: %q", err)
}
Expand Down
4 changes: 4 additions & 0 deletions libpod/events/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ type Event struct {
Time time.Time
// Type of event that occurred
Type Type
// Health status of the current container
HealthStatus string `json:"health_status,omitempty"`

Details
}
Expand Down Expand Up @@ -141,6 +143,8 @@ const (
Exited Status = "died"
// Export ...
Export Status = "export"
// HealthStatus ...
HealthStatus Status = "health_status"
// History ...
History Status = "history"
// Import ...
Expand Down
4 changes: 3 additions & 1 deletion libpod/events/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func (e *Event) ToHumanReadable(truncate bool) string {
}
switch e.Type {
case Container, Pod:
humanFormat = fmt.Sprintf("%s %s %s %s (image=%s, name=%s", e.Time, e.Type, e.Status, id, e.Image, e.Name)
humanFormat = fmt.Sprintf("%s %s %s %s (image=%s, name=%s, health_status=%s", e.Time, e.Type, e.Status, id, e.Image, e.Name, e.HealthStatus)
// check if the container has labels and add it to the output
if len(e.Attributes) > 0 {
for k, v := range e.Attributes {
Expand Down Expand Up @@ -168,6 +168,8 @@ func StringToStatus(name string) (Status, error) {
return Exited, nil
case Export.String():
return Export, nil
case HealthStatus.String():
return HealthStatus, nil
case History.String():
return History, nil
case Import.String():
Expand Down
2 changes: 2 additions & 0 deletions libpod/events/journal_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func (e EventJournalD) Write(ee Event) error {
}
m["PODMAN_LABELS"] = string(b)
}
m["PODMAN_HEALTH_STATUS"] = ee.HealthStatus
case Network:
m["PODMAN_ID"] = ee.ID
m["PODMAN_NETWORK_NAME"] = ee.Network
Expand Down Expand Up @@ -213,6 +214,7 @@ func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) {
newEvent.Details = Details{Attributes: labels}
}
}
newEvent.HealthStatus = entry.Fields["PODMAN_HEALTH_STATUS"]
case Network:
newEvent.ID = entry.Fields["PODMAN_ID"]
newEvent.Network = entry.Fields["PODMAN_NETWORK_NAME"]
Expand Down
15 changes: 12 additions & 3 deletions libpod/healthcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (c *Container) runHealthCheck() (define.HealthCheckStatus, error) {
hcResult := define.HealthCheckSuccess
config := new(ExecConfig)
config.Command = newCommand
exitCode, hcErr := c.Exec(config, streams, nil)
exitCode, hcErr := c.exec(config, streams, nil, true)
if hcErr != nil {
errCause := errors.Cause(hcErr)
hcResult = define.HealthCheckFailure
Expand Down Expand Up @@ -232,18 +232,27 @@ func (c *Container) getHealthCheckLog() (define.HealthCheckResults, error) {

// HealthCheckStatus returns the current state of a container with a healthcheck
func (c *Container) HealthCheckStatus() (string, error) {
c.lock.Lock()
defer c.lock.Unlock()
return c.healthCheckStatus()
}

// Internal function to return the current state of a container with a healthcheck.
// This function does not lock the container.
func (c *Container) healthCheckStatus() (string, error) {
if !c.HasHealthCheck() {
return "", errors.Errorf("container %s has no defined healthcheck", c.ID())
}
c.lock.Lock()
defer c.lock.Unlock()

if err := c.syncContainer(); err != nil {
return "", err
}

results, err := c.getHealthCheckLog()
if err != nil {
return "", errors.Wrapf(err, "unable to get healthcheck log for %s", c.ID())
}

return results.Status, nil
}

Expand Down
10 changes: 8 additions & 2 deletions pkg/domain/entities/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type Event struct {
// TODO: it would be nice to have full control over the types at some
// point and fork such Docker types.
dockerEvents.Message
HealthStatus string
}

// ConvertToLibpodEvent converts an entities event to a libpod one.
Expand Down Expand Up @@ -44,6 +45,7 @@ func ConvertToLibpodEvent(e Event) *libpodEvents.Event {
Status: status,
Time: time.Unix(0, e.TimeNano),
Type: t,
HealthStatus: e.HealthStatus,
Details: libpodEvents.Details{
Attributes: details,
},
Expand All @@ -59,7 +61,7 @@ func ConvertToEntitiesEvent(e libpodEvents.Event) *Event {
attributes["image"] = e.Image
attributes["name"] = e.Name
attributes["containerExitCode"] = strconv.Itoa(e.ContainerExitCode)
return &Event{dockerEvents.Message{
message := dockerEvents.Message{
// Compatibility with clients that still look for deprecated API elements
Status: e.Status.String(),
ID: e.ID,
Expand All @@ -73,5 +75,9 @@ func ConvertToEntitiesEvent(e libpodEvents.Event) *Event {
Scope: "local",
Time: e.Time.Unix(),
TimeNano: e.Time.UnixNano(),
}}
}
return &Event{
message,
e.HealthStatus,
}
}
4 changes: 4 additions & 0 deletions test/apiv2/27-containersEvents.at
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ t GET "libpod/events?stream=false&since=$START" 200 \
'select(.status | contains("died")).Action=died' \
'select(.status | contains("died")).Actor.Attributes.containerExitCode=1'

t GET "libpod/events?stream=false&since=$START" 200 \
'select(.status | contains("start")).Action=start' \
'select(.status | contains("start")).HealthStatus='\

# compat api, uses status=die (#12643)
t GET "events?stream=false&since=$START" 200 \
'select(.status | contains("start")).Action=start' \
Expand Down
21 changes: 21 additions & 0 deletions test/e2e/events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,4 +216,25 @@ var _ = Describe("Podman events", func() {
Expect(result.OutputToString()).To(ContainSubstring("create"))
})

It("podman events health_status generated", func() {
session := podmanTest.Podman([]string{"run", "--name", "test-hc", "-dt", "--health-cmd", "echo working", "busybox"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))

for i := 0; i < 5; i++ {
hc := podmanTest.Podman([]string{"healthcheck", "run", "test-hc"})
hc.WaitWithDefaultTimeout()
exitCode := hc.ExitCode()
if exitCode == 0 || i == 4 {
break
}
time.Sleep(1 * time.Second)
}

result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "event=health_status"})
result.WaitWithDefaultTimeout()
Expect(result).Should(Exit(0))
Expect(len(result.OutputToStringArray())).To(BeNumerically(">=", 1), "Number of health_status events")
})

})

0 comments on commit 278afae

Please sign in to comment.