mirror of https://github.com/containers/podman.git
commit
3176b3fe01
|
@ -5,8 +5,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/containers/common/libnetwork/types"
|
"github.com/containers/common/libnetwork/types"
|
||||||
"github.com/containers/podman/v4/libpod/define"
|
"github.com/containers/podman/v4/libpod/define"
|
||||||
|
@ -63,6 +65,13 @@ type BoltState struct {
|
||||||
// initially created the database. This must match for any further instances
|
// initially created the database. This must match for any further instances
|
||||||
// that access the database, to ensure that state mismatches with
|
// that access the database, to ensure that state mismatches with
|
||||||
// containers/storage do not occur.
|
// containers/storage do not occur.
|
||||||
|
// - exitCodeBucket/exitCodeTimeStampBucket: (#14559) exit codes must be part
|
||||||
|
// of the database to resolve a previous race condition when one process waits
|
||||||
|
// for the exit file to be written and another process removes it along with
|
||||||
|
// the container during auto-removal. The same race would happen trying to
|
||||||
|
// read the exit code from the containers bucket. Hence, exit codes go into
|
||||||
|
// their own bucket. To avoid the rather expensive JSON (un)marshaling, we
|
||||||
|
// have two buckets: one for the exit codes, the other for the timestamps.
|
||||||
|
|
||||||
// NewBoltState creates a new bolt-backed state database
|
// NewBoltState creates a new bolt-backed state database
|
||||||
func NewBoltState(path string, runtime *Runtime) (State, error) {
|
func NewBoltState(path string, runtime *Runtime) (State, error) {
|
||||||
|
@ -98,6 +107,8 @@ func NewBoltState(path string, runtime *Runtime) (State, error) {
|
||||||
allVolsBkt,
|
allVolsBkt,
|
||||||
execBkt,
|
execBkt,
|
||||||
runtimeConfigBkt,
|
runtimeConfigBkt,
|
||||||
|
exitCodeBkt,
|
||||||
|
exitCodeTimeStampBkt,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Does the DB need an update?
|
// Does the DB need an update?
|
||||||
|
@ -192,6 +203,45 @@ func (s *BoltState) Refresh() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exitCodeBucket, err := getExitCodeBucket(tx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
timeStampBucket, err := getExitCodeTimeStampBucket(tx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear all exec exit codes
|
||||||
|
toRemoveExitCodes := []string{}
|
||||||
|
err = exitCodeBucket.ForEach(func(id, _ []byte) error {
|
||||||
|
toRemoveExitCodes = append(toRemoveExitCodes, string(id))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "error reading exit codes bucket")
|
||||||
|
}
|
||||||
|
for _, id := range toRemoveExitCodes {
|
||||||
|
if err := exitCodeBucket.Delete([]byte(id)); err != nil {
|
||||||
|
return errors.Wrapf(err, "error removing exit code for ID %s", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toRemoveTimeStamps := []string{}
|
||||||
|
err = timeStampBucket.ForEach(func(id, _ []byte) error {
|
||||||
|
toRemoveTimeStamps = append(toRemoveTimeStamps, string(id))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "reading timestamps bucket")
|
||||||
|
}
|
||||||
|
for _, id := range toRemoveTimeStamps {
|
||||||
|
if err := timeStampBucket.Delete([]byte(id)); err != nil {
|
||||||
|
return errors.Wrapf(err, "removing timestamp for ID %s", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Iterate through all IDs. Check if they are containers.
|
// Iterate through all IDs. Check if they are containers.
|
||||||
// If they are, unmarshal their state, and then clear
|
// If they are, unmarshal their state, and then clear
|
||||||
// PID, mountpoint, and state for all of them
|
// PID, mountpoint, and state for all of them
|
||||||
|
@ -1341,6 +1391,204 @@ func (s *BoltState) GetContainerConfig(id string) (*ContainerConfig, error) {
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddContainerExitCode adds the exit code for the specified container to the database.
|
||||||
|
func (s *BoltState) AddContainerExitCode(id string, exitCode int32) error {
|
||||||
|
if len(id) == 0 {
|
||||||
|
return define.ErrEmptyID
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.valid {
|
||||||
|
return define.ErrDBClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := s.getDBCon()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.deferredCloseDBCon(db)
|
||||||
|
|
||||||
|
rawID := []byte(id)
|
||||||
|
rawExitCode := []byte(strconv.Itoa(int(exitCode)))
|
||||||
|
rawTimeStamp, err := time.Now().MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshaling exit-code time stamp: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.Update(func(tx *bolt.Tx) error {
|
||||||
|
exitCodeBucket, err := getExitCodeBucket(tx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
timeStampBucket, err := getExitCodeTimeStampBucket(tx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := exitCodeBucket.Put(rawID, rawExitCode); err != nil {
|
||||||
|
return fmt.Errorf("adding exit code of container %s to DB: %w", id, err)
|
||||||
|
}
|
||||||
|
if err := timeStampBucket.Put(rawID, rawTimeStamp); err != nil {
|
||||||
|
if rmErr := exitCodeBucket.Delete(rawID); rmErr != nil {
|
||||||
|
logrus.Errorf("Removing exit code of container %s from DB: %v", id, rmErr)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("adding exit-code time stamp of container %s to DB: %w", id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContainerExitCode returns the exit code for the specified container.
|
||||||
|
func (s *BoltState) GetContainerExitCode(id string) (int32, error) {
|
||||||
|
if len(id) == 0 {
|
||||||
|
return -1, define.ErrEmptyID
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.valid {
|
||||||
|
return -1, define.ErrDBClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := s.getDBCon()
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
defer s.deferredCloseDBCon(db)
|
||||||
|
|
||||||
|
rawID := []byte(id)
|
||||||
|
result := int32(-1)
|
||||||
|
return result, db.View(func(tx *bolt.Tx) error {
|
||||||
|
exitCodeBucket, err := getExitCodeBucket(tx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rawExitCode := exitCodeBucket.Get(rawID)
|
||||||
|
if rawExitCode == nil {
|
||||||
|
return fmt.Errorf("getting exit code of container %s from DB: %w", id, define.ErrNoSuchExitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
exitCode, err := strconv.Atoi(string(rawExitCode))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("converting raw exit code %v of container %s: %w", rawExitCode, id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = int32(exitCode)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContainerExitCodeTimeStamp returns the time stamp when the exit code of
|
||||||
|
// the specified container was added to the database.
|
||||||
|
func (s *BoltState) GetContainerExitCodeTimeStamp(id string) (*time.Time, error) {
|
||||||
|
if len(id) == 0 {
|
||||||
|
return nil, define.ErrEmptyID
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.valid {
|
||||||
|
return nil, define.ErrDBClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := s.getDBCon()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer s.deferredCloseDBCon(db)
|
||||||
|
|
||||||
|
rawID := []byte(id)
|
||||||
|
var result time.Time
|
||||||
|
return &result, db.View(func(tx *bolt.Tx) error {
|
||||||
|
timeStampBucket, err := getExitCodeTimeStampBucket(tx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rawTimeStamp := timeStampBucket.Get(rawID)
|
||||||
|
if rawTimeStamp == nil {
|
||||||
|
return fmt.Errorf("getting exit-code time stamp of container %s from DB: %w", id, define.ErrNoSuchExitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := result.UnmarshalText(rawTimeStamp); err != nil {
|
||||||
|
return fmt.Errorf("converting raw time stamp %v of container %s from DB: %w", rawTimeStamp, id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// PruneExitCodes removes exit codes older than 5 minutes.
|
||||||
|
func (s *BoltState) PruneContainerExitCodes() error {
|
||||||
|
if !s.valid {
|
||||||
|
return define.ErrDBClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := s.getDBCon()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.deferredCloseDBCon(db)
|
||||||
|
|
||||||
|
toRemoveIDs := []string{}
|
||||||
|
|
||||||
|
threshold := time.Minute * 5
|
||||||
|
err = db.View(func(tx *bolt.Tx) error {
|
||||||
|
timeStampBucket, err := getExitCodeTimeStampBucket(tx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return timeStampBucket.ForEach(func(rawID, rawTimeStamp []byte) error {
|
||||||
|
var timeStamp time.Time
|
||||||
|
if err := timeStamp.UnmarshalText(rawTimeStamp); err != nil {
|
||||||
|
return fmt.Errorf("converting raw time stamp %v of container %s from DB: %w", rawTimeStamp, string(rawID), err)
|
||||||
|
}
|
||||||
|
if time.Since(timeStamp) > threshold {
|
||||||
|
toRemoveIDs = append(toRemoveIDs, string(rawID))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "reading exit codes to prune")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(toRemoveIDs) > 0 {
|
||||||
|
err = db.Update(func(tx *bolt.Tx) error {
|
||||||
|
exitCodeBucket, err := getExitCodeBucket(tx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
timeStampBucket, err := getExitCodeTimeStampBucket(tx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var finalErr error
|
||||||
|
for _, id := range toRemoveIDs {
|
||||||
|
rawID := []byte(id)
|
||||||
|
if err := exitCodeBucket.Delete(rawID); err != nil {
|
||||||
|
if finalErr != nil {
|
||||||
|
logrus.Error(finalErr)
|
||||||
|
}
|
||||||
|
finalErr = fmt.Errorf("removing exit code of container %s from DB: %w", id, err)
|
||||||
|
}
|
||||||
|
if err := timeStampBucket.Delete(rawID); err != nil {
|
||||||
|
if finalErr != nil {
|
||||||
|
logrus.Error(finalErr)
|
||||||
|
}
|
||||||
|
finalErr = fmt.Errorf("removing exit code timestamp of container %s from DB: %w", id, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalErr
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "pruning exit codes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// AddExecSession adds an exec session to the state.
|
// AddExecSession adds an exec session to the state.
|
||||||
func (s *BoltState) AddExecSession(ctr *Container, session *ExecSession) error {
|
func (s *BoltState) AddExecSession(ctr *Container, session *ExecSession) error {
|
||||||
if !s.valid {
|
if !s.valid {
|
||||||
|
|
|
@ -29,6 +29,9 @@ const (
|
||||||
aliasesName = "aliases"
|
aliasesName = "aliases"
|
||||||
runtimeConfigName = "runtime-config"
|
runtimeConfigName = "runtime-config"
|
||||||
|
|
||||||
|
exitCodeName = "exit-code"
|
||||||
|
exitCodeTimeStampName = "exit-code-time-stamp"
|
||||||
|
|
||||||
configName = "config"
|
configName = "config"
|
||||||
stateName = "state"
|
stateName = "state"
|
||||||
dependenciesName = "dependencies"
|
dependenciesName = "dependencies"
|
||||||
|
@ -65,6 +68,9 @@ var (
|
||||||
volDependenciesBkt = []byte(volCtrDependencies)
|
volDependenciesBkt = []byte(volCtrDependencies)
|
||||||
networksBkt = []byte(networksName)
|
networksBkt = []byte(networksName)
|
||||||
|
|
||||||
|
exitCodeBkt = []byte(exitCodeName)
|
||||||
|
exitCodeTimeStampBkt = []byte(exitCodeTimeStampName)
|
||||||
|
|
||||||
configKey = []byte(configName)
|
configKey = []byte(configName)
|
||||||
stateKey = []byte(stateName)
|
stateKey = []byte(stateName)
|
||||||
netNSKey = []byte(netNSName)
|
netNSKey = []byte(netNSName)
|
||||||
|
@ -362,6 +368,22 @@ func getRuntimeConfigBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
||||||
return bkt, nil
|
return bkt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getExitCodeBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
||||||
|
bkt := tx.Bucket(exitCodeBkt)
|
||||||
|
if bkt == nil {
|
||||||
|
return nil, errors.Wrapf(define.ErrDBBadConfig, "exit-code container bucket not found in DB")
|
||||||
|
}
|
||||||
|
return bkt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getExitCodeTimeStampBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
|
||||||
|
bkt := tx.Bucket(exitCodeTimeStampBkt)
|
||||||
|
if bkt == nil {
|
||||||
|
return nil, errors.Wrapf(define.ErrDBBadConfig, "exit-code time stamp bucket not found in DB")
|
||||||
|
}
|
||||||
|
return bkt, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *BoltState) getContainerConfigFromDB(id []byte, config *ContainerConfig, ctrsBkt *bolt.Bucket) error {
|
func (s *BoltState) getContainerConfigFromDB(id []byte, config *ContainerConfig, ctrsBkt *bolt.Bucket) error {
|
||||||
ctrBkt := ctrsBkt.Bucket(id)
|
ctrBkt := ctrsBkt.Bucket(id)
|
||||||
if ctrBkt == nil {
|
if ctrBkt == nil {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package libpod
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -490,41 +491,84 @@ func (c *Container) RemoveArtifact(name string) error {
|
||||||
|
|
||||||
// Wait blocks until the container exits and returns its exit code.
|
// Wait blocks until the container exits and returns its exit code.
|
||||||
func (c *Container) Wait(ctx context.Context) (int32, error) {
|
func (c *Container) Wait(ctx context.Context) (int32, error) {
|
||||||
return c.WaitWithInterval(ctx, DefaultWaitInterval)
|
return c.WaitForExit(ctx, DefaultWaitInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitWithInterval blocks until the container to exit and returns its exit
|
// WaitForExit blocks until the container exits and returns its exit code. The
|
||||||
// code. The argument is the interval at which checks the container's status.
|
// argument is the interval at which checks the container's status.
|
||||||
func (c *Container) WaitWithInterval(ctx context.Context, waitTimeout time.Duration) (int32, error) {
|
func (c *Container) WaitForExit(ctx context.Context, pollInterval time.Duration) (int32, error) {
|
||||||
if !c.valid {
|
if !c.valid {
|
||||||
return -1, define.ErrCtrRemoved
|
return -1, define.ErrCtrRemoved
|
||||||
}
|
}
|
||||||
|
|
||||||
exitFile, err := c.exitFilePath()
|
id := c.ID()
|
||||||
if err != nil {
|
var conmonTimer time.Timer
|
||||||
return -1, err
|
conmonTimerSet := false
|
||||||
}
|
|
||||||
chWait := make(chan error, 1)
|
|
||||||
|
|
||||||
go func() {
|
getExitCode := func() (bool, int32, error) {
|
||||||
<-ctx.Done()
|
containerRemoved := false
|
||||||
chWait <- define.ErrCanceled
|
if !c.batched {
|
||||||
}()
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.syncContainer(); err != nil {
|
||||||
|
if !errors.Is(err, define.ErrNoSuchCtr) {
|
||||||
|
return false, -1, err
|
||||||
|
}
|
||||||
|
containerRemoved = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If conmon is not alive anymore set a timer to make sure
|
||||||
|
// we're returning even if conmon has forcefully been killed.
|
||||||
|
if !conmonTimerSet && !containerRemoved {
|
||||||
|
conmonAlive, err := c.ociRuntime.CheckConmonRunning(c)
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, define.ErrNoSuchCtr):
|
||||||
|
containerRemoved = true
|
||||||
|
case err != nil:
|
||||||
|
return false, -1, err
|
||||||
|
case !conmonAlive:
|
||||||
|
timerDuration := time.Second * 20
|
||||||
|
conmonTimer = *time.NewTimer(timerDuration)
|
||||||
|
conmonTimerSet = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !containerRemoved {
|
||||||
|
// If conmon is dead for more than $timerDuration or if the
|
||||||
|
// container has exited properly, try to look up the exit code.
|
||||||
|
select {
|
||||||
|
case <-conmonTimer.C:
|
||||||
|
logrus.Debugf("Exceeded conmon timeout waiting for container %s to exit", id)
|
||||||
|
default:
|
||||||
|
if !c.ensureState(define.ContainerStateExited, define.ContainerStateConfigured) {
|
||||||
|
return false, -1, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exitCode, err := c.runtime.state.GetContainerExitCode(id)
|
||||||
|
if err != nil {
|
||||||
|
return true, -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, exitCode, nil
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// ignore errors here (with exception of cancellation), it is only used to avoid waiting
|
hasExited, exitCode, err := getExitCode()
|
||||||
// too long.
|
if hasExited {
|
||||||
_, e := WaitForFile(exitFile, chWait, waitTimeout)
|
return exitCode, err
|
||||||
if e == define.ErrCanceled {
|
|
||||||
return -1, define.ErrCanceled
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stopped, code, err := c.isStopped()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
if stopped {
|
select {
|
||||||
return code, nil
|
case <-ctx.Done():
|
||||||
|
return -1, fmt.Errorf("waiting for exit code of container %s canceled", id)
|
||||||
|
default:
|
||||||
|
time.Sleep(pollInterval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -551,12 +595,13 @@ func (c *Container) WaitForConditionWithInterval(ctx context.Context, waitTimeou
|
||||||
wantedStates := make(map[define.ContainerStatus]bool, len(conditions))
|
wantedStates := make(map[define.ContainerStatus]bool, len(conditions))
|
||||||
|
|
||||||
for _, condition := range conditions {
|
for _, condition := range conditions {
|
||||||
if condition == define.ContainerStateStopped || condition == define.ContainerStateExited {
|
switch condition {
|
||||||
|
case define.ContainerStateExited, define.ContainerStateStopped:
|
||||||
waitForExit = true
|
waitForExit = true
|
||||||
continue
|
default:
|
||||||
}
|
|
||||||
wantedStates[condition] = true
|
wantedStates[condition] = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
trySend := func(code int32, err error) {
|
trySend := func(code int32, err error) {
|
||||||
select {
|
select {
|
||||||
|
@ -572,7 +617,7 @@ func (c *Container) WaitForConditionWithInterval(ctx context.Context, waitTimeou
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
code, err := c.WaitWithInterval(ctx, waitTimeout)
|
code, err := c.WaitForExit(ctx, waitTimeout)
|
||||||
trySend(code, err)
|
trySend(code, err)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
|
@ -219,7 +219,7 @@ func (c *Container) handleExitFile(exitFile string, fi os.FileInfo) error {
|
||||||
// Write an event for the container's death
|
// Write an event for the container's death
|
||||||
c.newContainerExitedEvent(c.state.ExitCode)
|
c.newContainerExitedEvent(c.state.ExitCode)
|
||||||
|
|
||||||
return nil
|
return c.runtime.state.AddContainerExitCode(c.ID(), c.state.ExitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) shouldRestart() bool {
|
func (c *Container) shouldRestart() bool {
|
||||||
|
@ -784,20 +784,6 @@ func (c *Container) getArtifactPath(name string) string {
|
||||||
return filepath.Join(c.config.StaticDir, artifactsDir, name)
|
return filepath.Join(c.config.StaticDir, artifactsDir, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used with Wait() to determine if a container has exited
|
|
||||||
func (c *Container) isStopped() (bool, int32, error) {
|
|
||||||
if !c.batched {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
}
|
|
||||||
err := c.syncContainer()
|
|
||||||
if err != nil {
|
|
||||||
return true, -1, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return !c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused, define.ContainerStateStopping), c.state.ExitCode, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// save container state to the database
|
// save container state to the database
|
||||||
func (c *Container) save() error {
|
func (c *Container) save() error {
|
||||||
if err := c.runtime.state.SaveContainer(c); err != nil {
|
if err := c.runtime.state.SaveContainer(c); err != nil {
|
||||||
|
@ -1282,13 +1268,6 @@ func (c *Container) stop(timeout uint) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if conmon is still alive.
|
|
||||||
// If it is not, we won't be getting an exit file.
|
|
||||||
conmonAlive, err := c.ociRuntime.CheckConmonRunning(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the container state to "stopping" and unlock the container
|
// Set the container state to "stopping" and unlock the container
|
||||||
// before handing it over to conmon to unblock other commands. #8501
|
// before handing it over to conmon to unblock other commands. #8501
|
||||||
// demonstrates nicely that a high stop timeout will block even simple
|
// demonstrates nicely that a high stop timeout will block even simple
|
||||||
|
@ -1341,21 +1320,18 @@ func (c *Container) stop(timeout uint) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.newContainerEvent(events.Stop)
|
c.newContainerEvent(events.Stop)
|
||||||
|
|
||||||
c.state.PID = 0
|
|
||||||
c.state.ConmonPID = 0
|
|
||||||
c.state.StoppedByUser = true
|
c.state.StoppedByUser = true
|
||||||
|
|
||||||
|
conmonAlive, err := c.ociRuntime.CheckConmonRunning(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if !conmonAlive {
|
if !conmonAlive {
|
||||||
// Conmon is dead, so we can't expect an exit code.
|
if err := c.checkExitFile(); err != nil {
|
||||||
c.state.ExitCode = -1
|
return err
|
||||||
c.state.FinishedTime = time.Now()
|
|
||||||
c.state.State = define.ContainerStateStopped
|
|
||||||
if err := c.save(); err != nil {
|
|
||||||
logrus.Errorf("Saving container %s status: %v", c.ID(), err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.Wrapf(define.ErrConmonDead, "container %s conmon process missing, cannot retrieve exit code", c.ID())
|
return c.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.save(); err != nil {
|
if err := c.save(); err != nil {
|
||||||
|
@ -1939,6 +1915,18 @@ func (c *Container) cleanup(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prune the exit codes of other container during clean up.
|
||||||
|
// Since Podman is no daemon, we have to clean them up somewhere.
|
||||||
|
// Cleanup seems like a good place as it's not performance
|
||||||
|
// critical.
|
||||||
|
if err := c.runtime.state.PruneContainerExitCodes(); err != nil {
|
||||||
|
if lastError == nil {
|
||||||
|
lastError = err
|
||||||
|
} else {
|
||||||
|
logrus.Errorf("Pruning container exit codes: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return lastError
|
return lastError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,10 @@ var (
|
||||||
// not exist.
|
// not exist.
|
||||||
ErrNoSuchExecSession = errors.New("no such exec session")
|
ErrNoSuchExecSession = errors.New("no such exec session")
|
||||||
|
|
||||||
|
// ErrNoSuchExitCode indicates that the requested container exit code
|
||||||
|
// does not exist.
|
||||||
|
ErrNoSuchExitCode = errors.New("no such exit code")
|
||||||
|
|
||||||
// ErrDepExists indicates that the current object has dependencies and
|
// ErrDepExists indicates that the current object has dependencies and
|
||||||
// cannot be removed before them.
|
// cannot be removed before them.
|
||||||
ErrDepExists = errors.New("dependency exists")
|
ErrDepExists = errors.New("dependency exists")
|
||||||
|
|
|
@ -151,6 +151,9 @@ func (r *Runtime) GetEvents(ctx context.Context, filters []string) ([]*events.Ev
|
||||||
// GetLastContainerEvent takes a container name or ID and an event status and returns
|
// GetLastContainerEvent takes a container name or ID and an event status and returns
|
||||||
// the last occurrence of the container event
|
// the last occurrence of the container event
|
||||||
func (r *Runtime) GetLastContainerEvent(ctx context.Context, nameOrID string, containerEvent events.Status) (*events.Event, error) {
|
func (r *Runtime) GetLastContainerEvent(ctx context.Context, nameOrID string, containerEvent events.Status) (*events.Event, error) {
|
||||||
|
// FIXME: events should be read in reverse order!
|
||||||
|
// https://github.com/containers/podman/issues/14579
|
||||||
|
|
||||||
// check to make sure the event.Status is valid
|
// check to make sure the event.Status is valid
|
||||||
if _, err := events.StringToStatus(containerEvent.String()); err != nil {
|
if _, err := events.StringToStatus(containerEvent.String()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -264,11 +264,6 @@ func (r *ConmonOCIRuntime) CreateContainer(ctr *Container, restoreOptions *Conta
|
||||||
// status, but will instead only check for the existence of the conmon exit file
|
// status, but will instead only check for the existence of the conmon exit file
|
||||||
// and update state to stopped if it exists.
|
// and update state to stopped if it exists.
|
||||||
func (r *ConmonOCIRuntime) UpdateContainerStatus(ctr *Container) error {
|
func (r *ConmonOCIRuntime) UpdateContainerStatus(ctr *Container) error {
|
||||||
exitFile, err := r.ExitFilePath(ctr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
runtimeDir, err := util.GetRuntimeDir()
|
runtimeDir, err := util.GetRuntimeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -340,24 +335,12 @@ func (r *ConmonOCIRuntime) UpdateContainerStatus(ctr *Container) error {
|
||||||
// Only grab exit status if we were not already stopped
|
// Only grab exit status if we were not already stopped
|
||||||
// If we were, it should already be in the database
|
// If we were, it should already be in the database
|
||||||
if ctr.state.State == define.ContainerStateStopped && oldState != define.ContainerStateStopped {
|
if ctr.state.State == define.ContainerStateStopped && oldState != define.ContainerStateStopped {
|
||||||
var fi os.FileInfo
|
if _, err := ctr.Wait(context.Background()); err != nil {
|
||||||
chWait := make(chan error)
|
logrus.Errorf("Waiting for container %s to exit: %v", ctr.ID(), err)
|
||||||
defer close(chWait)
|
|
||||||
|
|
||||||
_, err := WaitForFile(exitFile, chWait, time.Second*5)
|
|
||||||
if err == nil {
|
|
||||||
fi, err = os.Stat(exitFile)
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
ctr.state.ExitCode = -1
|
|
||||||
ctr.state.FinishedTime = time.Now()
|
|
||||||
logrus.Errorf("No exit file for container %s found: %v", ctr.ID(), err)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctr.handleExitFile(exitFile, fi)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle ContainerStateStopping - keep it unless the container
|
// Handle ContainerStateStopping - keep it unless the container
|
||||||
// transitioned to no longer running.
|
// transitioned to no longer running.
|
||||||
if oldState == define.ContainerStateStopping && (ctr.state.State == define.ContainerStatePaused || ctr.state.State == define.ContainerStateRunning) {
|
if oldState == define.ContainerStateStopping && (ctr.state.State == define.ContainerStatePaused || ctr.state.State == define.ContainerStateRunning) {
|
||||||
|
@ -1166,7 +1149,6 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
|
||||||
}).Debugf("running conmon: %s", r.conmonPath)
|
}).Debugf("running conmon: %s", r.conmonPath)
|
||||||
|
|
||||||
cmd := exec.Command(r.conmonPath, args...)
|
cmd := exec.Command(r.conmonPath, args...)
|
||||||
cmd.Dir = ctr.bundlePath()
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
Setpgid: true,
|
Setpgid: true,
|
||||||
}
|
}
|
||||||
|
@ -1354,8 +1336,6 @@ func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, p
|
||||||
logDriverArg = define.NoLogging
|
logDriverArg = define.NoLogging
|
||||||
case define.PassthroughLogging:
|
case define.PassthroughLogging:
|
||||||
logDriverArg = define.PassthroughLogging
|
logDriverArg = define.PassthroughLogging
|
||||||
case define.JSONLogging:
|
|
||||||
fallthrough
|
|
||||||
//lint:ignore ST1015 the default case has to be here
|
//lint:ignore ST1015 the default case has to be here
|
||||||
default: //nolint:stylecheck,gocritic
|
default: //nolint:stylecheck,gocritic
|
||||||
// No case here should happen except JSONLogging, but keep this here in case the options are extended
|
// No case here should happen except JSONLogging, but keep this here in case the options are extended
|
||||||
|
@ -1365,6 +1345,8 @@ func (r *ConmonOCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, p
|
||||||
// to get here, either a user would specify `--log-driver ""`, or this came from another place in libpod
|
// to get here, either a user would specify `--log-driver ""`, or this came from another place in libpod
|
||||||
// since the former case is obscure, and the latter case isn't an error, let's silently fallthrough
|
// since the former case is obscure, and the latter case isn't an error, let's silently fallthrough
|
||||||
fallthrough
|
fallthrough
|
||||||
|
case define.JSONLogging:
|
||||||
|
fallthrough
|
||||||
case define.KubernetesLogging:
|
case define.KubernetesLogging:
|
||||||
logDriverArg = fmt.Sprintf("%s:%s", define.KubernetesLogging, logPath)
|
logDriverArg = fmt.Sprintf("%s:%s", define.KubernetesLogging, logPath)
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,6 +111,15 @@ type State interface {
|
||||||
// Return a container config from the database by full ID
|
// Return a container config from the database by full ID
|
||||||
GetContainerConfig(id string) (*ContainerConfig, error)
|
GetContainerConfig(id string) (*ContainerConfig, error)
|
||||||
|
|
||||||
|
// Add the exit code for the specified container to the database.
|
||||||
|
AddContainerExitCode(id string, exitCode int32) error
|
||||||
|
|
||||||
|
// Return the exit code for the specified container.
|
||||||
|
GetContainerExitCode(id string) (int32, error)
|
||||||
|
|
||||||
|
// Remove exit codes older than 5 minutes.
|
||||||
|
PruneContainerExitCodes() error
|
||||||
|
|
||||||
// Add creates a reference to an exec session in the database.
|
// Add creates a reference to an exec session in the database.
|
||||||
// The container the exec session is attached to will be recorded.
|
// The container the exec session is attached to will be recorded.
|
||||||
// The container state will not be modified.
|
// The container state will not be modified.
|
||||||
|
|
|
@ -191,7 +191,6 @@ func waitDockerCondition(ctx context.Context, containerName string, interval tim
|
||||||
var notRunningStates = []define.ContainerStatus{
|
var notRunningStates = []define.ContainerStatus{
|
||||||
define.ContainerStateCreated,
|
define.ContainerStateCreated,
|
||||||
define.ContainerStateRemoving,
|
define.ContainerStateRemoving,
|
||||||
define.ContainerStateStopped,
|
|
||||||
define.ContainerStateExited,
|
define.ContainerStateExited,
|
||||||
define.ContainerStateConfigured,
|
define.ContainerStateConfigured,
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ import (
|
||||||
"github.com/containers/image/v5/manifest"
|
"github.com/containers/image/v5/manifest"
|
||||||
"github.com/containers/podman/v4/libpod"
|
"github.com/containers/podman/v4/libpod"
|
||||||
"github.com/containers/podman/v4/libpod/define"
|
"github.com/containers/podman/v4/libpod/define"
|
||||||
"github.com/containers/podman/v4/libpod/events"
|
|
||||||
"github.com/containers/podman/v4/libpod/logs"
|
"github.com/containers/podman/v4/libpod/logs"
|
||||||
"github.com/containers/podman/v4/pkg/checkpoint"
|
"github.com/containers/podman/v4/pkg/checkpoint"
|
||||||
"github.com/containers/podman/v4/pkg/domain/entities"
|
"github.com/containers/podman/v4/pkg/domain/entities"
|
||||||
|
@ -939,6 +938,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri
|
||||||
}
|
}
|
||||||
return reports, errors.Wrapf(err, "unable to start container %s", ctr.ID())
|
return reports, errors.Wrapf(err, "unable to start container %s", ctr.ID())
|
||||||
}
|
}
|
||||||
|
|
||||||
exitCode = ic.GetContainerExitCode(ctx, ctr)
|
exitCode = ic.GetContainerExitCode(ctx, ctr)
|
||||||
reports = append(reports, &entities.ContainerStartReport{
|
reports = append(reports, &entities.ContainerStartReport{
|
||||||
Id: ctr.ID(),
|
Id: ctr.ID(),
|
||||||
|
@ -1099,25 +1099,11 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
|
||||||
|
|
||||||
func (ic *ContainerEngine) GetContainerExitCode(ctx context.Context, ctr *libpod.Container) int {
|
func (ic *ContainerEngine) GetContainerExitCode(ctx context.Context, ctr *libpod.Container) int {
|
||||||
exitCode, err := ctr.Wait(ctx)
|
exitCode, err := ctr.Wait(ctx)
|
||||||
if err == nil {
|
|
||||||
return int(exitCode)
|
|
||||||
}
|
|
||||||
if errors.Cause(err) != define.ErrNoSuchCtr {
|
|
||||||
logrus.Errorf("Could not retrieve exit code: %v", err)
|
|
||||||
return define.ExecErrorCodeNotFound
|
|
||||||
}
|
|
||||||
// Make 4 attempt with 0.25s backoff between each for 1 second total
|
|
||||||
var event *events.Event
|
|
||||||
for i := 0; i < 4; i++ {
|
|
||||||
event, err = ic.Libpod.GetLastContainerEvent(ctx, ctr.ID(), events.Exited)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
time.Sleep(250 * time.Millisecond)
|
logrus.Errorf("Waiting for container %s: %v", ctr.ID(), err)
|
||||||
continue
|
|
||||||
}
|
|
||||||
return event.ContainerExitCode
|
|
||||||
}
|
|
||||||
logrus.Errorf("Could not retrieve exit code from event: %v", err)
|
|
||||||
return define.ExecErrorCodeNotFound
|
return define.ExecErrorCodeNotFound
|
||||||
|
}
|
||||||
|
return int(exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []string, options entities.ContainerLogsOptions) error {
|
func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []string, options entities.ContainerLogsOptions) error {
|
||||||
|
|
|
@ -376,17 +376,7 @@ json-file | f
|
||||||
while read driver do_check; do
|
while read driver do_check; do
|
||||||
msg=$(random_string 15)
|
msg=$(random_string 15)
|
||||||
run_podman run --name myctr --log-driver $driver $IMAGE echo $msg
|
run_podman run --name myctr --log-driver $driver $IMAGE echo $msg
|
||||||
|
|
||||||
# Simple output check
|
|
||||||
# Special case: 'json-file' emits a warning, the rest do not
|
|
||||||
# ...but with podman-remote the warning is on the server only
|
|
||||||
if [[ $do_check == 'f' ]] && ! is_remote; then # 'f' for 'fallback'
|
|
||||||
is "${lines[0]}" ".* level=error msg=\"json-file logging specified but not supported. Choosing k8s-file logging instead\"" \
|
|
||||||
"Fallback warning emitted"
|
|
||||||
is "${lines[1]}" "$msg" "basic output sanity check (driver=$driver)"
|
|
||||||
else
|
|
||||||
is "$output" "$msg" "basic output sanity check (driver=$driver)"
|
is "$output" "$msg" "basic output sanity check (driver=$driver)"
|
||||||
fi
|
|
||||||
|
|
||||||
# Simply confirm that podman preserved our argument as-is
|
# Simply confirm that podman preserved our argument as-is
|
||||||
run_podman inspect --format '{{.HostConfig.LogConfig.Type}}' myctr
|
run_podman inspect --format '{{.HostConfig.LogConfig.Type}}' myctr
|
||||||
|
|
Loading…
Reference in New Issue