Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run HealthCheck without saving the ExecSession to the database #25003

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -757,6 +775,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
Loading