mirror of https://github.com/containers/podman.git
Merge pull request #1089 from mheon/add_exited
Record whether the container has exited
This commit is contained in:
commit
4729fd4255
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
101
cmd/podman/ps.go
101
cmd/podman/ps.go
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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])
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue