aboutsummaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
Diffstat (limited to 'libpod')
-rw-r--r--libpod/boltdb_state_internal.go13
-rw-r--r--libpod/container.go19
-rw-r--r--libpod/container_copy_linux.go12
-rw-r--r--libpod/container_internal.go42
-rw-r--r--libpod/container_internal_linux.go71
-rw-r--r--libpod/container_internal_linux_test.go8
-rw-r--r--libpod/container_internal_test.go12
-rw-r--r--libpod/define/info.go25
-rw-r--r--libpod/define/pod_inspect.go2
-rw-r--r--libpod/events/config.go6
-rw-r--r--libpod/events/events.go22
-rw-r--r--libpod/events/events_linux.go2
-rw-r--r--libpod/events/memory.go49
-rw-r--r--libpod/info.go75
-rw-r--r--libpod/info_test.go59
-rw-r--r--libpod/kube.go6
-rw-r--r--libpod/lock/file/file_lock_test.go11
-rw-r--r--libpod/networking_linux.go2
-rw-r--r--libpod/oci_attach_linux.go11
-rw-r--r--libpod/options.go18
-rw-r--r--libpod/pod.go4
-rw-r--r--libpod/pod_api.go51
-rw-r--r--libpod/runtime.go28
-rw-r--r--libpod/runtime_ctr.go4
-rw-r--r--libpod/runtime_pod_linux.go15
-rw-r--r--libpod/runtime_worker.go41
-rw-r--r--libpod/util.go7
27 files changed, 511 insertions, 104 deletions
diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go
index e43226490..d6f035af9 100644
--- a/libpod/boltdb_state_internal.go
+++ b/libpod/boltdb_state_internal.go
@@ -542,8 +542,12 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error {
ctr.ID(), s.namespace, ctr.config.Namespace)
}
+ // Set the original networks to nil. We can save some space by not storing it in the config
+ // since we store it in a different mutable bucket anyway.
+ configNetworks := ctr.config.Networks
+ ctr.config.Networks = nil
+
// JSON container structs to insert into DB
- // TODO use a higher-performance struct encoding than JSON
configJSON, err := json.Marshal(ctr.config)
if err != nil {
return errors.Wrapf(err, "error marshalling container %s config to JSON", ctr.ID())
@@ -564,8 +568,8 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error {
}
// make sure to marshal the network options before we get the db lock
- networks := make(map[string][]byte, len(ctr.config.Networks))
- for net, opts := range ctr.config.Networks {
+ networks := make(map[string][]byte, len(configNetworks))
+ for net, opts := range configNetworks {
// Check that we don't have any empty network names
if net == "" {
return errors.Wrapf(define.ErrInvalidArg, "network names cannot be an empty string")
@@ -581,9 +585,6 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error {
}
networks[net] = optBytes
}
- // Set the original value to nil. We can safe some space by not storing it in the config
- // since we store it in a different mutable bucket anyway.
- ctr.config.Networks = nil
db, err := s.getDBCon()
if err != nil {
diff --git a/libpod/container.go b/libpod/container.go
index 3e7ab7b0a..4b2af02ab 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -281,12 +281,29 @@ type ContainerNetworkDescriptions map[string]int
// Config accessors
// Unlocked
-// Config returns the configuration used to create the container
+// Config returns the configuration used to create the container.
+// Note that the returned config does not include the actual networks.
+// Use ConfigWithNetworks() if you need them.
func (c *Container) Config() *ContainerConfig {
returnConfig := new(ContainerConfig)
if err := JSONDeepCopy(c.config, returnConfig); err != nil {
return nil
}
+ return returnConfig
+}
+
+// Config returns the configuration used to create the container.
+func (c *Container) ConfigWithNetworks() *ContainerConfig {
+ returnConfig := c.Config()
+ if returnConfig == nil {
+ return nil
+ }
+
+ networks, err := c.networks()
+ if err != nil {
+ return nil
+ }
+ returnConfig.Networks = networks
return returnConfig
}
diff --git a/libpod/container_copy_linux.go b/libpod/container_copy_linux.go
index 91e712c74..7566fbb12 100644
--- a/libpod/container_copy_linux.go
+++ b/libpod/container_copy_linux.go
@@ -48,7 +48,11 @@ func (c *Container) copyFromArchive(path string, chown bool, rename map[string]s
if err != nil {
return nil, err
}
- unmount = func() { c.unmount(false) }
+ unmount = func() {
+ if err := c.unmount(false); err != nil {
+ logrus.Errorf("Failed to unmount container: %v", err)
+ }
+ }
}
if c.state.State == define.ContainerStateRunning {
@@ -117,7 +121,11 @@ func (c *Container) copyToArchive(path string, writer io.Writer) (func() error,
if err != nil {
return nil, err
}
- unmount = func() { c.unmount(false) }
+ unmount = func() {
+ if err := c.unmount(false); err != nil {
+ logrus.Errorf("Failed to unmount container: %v", err)
+ }
+ }
}
statInfo, resolvedRoot, resolvedPath, err := c.stat(mountPoint, path)
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 5c6719bdf..7494eb3ec 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -1939,9 +1939,51 @@ func (c *Container) cleanup(ctx context.Context) error {
}
}
+ if err := c.stopPodIfNeeded(context.Background()); err != nil {
+ if lastError == nil {
+ lastError = err
+ } else {
+ logrus.Errorf("Stopping pod of container %s: %v", c.ID(), err)
+ }
+ }
+
return lastError
}
+// If the container is part of a pod where only the infra container remains
+// running, attempt to stop the pod.
+func (c *Container) stopPodIfNeeded(ctx context.Context) error {
+ if c.config.Pod == "" {
+ return nil
+ }
+
+ pod, err := c.runtime.state.Pod(c.config.Pod)
+ if err != nil {
+ return fmt.Errorf("container %s is in pod %s, but pod cannot be retrieved: %w", c.ID(), c.config.Pod, err)
+ }
+
+ switch pod.config.ExitPolicy {
+ case config.PodExitPolicyContinue:
+ return nil
+
+ case config.PodExitPolicyStop:
+ // Use the runtime's work queue to stop the pod. This resolves
+ // a number of scenarios where we'd otherwise run into
+ // deadlocks. For instance, during `pod stop`, the pod has
+ // already been locked.
+ // The work queue is a simple means without having to worry about
+ // future changes that may introduce more deadlock scenarios.
+ c.runtime.queueWork(func() {
+ if err := pod.stopIfOnlyInfraRemains(ctx, c.ID()); err != nil {
+ if !errors.Is(err, define.ErrNoSuchPod) {
+ logrus.Errorf("Checking if infra needs to be stopped: %v", err)
+ }
+ }
+ })
+ }
+ return nil
+}
+
// delete deletes the container and runs any configured poststop
// hooks.
func (c *Container) delete(ctx context.Context) error {
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 31edff762..4742b22ab 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -1180,7 +1180,11 @@ func (c *Container) createCheckpointImage(ctx context.Context, options Container
return err
}
// Clean-up buildah working container
- defer importBuilder.Delete()
+ defer func() {
+ if err := importBuilder.Delete(); err != nil {
+ logrus.Errorf("Image builder delete failed: %v", err)
+ }
+ }()
if err := c.prepareCheckpointExport(); err != nil {
return err
@@ -1201,7 +1205,9 @@ func (c *Container) createCheckpointImage(ctx context.Context, options Container
// Copy checkpoint from temporary tar file in the image
addAndCopyOptions := buildah.AddAndCopyOptions{}
- importBuilder.Add("", true, addAndCopyOptions, options.TargetFile)
+ if err := importBuilder.Add("", true, addAndCopyOptions, options.TargetFile); err != nil {
+ return err
+ }
if err := c.addCheckpointImageMetadata(importBuilder); err != nil {
return err
@@ -1543,7 +1549,11 @@ func (c *Container) importCheckpointImage(ctx context.Context, imageID string) e
}
mountPoint, err := img.Mount(ctx, nil, "")
- defer img.Unmount(true)
+ defer func() {
+ if err := c.unmount(true); err != nil {
+ logrus.Errorf("Failed to unmount container: %v", err)
+ }
+ }()
if err != nil {
return err
}
@@ -2113,15 +2123,9 @@ func (c *Container) makeBindMounts() error {
}
} else {
if !c.config.UseImageResolvConf {
- newResolv, err := c.generateResolvConf()
- if err != nil {
+ if err := c.generateResolvConf(); err != nil {
return errors.Wrapf(err, "error creating resolv.conf for container %s", c.ID())
}
- err = c.mountIntoRootDirs("/etc/resolv.conf", newResolv)
-
- if err != nil {
- return errors.Wrapf(err, "error assigning mounts to container %s", c.ID())
- }
}
if !c.config.UseImageHosts {
@@ -2278,23 +2282,25 @@ rootless=%d
}
// generateResolvConf generates a containers resolv.conf
-func (c *Container) generateResolvConf() (string, error) {
+func (c *Container) generateResolvConf() error {
var (
nameservers []string
networkNameServers []string
networkSearchDomains []string
)
+ hostns := true
resolvConf := "/etc/resolv.conf"
for _, namespace := range c.config.Spec.Linux.Namespaces {
if namespace.Type == spec.NetworkNamespace {
+ hostns = false
if namespace.Path != "" && !strings.HasPrefix(namespace.Path, "/proc/") {
definedPath := filepath.Join("/etc/netns", filepath.Base(namespace.Path), "resolv.conf")
_, err := os.Stat(definedPath)
if err == nil {
resolvConf = definedPath
} else if !os.IsNotExist(err) {
- return "", err
+ return err
}
}
break
@@ -2304,17 +2310,17 @@ func (c *Container) generateResolvConf() (string, error) {
contents, err := ioutil.ReadFile(resolvConf)
// resolv.conf doesn't have to exists
if err != nil && !os.IsNotExist(err) {
- return "", err
+ return err
}
ns := resolvconf.GetNameservers(contents)
// check if systemd-resolved is used, assume it is used when 127.0.0.53 is the only nameserver
- if len(ns) == 1 && ns[0] == "127.0.0.53" {
+ if !hostns && len(ns) == 1 && ns[0] == "127.0.0.53" {
// read the actual resolv.conf file for systemd-resolved
resolvedContents, err := ioutil.ReadFile("/run/systemd/resolve/resolv.conf")
if err != nil {
if !os.IsNotExist(err) {
- return "", errors.Wrapf(err, "detected that systemd-resolved is in use, but could not locate real resolv.conf")
+ return errors.Wrapf(err, "detected that systemd-resolved is in use, but could not locate real resolv.conf")
}
} else {
contents = resolvedContents
@@ -2337,21 +2343,21 @@ func (c *Container) generateResolvConf() (string, error) {
ipv6, err := c.checkForIPv6(netStatus)
if err != nil {
- return "", err
+ return err
}
// Ensure that the container's /etc/resolv.conf is compatible with its
// network configuration.
- resolv, err := resolvconf.FilterResolvDNS(contents, ipv6, c.config.CreateNetNS)
+ resolv, err := resolvconf.FilterResolvDNS(contents, ipv6, !hostns)
if err != nil {
- return "", errors.Wrapf(err, "error parsing host resolv.conf")
+ return errors.Wrapf(err, "error parsing host resolv.conf")
}
dns := make([]net.IP, 0, len(c.runtime.config.Containers.DNSServers)+len(c.config.DNSServer))
for _, i := range c.runtime.config.Containers.DNSServers {
result := net.ParseIP(i)
if result == nil {
- return "", errors.Wrapf(define.ErrInvalidArg, "invalid IP address %s", i)
+ return errors.Wrapf(define.ErrInvalidArg, "invalid IP address %s", i)
}
dns = append(dns, result)
}
@@ -2402,20 +2408,15 @@ func (c *Container) generateResolvConf() (string, error) {
destPath := filepath.Join(c.state.RunDir, "resolv.conf")
if err := os.Remove(destPath); err != nil && !os.IsNotExist(err) {
- return "", errors.Wrapf(err, "container %s", c.ID())
+ return errors.Wrapf(err, "container %s", c.ID())
}
// Build resolv.conf
if _, err = resolvconf.Build(destPath, nameservers, search, options); err != nil {
- return "", errors.Wrapf(err, "error building resolv.conf for container %s", c.ID())
+ return errors.Wrapf(err, "error building resolv.conf for container %s", c.ID())
}
- // Relabel resolv.conf for the container
- if err := c.relabel(destPath, c.config.MountLabel, true); err != nil {
- return "", err
- }
-
- return destPath, nil
+ return c.bindMountRootFile(destPath, "/etc/resolv.conf")
}
// Check if a container uses IPv6.
@@ -2590,17 +2591,21 @@ func (c *Container) createHosts() error {
return err
}
- if err := os.Chown(targetFile, c.RootUID(), c.RootGID()); err != nil {
+ return c.bindMountRootFile(targetFile, config.DefaultHostsFile)
+}
+
+// bindMountRootFile will chown and relabel the source file to make it usable in the container.
+// It will also add the path to the container bind mount map.
+// source is the path on the host, dest is the path in the container.
+func (c *Container) bindMountRootFile(source, dest string) error {
+ if err := os.Chown(source, c.RootUID(), c.RootGID()); err != nil {
return err
}
- if err := label.Relabel(targetFile, c.MountLabel(), false); err != nil {
+ if err := label.Relabel(source, c.MountLabel(), false); err != nil {
return err
}
- if err = c.mountIntoRootDirs(config.DefaultHostsFile, targetFile); err != nil {
- return err
- }
- return nil
+ return c.mountIntoRootDirs(dest, source)
}
// generateGroupEntry generates an entry or entries into /etc/group as
diff --git a/libpod/container_internal_linux_test.go b/libpod/container_internal_linux_test.go
index 03095aa58..ed1f63a31 100644
--- a/libpod/container_internal_linux_test.go
+++ b/libpod/container_internal_linux_test.go
@@ -4,8 +4,6 @@
package libpod
import (
- "io/ioutil"
- "os"
"testing"
spec "github.com/opencontainers/runtime-spec/specs-go"
@@ -13,12 +11,6 @@ import (
)
func TestGenerateUserPasswdEntry(t *testing.T) {
- dir, err := ioutil.TempDir("", "libpod_test_")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(dir)
-
c := Container{
config: &ContainerConfig{
Spec: &spec.Spec{},
diff --git a/libpod/container_internal_test.go b/libpod/container_internal_test.go
index 2b50093b2..1b4e62e91 100644
--- a/libpod/container_internal_test.go
+++ b/libpod/container_internal_test.go
@@ -4,10 +4,8 @@ import (
"context"
"fmt"
"io/ioutil"
- "os"
"path/filepath"
"runtime"
- "strings"
"testing"
rspec "github.com/opencontainers/runtime-spec/specs-go"
@@ -19,11 +17,7 @@ var hookPath string
func TestPostDeleteHooks(t *testing.T) {
ctx := context.Background()
- dir, err := ioutil.TempDir("", "libpod_test_")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(dir)
+ dir := t.TempDir()
statePath := filepath.Join(dir, "state")
copyPath := filepath.Join(dir, "copy")
@@ -57,12 +51,12 @@ func TestPostDeleteHooks(t *testing.T) {
},
},
}
- err = c.postDeleteHooks(ctx)
+ err := c.postDeleteHooks(ctx)
if err != nil {
t.Fatal(err)
}
- stateRegexp := `{"ociVersion":"1\.0\.2-dev","id":"123abc","status":"stopped","bundle":"` + strings.TrimSuffix(os.TempDir(), "/") + `/libpod_test_[0-9]*","annotations":{"a":"b"}}`
+ stateRegexp := `{"ociVersion":"1\.0\.2-dev","id":"123abc","status":"stopped","bundle":"` + dir + `","annotations":{"a":"b"}}`
for _, p := range []string{statePath, copyPath} {
path := p
t.Run(path, func(t *testing.T) {
diff --git a/libpod/define/info.go b/libpod/define/info.go
index 713129ada..911fa5c03 100644
--- a/libpod/define/info.go
+++ b/libpod/define/info.go
@@ -1,6 +1,8 @@
package define
-import "github.com/containers/storage/pkg/idtools"
+import (
+ "github.com/containers/storage/pkg/idtools"
+)
// Info is the overall struct that describes the host system
// running libpod/podman
@@ -31,6 +33,7 @@ type HostInfo struct {
CgroupControllers []string `json:"cgroupControllers"`
Conmon *ConmonInfo `json:"conmon"`
CPUs int `json:"cpus"`
+ CPUUtilization *CPUUsage `json:"cpuUtilization"`
Distribution DistributionInfo `json:"distribution"`
EventLogger string `json:"eventLogger"`
Hostname string `json:"hostname"`
@@ -108,11 +111,15 @@ type StoreInfo struct {
GraphDriverName string `json:"graphDriverName"`
GraphOptions map[string]interface{} `json:"graphOptions"`
GraphRoot string `json:"graphRoot"`
- GraphStatus map[string]string `json:"graphStatus"`
- ImageCopyTmpDir string `json:"imageCopyTmpDir"`
- ImageStore ImageStore `json:"imageStore"`
- RunRoot string `json:"runRoot"`
- VolumePath string `json:"volumePath"`
+ // GraphRootAllocated is how much space the graphroot has in bytes
+ GraphRootAllocated uint64 `json:"graphRootAllocated"`
+ // GraphRootUsed is how much of graphroot is used in bytes
+ GraphRootUsed uint64 `json:"graphRootUsed"`
+ GraphStatus map[string]string `json:"graphStatus"`
+ ImageCopyTmpDir string `json:"imageCopyTmpDir"`
+ ImageStore ImageStore `json:"imageStore"`
+ RunRoot string `json:"runRoot"`
+ VolumePath string `json:"volumePath"`
}
// ImageStore describes the image store. Right now only the number
@@ -137,3 +144,9 @@ type Plugins struct {
// FIXME what should we do with Authorization, docker seems to return nothing by default
// Authorization []string `json:"authorization"`
}
+
+type CPUUsage struct {
+ UserPercent float64 `json:"userPercent"`
+ SystemPercent float64 `json:"systemPercent"`
+ IdlePercent float64 `json:"idlePercent"`
+}
diff --git a/libpod/define/pod_inspect.go b/libpod/define/pod_inspect.go
index e85a660a1..219ffade2 100644
--- a/libpod/define/pod_inspect.go
+++ b/libpod/define/pod_inspect.go
@@ -19,6 +19,8 @@ type InspectPodData struct {
// CreateCommand is the full command plus arguments of the process the
// container has been created with.
CreateCommand []string `json:"CreateCommand,omitempty"`
+ // ExitPolicy of the pod.
+ ExitPolicy string `json:"ExitPolicy,omitempty"`
// State represents the current state of the pod.
State string `json:"State"`
// Hostname is the hostname that the pod will set.
diff --git a/libpod/events/config.go b/libpod/events/config.go
index 35680a275..00cdca007 100644
--- a/libpod/events/config.go
+++ b/libpod/events/config.go
@@ -17,6 +17,8 @@ const (
Journald EventerType = iota
// Null is a no-op events logger. It does not read or write events.
Null EventerType = iota
+ // Memory indicates the event logger will hold events in memory
+ Memory EventerType = iota
)
// Event describes the attributes of a libpod event
@@ -55,7 +57,7 @@ type Details struct {
// EventerOptions describe options that need to be passed to create
// an eventer
type EventerOptions struct {
- // EventerType describes whether to use journald or a file
+ // EventerType describes whether to use journald, file or memory
EventerType string
// LogFilePath is the path to where the log file should reside if using
// the file logger
@@ -110,6 +112,8 @@ const (
System Type = "system"
// Volume - event is related to volumes
Volume Type = "volume"
+ // Machine - event is related to machine VM's
+ Machine Type = "machine"
// Attach ...
Attach Status = "attach"
diff --git a/libpod/events/events.go b/libpod/events/events.go
index 1745095fb..04417fd8d 100644
--- a/libpod/events/events.go
+++ b/libpod/events/events.go
@@ -20,6 +20,8 @@ func (et EventerType) String() string {
return "file"
case Journald:
return "journald"
+ case Memory:
+ return "memory"
case Null:
return "none"
default:
@@ -34,6 +36,8 @@ func IsValidEventer(eventer string) bool {
return true
case Journald.String():
return true
+ case Memory.String():
+ return true
case Null.String():
return true
default:
@@ -41,7 +45,7 @@ func IsValidEventer(eventer string) bool {
}
}
-// NewEvent creates a event struct and populates with
+// NewEvent creates an event struct and populates with
// the given status and time.
func NewEvent(status Status) Event {
return Event{
@@ -63,7 +67,7 @@ func (e *Event) ToJSONString() (string, error) {
return string(b), err
}
-// ToHumanReadable returns human readable event as a formatted string
+// ToHumanReadable returns human-readable event as a formatted string
func (e *Event) ToHumanReadable(truncate bool) string {
var humanFormat string
id := e.ID
@@ -90,7 +94,7 @@ func (e *Event) ToHumanReadable(truncate bool) string {
} else {
humanFormat = fmt.Sprintf("%s %s %s", e.Time, e.Type, e.Status)
}
- case Volume:
+ case Volume, Machine:
humanFormat = fmt.Sprintf("%s %s %s %s", e.Time, e.Type, e.Status, e.Name)
}
return humanFormat
@@ -99,19 +103,19 @@ func (e *Event) ToHumanReadable(truncate bool) string {
// NewEventFromString takes stringified json and converts
// it to an event
func newEventFromJSONString(event string) (*Event, error) {
- e := Event{}
- if err := json.Unmarshal([]byte(event), &e); err != nil {
+ e := new(Event)
+ if err := json.Unmarshal([]byte(event), e); err != nil {
return nil, err
}
- return &e, nil
+ return e, nil
}
-// ToString converts a Type to a string
+// String converts a Type to a string
func (t Type) String() string {
return string(t)
}
-// ToString converts a status to a string
+// String converts a status to a string
func (s Status) String() string {
return string(s)
}
@@ -123,6 +127,8 @@ func StringToType(name string) (Type, error) {
return Container, nil
case Image.String():
return Image, nil
+ case Machine.String():
+ return Machine, nil
case Network.String():
return Network, nil
case Pod.String():
diff --git a/libpod/events/events_linux.go b/libpod/events/events_linux.go
index 482d7d6dd..4320f2190 100644
--- a/libpod/events/events_linux.go
+++ b/libpod/events/events_linux.go
@@ -21,6 +21,8 @@ func NewEventer(options EventerOptions) (Eventer, error) {
return EventLogFile{options}, nil
case strings.ToUpper(Null.String()):
return NewNullEventer(), nil
+ case strings.ToUpper(Memory.String()):
+ return NewMemoryEventer(), nil
default:
return nil, errors.Errorf("unknown event logger type: %s", strings.ToUpper(options.EventerType))
}
diff --git a/libpod/events/memory.go b/libpod/events/memory.go
new file mode 100644
index 000000000..b3e03d86b
--- /dev/null
+++ b/libpod/events/memory.go
@@ -0,0 +1,49 @@
+package events
+
+import (
+ "context"
+)
+
+// EventMemory is the structure for event writing to a channel. It contains the eventer
+// options and the event itself. Methods for reading and writing are also defined from it.
+type EventMemory struct {
+ options EventerOptions
+ elements chan *Event
+}
+
+// Write event to memory queue
+func (e EventMemory) Write(event Event) (err error) {
+ e.elements <- &event
+ return
+}
+
+// Read event(s) from memory queue
+func (e EventMemory) Read(ctx context.Context, options ReadOptions) (err error) {
+ select {
+ case <-ctx.Done():
+ return
+ default:
+ }
+
+ select {
+ case event := <-e.elements:
+ options.EventChannel <- event
+ default:
+ }
+ return nil
+}
+
+// String returns eventer type
+func (e EventMemory) String() string {
+ return e.options.EventerType
+}
+
+// NewMemoryEventer returns configured MemoryEventer
+func NewMemoryEventer() Eventer {
+ return EventMemory{
+ options: EventerOptions{
+ EventerType: Memory.String(),
+ },
+ elements: make(chan *Event, 100),
+ }
+}
diff --git a/libpod/info.go b/libpod/info.go
index e0b490768..321680a81 100644
--- a/libpod/info.go
+++ b/libpod/info.go
@@ -5,11 +5,13 @@ import (
"bytes"
"fmt"
"io/ioutil"
+ "math"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
+ "syscall"
"time"
"github.com/containers/buildah"
@@ -115,7 +117,10 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) {
if err != nil {
return nil, errors.Wrapf(err, "error getting available cgroup controllers")
}
-
+ cpuUtil, err := getCPUUtilization()
+ if err != nil {
+ return nil, err
+ }
info := define.HostInfo{
Arch: runtime.GOARCH,
BuildahVersion: buildah.Version,
@@ -123,6 +128,7 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) {
CgroupControllers: availableControllers,
Linkmode: linkmode.Linkmode(),
CPUs: runtime.NumCPU(),
+ CPUUtilization: cpuUtil,
Distribution: hostDistributionInfo,
LogDriver: r.config.Containers.LogDriver,
EventLogger: r.eventer.String(),
@@ -285,17 +291,25 @@ func (r *Runtime) storeInfo() (*define.StoreInfo, error) {
}
imageInfo := define.ImageStore{Number: len(images)}
+ var grStats syscall.Statfs_t
+ if err := syscall.Statfs(r.store.GraphRoot(), &grStats); err != nil {
+ return nil, errors.Wrapf(err, "unable to collect graph root usasge for %q", r.store.GraphRoot())
+ }
+ allocated := uint64(grStats.Bsize) * grStats.Blocks
info := define.StoreInfo{
- ImageStore: imageInfo,
- ImageCopyTmpDir: os.Getenv("TMPDIR"),
- ContainerStore: conInfo,
- GraphRoot: r.store.GraphRoot(),
- RunRoot: r.store.RunRoot(),
- GraphDriverName: r.store.GraphDriverName(),
- GraphOptions: nil,
- VolumePath: r.config.Engine.VolumePath,
- ConfigFile: configFile,
+ ImageStore: imageInfo,
+ ImageCopyTmpDir: os.Getenv("TMPDIR"),
+ ContainerStore: conInfo,
+ GraphRoot: r.store.GraphRoot(),
+ GraphRootAllocated: allocated,
+ GraphRootUsed: allocated - (uint64(grStats.Bsize) * grStats.Bfree),
+ RunRoot: r.store.RunRoot(),
+ GraphDriverName: r.store.GraphDriverName(),
+ GraphOptions: nil,
+ VolumePath: r.config.Engine.VolumePath,
+ ConfigFile: configFile,
}
+
graphOptions := map[string]interface{}{}
for _, o := range r.store.GraphOptions() {
split := strings.SplitN(o, "=", 2)
@@ -382,3 +396,44 @@ func (r *Runtime) GetHostDistributionInfo() define.DistributionInfo {
}
return dist
}
+
+// getCPUUtilization Returns a CPUUsage object that summarizes CPU
+// usage for userspace, system, and idle time.
+func getCPUUtilization() (*define.CPUUsage, error) {
+ f, err := os.Open("/proc/stat")
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ scanner := bufio.NewScanner(f)
+ // Read firt line of /proc/stat
+ for scanner.Scan() {
+ break
+ }
+ // column 1 is user, column 3 is system, column 4 is idle
+ stats := strings.Split(scanner.Text(), " ")
+ return statToPercent(stats)
+}
+
+func statToPercent(stats []string) (*define.CPUUsage, error) {
+ // There is always an extra space between cpu and the first metric
+ userTotal, err := strconv.ParseFloat(stats[2], 64)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to parse user value %q", stats[1])
+ }
+ systemTotal, err := strconv.ParseFloat(stats[4], 64)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to parse system value %q", stats[3])
+ }
+ idleTotal, err := strconv.ParseFloat(stats[5], 64)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to parse idle value %q", stats[4])
+ }
+ total := userTotal + systemTotal + idleTotal
+ s := define.CPUUsage{
+ UserPercent: math.Round((userTotal/total*100)*100) / 100,
+ SystemPercent: math.Round((systemTotal/total*100)*100) / 100,
+ IdlePercent: math.Round((idleTotal/total*100)*100) / 100,
+ }
+ return &s, nil
+}
diff --git a/libpod/info_test.go b/libpod/info_test.go
new file mode 100644
index 000000000..909b573c0
--- /dev/null
+++ b/libpod/info_test.go
@@ -0,0 +1,59 @@
+package libpod
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/containers/podman/v4/libpod/define"
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_statToPercent(t *testing.T) {
+ type args struct {
+ in0 []string
+ }
+ tests := []struct {
+ name string
+ args args
+ want *define.CPUUsage
+ wantErr assert.ErrorAssertionFunc
+ }{
+ {
+ name: "GoodParse",
+ args: args{in0: []string{"cpu", " ", "33628064", "27537", "9696996", "1314806705", "588142", "4775073", "2789228", "0", "598711", "0"}},
+ want: &define.CPUUsage{
+ UserPercent: 2.48,
+ SystemPercent: 0.71,
+ IdlePercent: 96.81,
+ },
+ wantErr: assert.NoError,
+ },
+ {
+ name: "BadUserValue",
+ args: args{in0: []string{"cpu", " ", "k", "27537", "9696996", "1314806705", "588142", "4775073", "2789228", "0", "598711", "0"}},
+ want: nil,
+ wantErr: assert.Error,
+ },
+ {
+ name: "BadSystemValue",
+ args: args{in0: []string{"cpu", " ", "33628064", "27537", "k", "1314806705", "588142", "4775073", "2789228", "0", "598711", "0"}},
+ want: nil,
+ wantErr: assert.Error,
+ },
+ {
+ name: "BadIdleValue",
+ args: args{in0: []string{"cpu", " ", "33628064", "27537", "9696996", "k", "588142", "4775073", "2789228", "0", "598711", "0"}},
+ want: nil,
+ wantErr: assert.Error,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := statToPercent(tt.args.in0)
+ if !tt.wantErr(t, err, fmt.Sprintf("statToPercent(%v)", tt.args.in0)) {
+ return
+ }
+ assert.Equalf(t, tt.want, got, "statToPercent(%v)", tt.args.in0)
+ })
+ }
+}
diff --git a/libpod/kube.go b/libpod/kube.go
index 8b75a0c44..5a5fe9d35 100644
--- a/libpod/kube.go
+++ b/libpod/kube.go
@@ -1034,7 +1034,11 @@ func generateKubeSecurityContext(c *Container) (*v1.SecurityContext, error) {
if err != nil {
return nil, errors.Wrapf(err, "failed to mount %s mountpoint", c.ID())
}
- defer c.unmount(false)
+ defer func() {
+ if err := c.unmount(false); err != nil {
+ logrus.Errorf("Failed to unmount container: %v", err)
+ }
+ }()
}
logrus.Debugf("Looking in container for user: %s", c.User())
diff --git a/libpod/lock/file/file_lock_test.go b/libpod/lock/file/file_lock_test.go
index 7ac8bf31a..2d7dded23 100644
--- a/libpod/lock/file/file_lock_test.go
+++ b/libpod/lock/file/file_lock_test.go
@@ -2,7 +2,6 @@ package file
import (
"fmt"
- "io/ioutil"
"os"
"os/exec"
"path/filepath"
@@ -13,11 +12,9 @@ import (
// Test that creating and destroying locks work
func TestCreateAndDeallocate(t *testing.T) {
- d, err := ioutil.TempDir("", "filelock")
- assert.NoError(t, err)
- defer os.RemoveAll(d)
+ d := t.TempDir()
- _, err = OpenFileLock(filepath.Join(d, "locks"))
+ _, err := OpenFileLock(filepath.Join(d, "locks"))
assert.Error(t, err)
l, err := CreateFileLock(filepath.Join(d, "locks"))
@@ -47,9 +44,7 @@ func TestCreateAndDeallocate(t *testing.T) {
// Test that creating and destroying locks work
func TestLockAndUnlock(t *testing.T) {
- d, err := ioutil.TempDir("", "filelock")
- assert.NoError(t, err)
- defer os.RemoveAll(d)
+ d := t.TempDir()
l, err := CreateFileLock(filepath.Join(d, "locks"))
assert.NoError(t, err)
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index 2770b040e..0c124cf0b 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -488,7 +488,7 @@ func (r *Runtime) GetRootlessNetNs(new bool) (*RootlessNetNS, error) {
pid := strconv.Itoa(cmd.Process.Pid)
err = ioutil.WriteFile(filepath.Join(rootlessNetNsDir, rootlessNetNsSilrp4netnsPidFile), []byte(pid), 0700)
if err != nil {
- errors.Wrap(err, "unable to write rootless-netns slirp4netns pid file")
+ return nil, errors.Wrap(err, "unable to write rootless-netns slirp4netns pid file")
}
defer func() {
diff --git a/libpod/oci_attach_linux.go b/libpod/oci_attach_linux.go
index b5eabec1f..06f8f8719 100644
--- a/libpod/oci_attach_linux.go
+++ b/libpod/oci_attach_linux.go
@@ -9,6 +9,7 @@ import (
"net"
"os"
"path/filepath"
+ "syscall"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v4/libpod/define"
@@ -259,7 +260,7 @@ func redirectResponseToOutputStreams(outputStream, errorStream io.Writer, writeO
}
}
}
- if er == io.EOF {
+ if errors.Is(er, io.EOF) || errors.Is(er, syscall.ECONNRESET) {
break
}
if er != nil {
@@ -274,11 +275,15 @@ func readStdio(conn *net.UnixConn, streams *define.AttachStreams, receiveStdoutE
var err error
select {
case err = <-receiveStdoutError:
- conn.CloseWrite()
+ if err := conn.CloseWrite(); err != nil {
+ logrus.Errorf("Failed to close stdin: %v", err)
+ }
return err
case err = <-stdinDone:
if err == define.ErrDetach {
- conn.CloseWrite()
+ if err := conn.CloseWrite(); err != nil {
+ logrus.Errorf("Failed to close stdin: %v", err)
+ }
return err
}
if err == nil {
diff --git a/libpod/options.go b/libpod/options.go
index 98eb45e76..9b83cb76a 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -1843,6 +1843,24 @@ func WithPodName(name string) PodCreateOption {
}
}
+// WithPodExitPolicy sets the exit policy of the pod.
+func WithPodExitPolicy(policy string) PodCreateOption {
+ return func(pod *Pod) error {
+ if pod.valid {
+ return define.ErrPodFinalized
+ }
+
+ parsed, err := config.ParsePodExitPolicy(policy)
+ if err != nil {
+ return err
+ }
+
+ pod.config.ExitPolicy = parsed
+
+ return nil
+ }
+}
+
// WithPodHostname sets the hostname of the pod.
func WithPodHostname(hostname string) PodCreateOption {
return func(pod *Pod) error {
diff --git a/libpod/pod.go b/libpod/pod.go
index 237c42901..2211d5be7 100644
--- a/libpod/pod.go
+++ b/libpod/pod.go
@@ -6,6 +6,7 @@ import (
"strings"
"time"
+ "github.com/containers/common/pkg/config"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/libpod/lock"
"github.com/opencontainers/runtime-spec/specs-go"
@@ -70,6 +71,9 @@ type PodConfig struct {
// container has been created with.
CreateCommand []string `json:"CreateCommand,omitempty"`
+ // The pod's exit policy.
+ ExitPolicy config.PodExitPolicy `json:"ExitPolicy,omitempty"`
+
// ID of the pod's lock
LockID uint32 `json:"lockID"`
}
diff --git a/libpod/pod_api.go b/libpod/pod_api.go
index ba30d878e..73b28822b 100644
--- a/libpod/pod_api.go
+++ b/libpod/pod_api.go
@@ -2,6 +2,7 @@ package libpod
import (
"context"
+ "fmt"
"github.com/containers/common/pkg/cgroups"
"github.com/containers/podman/v4/libpod/define"
@@ -134,6 +135,10 @@ func (p *Pod) StopWithTimeout(ctx context.Context, cleanup bool, timeout int) (m
p.lock.Lock()
defer p.lock.Unlock()
+ return p.stopWithTimeout(ctx, cleanup, timeout)
+}
+
+func (p *Pod) stopWithTimeout(ctx context.Context, cleanup bool, timeout int) (map[string]error, error) {
if !p.valid {
return nil, define.ErrPodRemoved
}
@@ -195,6 +200,51 @@ func (p *Pod) StopWithTimeout(ctx context.Context, cleanup bool, timeout int) (m
return nil, nil
}
+// Stops the pod if only the infra containers remains running.
+func (p *Pod) stopIfOnlyInfraRemains(ctx context.Context, ignoreID string) error {
+ p.lock.Lock()
+ defer p.lock.Unlock()
+
+ infraID := ""
+
+ if p.HasInfraContainer() {
+ infra, err := p.infraContainer()
+ if err != nil {
+ return err
+ }
+ infraID = infra.ID()
+ }
+
+ allCtrs, err := p.runtime.state.PodContainers(p)
+ if err != nil {
+ return err
+ }
+
+ for _, ctr := range allCtrs {
+ if ctr.ID() == infraID || ctr.ID() == ignoreID {
+ continue
+ }
+
+ state, err := ctr.State()
+ if err != nil {
+ return fmt.Errorf("getting state of container %s: %w", ctr.ID(), err)
+ }
+
+ switch state {
+ case define.ContainerStateExited,
+ define.ContainerStateRemoving,
+ define.ContainerStateStopping,
+ define.ContainerStateUnknown:
+ continue
+ default:
+ return nil
+ }
+ }
+
+ _, err = p.stopWithTimeout(ctx, true, -1)
+ return err
+}
+
// Cleanup cleans up all containers within a pod that have stopped.
// All containers are cleaned up independently. An error with one container will
// not prevent other containers being cleaned up.
@@ -661,6 +711,7 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) {
Namespace: p.Namespace(),
Created: p.CreatedTime(),
CreateCommand: p.config.CreateCommand,
+ ExitPolicy: string(p.config.ExitPolicy),
State: podState,
Hostname: p.config.Hostname,
Labels: p.Labels(),
diff --git a/libpod/runtime.go b/libpod/runtime.go
index 6c2323d88..f4cd9cf00 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -86,6 +86,10 @@ type Runtime struct {
libimageEventsShutdown chan bool
lockManager lock.Manager
+ // Worker
+ workerShutdown chan bool
+ workerChannel chan func()
+
// syslog describes whenever logrus should log to the syslog as well.
// Note that the syslog hook will be enabled early in cmd/podman/syslog_linux.go
// This bool is just needed so that we can set it for netavark interface.
@@ -597,6 +601,8 @@ func makeRuntime(runtime *Runtime) (retErr error) {
}
}
+ runtime.startWorker()
+
// Mark the runtime as valid - ready to be used, cannot be modified
// further
runtime.valid = true
@@ -817,6 +823,14 @@ func (r *Runtime) Shutdown(force bool) error {
return define.ErrRuntimeStopped
}
+ if r.workerShutdown != nil {
+ // Signal the worker routine to shutdown. The routine will
+ // process all pending work items and then read from the
+ // channel; we're blocked until all work items have been
+ // processed.
+ r.workerShutdown <- true
+ }
+
r.valid = false
// Shutdown all containers if --force is given
@@ -1144,7 +1158,7 @@ func (r *Runtime) getVolumePlugin(name string) (*plugin.VolumePlugin, error) {
return plugin.GetVolumePlugin(name, pluginPath)
}
-// GetSecretsStoreageDir returns the directory that the secrets manager should take
+// GetSecretsStorageDir returns the directory that the secrets manager should take
func (r *Runtime) GetSecretsStorageDir() string {
return filepath.Join(r.store.GraphRoot(), "secrets")
}
@@ -1192,7 +1206,17 @@ func (r *Runtime) Network() nettypes.ContainerNetwork {
return r.network
}
-// Network returns the network interface which is used by the runtime
+// GetDefaultNetworkName returns the network interface which is used by the runtime
func (r *Runtime) GetDefaultNetworkName() string {
return r.config.Network.DefaultNetwork
}
+
+// RemoteURI returns the API server URI
+func (r *Runtime) RemoteURI() string {
+ return r.config.Engine.RemoteURI
+}
+
+// SetRemoteURI records the API server URI
+func (r *Runtime) SetRemoteURI(uri string) {
+ r.config.Engine.RemoteURI = uri
+}
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index fd3ffd199..df7174ac6 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -513,7 +513,9 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai
case define.NoLogging, define.PassthroughLogging:
break
case define.JournaldLogging:
- ctr.initializeJournal(ctx)
+ if err := ctr.initializeJournal(ctx); err != nil {
+ return nil, fmt.Errorf("failed to initialize journal: %w", err)
+ }
default:
if ctr.config.LogPath == "" {
ctr.config.LogPath = filepath.Join(ctr.config.StaticDir, "ctr.log")
diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go
index 2bbccfdf6..62ec7df60 100644
--- a/libpod/runtime_pod_linux.go
+++ b/libpod/runtime_pod_linux.go
@@ -199,10 +199,15 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool,
// Go through and lock all containers so we can operate on them all at
// once.
// First loop also checks that we are ready to go ahead and remove.
+ containersLocked := true
for _, ctr := range ctrs {
ctrLock := ctr.lock
ctrLock.Lock()
- defer ctrLock.Unlock()
+ defer func() {
+ if containersLocked {
+ ctrLock.Unlock()
+ }
+ }()
// If we're force-removing, no need to check status.
if force {
@@ -304,6 +309,12 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool,
}
}
+ // let's unlock the containers so if there is any cleanup process, it can terminate its execution
+ for _, ctr := range ctrs {
+ ctr.lock.Unlock()
+ }
+ containersLocked = false
+
// Remove pod cgroup, if present
if p.state.CgroupPath != "" {
logrus.Debugf("Removing pod cgroup %s", p.state.CgroupPath)
@@ -332,7 +343,7 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool,
}
}
if err == nil {
- if err := conmonCgroup.Delete(); err != nil {
+ if err = conmonCgroup.Delete(); err != nil {
if removalErr == nil {
removalErr = errors.Wrapf(err, "error removing pod %s conmon cgroup", p.ID())
} else {
diff --git a/libpod/runtime_worker.go b/libpod/runtime_worker.go
new file mode 100644
index 000000000..ca44a27f7
--- /dev/null
+++ b/libpod/runtime_worker.go
@@ -0,0 +1,41 @@
+package libpod
+
+import (
+ "time"
+)
+
+func (r *Runtime) startWorker() {
+ if r.workerChannel == nil {
+ r.workerChannel = make(chan func(), 1)
+ r.workerShutdown = make(chan bool)
+ }
+ go func() {
+ for {
+ // Make sure to read all workers before
+ // checking if we're about to shutdown.
+ for len(r.workerChannel) > 0 {
+ w := <-r.workerChannel
+ w()
+ }
+
+ select {
+ // We'll read from the shutdown channel only when all
+ // items above have been processed.
+ //
+ // (*Runtime).Shutdown() will block until until the
+ // item is read.
+ case <-r.workerShutdown:
+ return
+
+ default:
+ time.Sleep(100 * time.Millisecond)
+ }
+ }
+ }()
+}
+
+func (r *Runtime) queueWork(f func()) {
+ go func() {
+ r.workerChannel <- f
+ }()
+}
diff --git a/libpod/util.go b/libpod/util.go
index 51fe60427..1753b4f34 100644
--- a/libpod/util.go
+++ b/libpod/util.go
@@ -55,8 +55,11 @@ func WaitForFile(path string, chWait chan error, timeout time.Duration) (bool, e
if err := watcher.Add(filepath.Dir(path)); err == nil {
inotifyEvents = watcher.Events
}
- defer watcher.Close()
- defer watcher.Remove(filepath.Dir(path))
+ defer func() {
+ if err := watcher.Close(); err != nil {
+ logrus.Errorf("Failed to close fsnotify watcher: %v", err)
+ }
+ }()
}
var timeoutChan <-chan time.Time