Merge pull request #1089 from mheon/add_exited

Record whether the container has exited
This commit is contained in:
Matthew Heon 2018-07-13 16:35:05 -04:00 committed by GitHub
commit 4729fd4255
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 102 additions and 74 deletions

View File

@ -35,12 +35,13 @@ type PsOptions struct {
// BatchContainerStruct is the return obkect from BatchContainer and contains // BatchContainerStruct is the return obkect from BatchContainer and contains
// container related information // container related information
type BatchContainerStruct struct { type BatchContainerStruct struct {
ConConfig *libpod.ContainerConfig ConConfig *libpod.ContainerConfig
ConState libpod.ContainerStatus ConState libpod.ContainerStatus
ExitCode int32 ExitCode int32
Pid int Exited bool
RootFsSize, RwSize int64 Pid int
StartedTime time.Time StartedTime time.Time
Size *ContainerSize
} }
// Namespace describes output for ps namespace // Namespace describes output for ps namespace
@ -55,17 +56,25 @@ type Namespace struct {
UTS string `json:"uts,omitempty"` UTS string `json:"uts,omitempty"`
} }
// ContainerSize holds the size of the container's root filesystem and top
// read-write layer
type ContainerSize struct {
RootFsSize int64 `json:"rootFsSize"`
RwSize int64 `json:"rwSize"`
}
// BatchContainer is used in ps to reduce performance hits by "batching" // BatchContainer is used in ps to reduce performance hits by "batching"
// locks. // locks.
func BatchContainerOp(ctr *libpod.Container, opts PsOptions) (BatchContainerStruct, error) { func BatchContainerOp(ctr *libpod.Container, opts PsOptions) (BatchContainerStruct, error) {
var ( var (
conConfig *libpod.ContainerConfig conConfig *libpod.ContainerConfig
conState libpod.ContainerStatus conState libpod.ContainerStatus
err error err error
exitCode int32 exitCode int32
pid int exited bool
rootFsSize, rwSize int64 pid int
startedTime time.Time size *ContainerSize
startedTime time.Time
) )
batchErr := ctr.Batch(func(c *libpod.Container) error { batchErr := ctr.Batch(func(c *libpod.Container) error {
@ -75,7 +84,7 @@ func BatchContainerOp(ctr *libpod.Container, opts PsOptions) (BatchContainerStru
return errors.Wrapf(err, "unable to obtain container state") return errors.Wrapf(err, "unable to obtain container state")
} }
exitCode, err = c.ExitCode() exitCode, exited, err = c.ExitCode()
if err != nil { if err != nil {
return errors.Wrapf(err, "unable to obtain container exit code") return errors.Wrapf(err, "unable to obtain container exit code")
} }
@ -95,16 +104,20 @@ func BatchContainerOp(ctr *libpod.Container, opts PsOptions) (BatchContainerStru
} }
} }
if opts.Size { if opts.Size {
rootFsSize, err = c.RootFsSize() size = new(ContainerSize)
rootFsSize, err := c.RootFsSize()
if err != nil { if err != nil {
logrus.Errorf("error getting root fs size for %q: %v", c.ID(), err) logrus.Errorf("error getting root fs size for %q: %v", c.ID(), err)
} }
rwSize, err = c.RWSize() rwSize, err := c.RWSize()
if err != nil { if err != nil {
logrus.Errorf("error getting rw size for %q: %v", c.ID(), err) logrus.Errorf("error getting rw size for %q: %v", c.ID(), err)
} }
size.RootFsSize = rootFsSize
size.RwSize = rwSize
} }
return nil return nil
}) })
@ -115,10 +128,10 @@ func BatchContainerOp(ctr *libpod.Container, opts PsOptions) (BatchContainerStru
ConConfig: conConfig, ConConfig: conConfig,
ConState: conState, ConState: conState,
ExitCode: exitCode, ExitCode: exitCode,
Exited: exited,
Pid: pid, Pid: pid,
RootFsSize: rootFsSize,
RwSize: rwSize,
StartedTime: startedTime, StartedTime: startedTime,
Size: size,
}, nil }, nil
} }

View File

@ -52,23 +52,23 @@ type psTemplateParams struct {
// psJSONParams will be populated by data from libpod.Container, // psJSONParams will be populated by data from libpod.Container,
// the members of the struct are the sama data types as their sources. // the members of the struct are the sama data types as their sources.
type psJSONParams struct { type psJSONParams struct {
ID string `json:"id"` ID string `json:"id"`
Image string `json:"image"` Image string `json:"image"`
ImageID string `json:"image_id"` ImageID string `json:"image_id"`
Command []string `json:"command"` Command []string `json:"command"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
ExitCode int32 `json:"exitCode"` ExitCode int32 `json:"exitCode"`
RunningFor time.Duration `json:"runningFor"` Exited bool `json:"exited"`
Status string `json:"status"` RunningFor time.Duration `json:"runningFor"`
PID int `json:"PID"` Status string `json:"status"`
Ports []ocicni.PortMapping `json:"ports"` PID int `json:"PID"`
RootFsSize int64 `json:"rootFsSize"` Ports []ocicni.PortMapping `json:"ports"`
RWSize int64 `json:"rwSize"` Size *batchcontainer.ContainerSize `json:"size,omitempty"`
Names string `json:"names"` Names string `json:"names"`
Labels fields.Set `json:"labels"` Labels fields.Set `json:"labels"`
Mounts []string `json:"mounts"` Mounts []string `json:"mounts"`
ContainerRunning bool `json:"ctrRunning"` ContainerRunning bool `json:"ctrRunning"`
Namespaces *batchcontainer.Namespace `json:"namespace,omitempty"` Namespaces *batchcontainer.Namespace `json:"namespace,omitempty"`
} }
// Type declaration and functions for sorting the PS output // Type declaration and functions for sorting the PS output
@ -114,7 +114,10 @@ func (a psSortedStatus) Less(i, j int) bool { return a.psSorted[i].Status < a.ps
type psSortedSize struct{ psSorted } type psSortedSize struct{ psSorted }
func (a psSortedSize) Less(i, j int) bool { func (a psSortedSize) Less(i, j int) bool {
return a.psSorted[i].RootFsSize < a.psSorted[j].RootFsSize if a.psSorted[i].Size == nil || a.psSorted[j].Size == nil {
return false
}
return a.psSorted[i].Size.RootFsSize < a.psSorted[j].Size.RootFsSize
} }
var ( var (
@ -279,22 +282,16 @@ func checkFlagsPassed(c *cli.Context) error {
if c.Int("last") >= 0 && c.Bool("latest") { if c.Int("last") >= 0 && c.Bool("latest") {
return errors.Errorf("last and latest are mutually exclusive") return errors.Errorf("last and latest are mutually exclusive")
} }
// quiet, size, namespace, and format with Go template are mutually exclusive // Quiet conflicts with size, namespace, and format with a Go template
flags := 0
if c.Bool("quiet") { if c.Bool("quiet") {
flags++ if c.Bool("size") || c.Bool("namespace") || (c.IsSet("format") &&
c.String("format") != formats.JSONString) {
return errors.Errorf("quiet conflicts with size, namespace, and format with go template")
}
} }
if c.Bool("size") { // Size and namespace conflict with each other
flags++ if c.Bool("size") && c.Bool("namespace") {
} return errors.Errorf("size and namespace options conflict")
if c.Bool("namespace") {
flags++
}
if c.IsSet("format") && c.String("format") != formats.JSONString {
flags++
}
if flags > 1 {
return errors.Errorf("quiet, size, namespace, and format with Go template are mutually exclusive")
} }
return nil return nil
} }
@ -324,8 +321,8 @@ func generateContainerFilterFuncs(filter, filterValue string, runtime *libpod.Ru
return nil, errors.Wrapf(err, "exited code out of range %q", filterValue) return nil, errors.Wrapf(err, "exited code out of range %q", filterValue)
} }
return func(c *libpod.Container) bool { return func(c *libpod.Container) bool {
ec, err := c.ExitCode() ec, exited, err := c.ExitCode()
if ec == int32(exitCode) && err == nil { if ec == int32(exitCode) && err == nil && exited == true {
return true return true
} }
return false return false
@ -496,7 +493,10 @@ func getTemplateOutput(psParams []psJSONParams, opts batchcontainer.PsOptions) (
ns = psParam.Namespaces ns = psParam.Namespaces
} }
if opts.Size { if opts.Size {
size = units.HumanSizeWithPrecision(float64(psParam.RWSize), 3) + " (virtual " + units.HumanSizeWithPrecision(float64(psParam.RootFsSize), 3) + ")" if psParam.Size == nil {
return nil, errors.Errorf("Container %s does not have a size struct", psParam.ID)
}
size = units.HumanSizeWithPrecision(float64(psParam.Size.RwSize), 3) + " (virtual " + units.HumanSizeWithPrecision(float64(psParam.Size.RootFsSize), 3) + ")"
} }
runningFor := units.HumanDuration(psParam.RunningFor) runningFor := units.HumanDuration(psParam.RunningFor)
@ -576,22 +576,25 @@ func getAndSortJSONParams(containers []*libpod.Container, opts batchcontainer.Ps
ns = batchcontainer.GetNamespaces(batchInfo.Pid) ns = batchcontainer.GetNamespaces(batchInfo.Pid)
} }
params := psJSONParams{ params := psJSONParams{
ID: ctr.ID(), ID: ctr.ID(),
Image: batchInfo.ConConfig.RootfsImageName, Image: batchInfo.ConConfig.RootfsImageName,
ImageID: batchInfo.ConConfig.RootfsImageID, ImageID: batchInfo.ConConfig.RootfsImageID,
Command: batchInfo.ConConfig.Spec.Process.Args, Command: batchInfo.ConConfig.Spec.Process.Args,
CreatedAt: batchInfo.ConConfig.CreatedTime, CreatedAt: batchInfo.ConConfig.CreatedTime,
Status: batchInfo.ConState.String(), ExitCode: batchInfo.ExitCode,
Ports: batchInfo.ConConfig.PortMappings, Exited: batchInfo.Exited,
RootFsSize: batchInfo.RootFsSize, Status: batchInfo.ConState.String(),
RWSize: batchInfo.RwSize, PID: batchInfo.Pid,
Names: batchInfo.ConConfig.Name, Ports: batchInfo.ConConfig.PortMappings,
Labels: batchInfo.ConConfig.Labels, Size: batchInfo.Size,
Mounts: batchInfo.ConConfig.UserVolumes, Names: batchInfo.ConConfig.Name,
Namespaces: ns, Labels: batchInfo.ConConfig.Labels,
Mounts: batchInfo.ConConfig.UserVolumes,
ContainerRunning: batchInfo.ConState == libpod.ContainerStateRunning,
Namespaces: ns,
} }
if !batchInfo.StartedTime.IsZero() { if !batchInfo.StartedTime.IsZero() && batchInfo.ConState == libpod.ContainerStateRunning {
params.RunningFor = time.Since(batchInfo.StartedTime) params.RunningFor = time.Since(batchInfo.StartedTime)
} }

View File

@ -135,6 +135,8 @@ type containerState struct {
FinishedTime time.Time `json:"finishedTime,omitempty"` FinishedTime time.Time `json:"finishedTime,omitempty"`
// ExitCode is the exit code returned when the container stopped // ExitCode is the exit code returned when the container stopped
ExitCode int32 `json:"exitCode,omitempty"` ExitCode int32 `json:"exitCode,omitempty"`
// Exited is whether the container has exited
Exited bool `json:"exited,omitempty"`
// OOMKilled indicates that the container was killed as it ran out of // OOMKilled indicates that the container was killed as it ran out of
// memory // memory
OOMKilled bool `json:"oomKilled,omitempty"` OOMKilled bool `json:"oomKilled,omitempty"`
@ -667,16 +669,18 @@ func (c *Container) FinishedTime() (time.Time, error) {
} }
// ExitCode returns the exit code of the container as // ExitCode returns the exit code of the container as
// an int32 // an int32, and whether the container has exited.
func (c *Container) ExitCode() (int32, error) { // If the container has not exited, exit code will always be 0.
// If the container restarts, the exit code is reset to 0.
func (c *Container) ExitCode() (int32, bool, error) {
if !c.batched { if !c.batched {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
if err := c.syncContainer(); err != nil { if err := c.syncContainer(); err != nil {
return 0, errors.Wrapf(err, "error updating container %s state", c.ID()) return 0, false, errors.Wrapf(err, "error updating container %s state", c.ID())
} }
} }
return c.state.ExitCode, nil return c.state.ExitCode, c.state.Exited, nil
} }
// OOMKilled returns whether the container was killed by an OOM condition // OOMKilled returns whether the container was killed by an OOM condition

View File

@ -586,6 +586,8 @@ func (c *Container) reinit(ctx context.Context) error {
// Set and save now to make sure that, if the init() below fails // Set and save now to make sure that, if the init() below fails
// we still have a valid state // we still have a valid state
c.state.State = ContainerStateConfigured c.state.State = ContainerStateConfigured
c.state.ExitCode = 0
c.state.Exited = false
if err := c.save(); err != nil { if err := c.save(); err != nil {
return err return err
} }

View File

@ -450,6 +450,7 @@ func (r *OCIRuntime) updateContainerStatus(ctr *Container) error {
ctr.state.OOMKilled = true ctr.state.OOMKilled = true
} }
ctr.state.Exited = true
} }
return nil return nil

View File

@ -65,13 +65,15 @@ func makeListContainer(containerID string, batchInfo batchcontainer.BatchContain
Runningfor: time.Since(batchInfo.ConConfig.CreatedTime).String(), Runningfor: time.Since(batchInfo.ConConfig.CreatedTime).String(),
Status: batchInfo.ConState.String(), Status: batchInfo.ConState.String(),
Ports: ports, Ports: ports,
Rootfssize: batchInfo.RootFsSize,
Rwsize: batchInfo.RwSize,
Names: batchInfo.ConConfig.Name, Names: batchInfo.ConConfig.Name,
Labels: batchInfo.ConConfig.Labels, Labels: batchInfo.ConConfig.Labels,
Mounts: mounts, Mounts: mounts,
Containerrunning: batchInfo.ConState == libpod.ContainerStateRunning, Containerrunning: batchInfo.ConState == libpod.ContainerStateRunning,
Namespaces: namespace, Namespaces: namespace,
} }
if batchInfo.Size != nil {
lc.Rootfssize = batchInfo.Size.RootFsSize
lc.Rwsize = batchInfo.Size.RwSize
}
return lc return lc
} }

View File

@ -201,12 +201,15 @@ var _ = Describe("Podman ps", func() {
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0)) Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"ps", "-a", "--sort=size", "--format", "{{.Size}}"}) session = podmanTest.Podman([]string{"ps", "-a", "-s", "--sort=size", "--format", "{{.Size}}"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0)) Expect(session.ExitCode()).To(Equal(0))
sortedArr := session.OutputToStringArray() sortedArr := session.OutputToStringArray()
// TODO: This may be broken - the test was running without the
// ability to perform any sorting for months and succeeded
// without error.
Expect(sort.SliceIsSorted(sortedArr, func(i, j int) bool { Expect(sort.SliceIsSorted(sortedArr, func(i, j int) bool {
r := regexp.MustCompile(`^\S+\s+\(virtual (\S+)\)`) r := regexp.MustCompile(`^\S+\s+\(virtual (\S+)\)`)
matches1 := r.FindStringSubmatch(sortedArr[i]) matches1 := r.FindStringSubmatch(sortedArr[i])