Skip to content

Commit

Permalink
Run HealthCheck without creating and removing the ExecSession in the …
Browse files Browse the repository at this point in the history
…database

Fixes: https://issues.redhat.com/browse/RHEL-69970

Signed-off-by: Jan Rodák <[email protected]>
  • Loading branch information
Honny1 committed Feb 3, 2025
1 parent e300f5c commit d7e9667
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 26 deletions.
124 changes: 99 additions & 25 deletions libpod/container_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,34 +158,20 @@ type legacyExecSession struct {
PID int `json:"pid"`
}

// ExecCreate creates a new exec session for the container.
// The session is not started. The ID of the new exec session will be returned.
func (c *Container) ExecCreate(config *ExecConfig) (string, error) {
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()

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

// Verify our config
func (c *Container) verifyExecConfig(config *ExecConfig) error {
if config == nil {
return "", fmt.Errorf("must provide a configuration to ExecCreate: %w", define.ErrInvalidArg)
return fmt.Errorf("must provide a configuration to ExecCreate: %w", define.ErrInvalidArg)
}
if len(config.Command) == 0 {
return "", fmt.Errorf("must provide a non-empty command to start an exec session: %w", define.ErrInvalidArg)
return fmt.Errorf("must provide a non-empty command to start an exec session: %w", define.ErrInvalidArg)
}
if config.ExitCommandDelay > 0 && len(config.ExitCommand) == 0 {
return "", fmt.Errorf("must provide a non-empty exit command if giving an exit command delay: %w", define.ErrInvalidArg)
}

// Verify that we are in a good state to continue
if !c.ensureState(define.ContainerStateRunning) {
return "", fmt.Errorf("can only create exec sessions on running containers: %w", define.ErrCtrStateInvalid)
return fmt.Errorf("must provide a non-empty exit command if giving an exit command delay: %w", define.ErrInvalidArg)
}
return nil
}

func (c *Container) getUniqueExecSessionID() string {
// Generate an ID for our new exec session
sessionID := stringid.GenerateRandomID()
found := true
Expand All @@ -202,20 +188,52 @@ func (c *Container) ExecCreate(config *ExecConfig) (string, error) {
sessionID = stringid.GenerateRandomID()
}
}
return sessionID
}

// Make our new exec session
func (c *Container) createExecSession(config *ExecConfig) (*ExecSession, error) {
session := new(ExecSession)
session.Id = sessionID
session.Id = c.getUniqueExecSessionID()
session.ContainerId = c.ID()
session.State = define.ExecStateCreated
session.Config = new(ExecConfig)
if err := JSONDeepCopy(config, session.Config); err != nil {
return "", fmt.Errorf("copying exec configuration into exec session: %w", err)
return nil, fmt.Errorf("copying exec configuration into exec session: %w", err)
}

if len(session.Config.ExitCommand) > 0 {
session.Config.ExitCommand = append(session.Config.ExitCommand, []string{session.ID(), c.ID()}...)
}
return session, nil
}

// ExecCreate creates a new exec session for the container.
// The session is not started. The ID of the new exec session will be returned.
func (c *Container) ExecCreate(config *ExecConfig) (string, error) {
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()

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

// Verify our config
if err := c.verifyExecConfig(config); err != nil {
return "", err
}

// Verify that we are in a good state to continue
if !c.ensureState(define.ContainerStateRunning) {
return "", fmt.Errorf("can only create exec sessions on running containers: %w", define.ErrCtrStateInvalid)
}

// Make our new exec session
session, err := c.createExecSession(config)
if err != nil {
return "", err
}

if c.state.ExecSessions == nil {
c.state.ExecSessions = make(map[string]*ExecSession)
Expand All @@ -232,7 +250,7 @@ func (c *Container) ExecCreate(config *ExecConfig) (string, error) {

logrus.Infof("Created exec session %s in container %s", session.ID(), c.ID())

return sessionID, nil
return session.Id, nil
}

// ExecStart starts an exec session in the container, but does not attach to it.
Expand Down Expand Up @@ -775,6 +793,62 @@ func (c *Container) ExecResize(sessionID string, newSize resize.TerminalSize) er
return c.ociRuntime.ExecAttachResize(c, sessionID, newSize)
}

func (c *Container) healthCheckExec(config *ExecConfig, streams *define.AttachStreams) (exitCode int, retErr error) {
if !c.batched {
c.lock.Lock()

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

if err := c.verifyExecConfig(config); err != nil {
return -1, err
}

if !c.ensureState(define.ContainerStateRunning) {
return -1, fmt.Errorf("can only create exec sessions on running containers: %w", define.ErrCtrStateInvalid)
}

session, err := c.createExecSession(config)
if err != nil {
return -1, err
}

if c.state.ExecSessions == nil {
c.state.ExecSessions = make(map[string]*ExecSession)
}
c.state.ExecSessions[session.ID()] = session
defer delete(c.state.ExecSessions, session.ID())

opts, err := prepareForExec(c, session)
if err != nil {
return -1, err
}
defer func() {
if err := c.cleanupExecBundle(session.ID()); err != nil {
logrus.Errorf("Container %s light exec session cleanup error: %v", c.ID(), err)
}
}()

pid, attachErrChan, err := c.ociRuntime.ExecContainer(c, session.ID(), opts, streams, nil)
if err != nil {
return -1, err
}

if !c.batched {
c.lock.Unlock()
}

err = <-attachErrChan
if err != nil {
logrus.Errorf("Container %s light exec session with pid: %d error: %v", c.ID(), pid, err)
return -1, err
}

return c.readExecExitCode(session.ID())
}

func (c *Container) Exec(config *ExecConfig, streams *define.AttachStreams, resize <-chan resize.TerminalSize) (int, error) {
return c.exec(config, streams, resize, false)
}
Expand Down
2 changes: 1 addition & 1 deletion libpod/healthcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func (c *Container) runHealthCheck(ctx context.Context, isStartup bool) (define.
hcResult := define.HealthCheckSuccess
config := new(ExecConfig)
config.Command = newCommand
exitCode, hcErr := c.exec(config, streams, nil, true)
exitCode, hcErr := c.healthCheckExec(config, streams)
if hcErr != nil {
hcResult = define.HealthCheckFailure
if errors.Is(hcErr, define.ErrOCIRuntimeNotFound) ||
Expand Down
24 changes: 24 additions & 0 deletions test/system/220-healthcheck.bats
Original file line number Diff line number Diff line change
Expand Up @@ -466,4 +466,28 @@ function _check_health_log {
run_podman rm -t 0 -f $ctrname
}

@test "podman healthcheck - stop container when healthcheck runs" {
ctr="c-h-$(safename)"
msg="hc-msg-$(random_string)"

run_podman run -d --name $ctr \
--health-cmd "sleep 20; echo $msg" \
$IMAGE /home/podman/pause

run_podman 1 healthcheck run $ctr &

run_podman inspect $ctr --format "{{.State.Status}}"
assert "$output" == "running" "Container is stopped"

run_podman stop $ctr

run_podman inspect $ctr --format "{{.State.Status}}"
assert "$output" == "exited" "Container is stopped"

run_podman inspect $ctr --format "{{.State.Health.Log}}"
assert "$output" !~ "$msg" "Health log message not found"

run_podman rm -f -t0 $ctr
}

# vim: filetype=sh

0 comments on commit d7e9667

Please sign in to comment.