summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/containers/checkpoint.go34
-rw-r--r--cmd/podman/containers/restore.go34
-rw-r--r--cmd/podman/generate/systemd.go11
-rw-r--r--docs/source/markdown/podman-container-checkpoint.1.md36
-rw-r--r--docs/source/markdown/podman-container-restore.1.md26
-rw-r--r--docs/source/markdown/podman-generate-systemd.1.md6
-rw-r--r--libpod/container_api.go24
-rw-r--r--libpod/container_internal.go2
-rw-r--r--libpod/container_internal_linux.go159
-rw-r--r--libpod/container_log_linux.go26
-rw-r--r--libpod/define/checkpoint_restore.go32
-rw-r--r--libpod/oci.go11
-rw-r--r--libpod/oci_conmon_linux.go96
-rw-r--r--libpod/oci_missing.go8
-rw-r--r--pkg/api/handlers/libpod/containers.go28
-rw-r--r--pkg/api/handlers/libpod/generate.go34
-rw-r--r--pkg/api/server/register_containers.go8
-rw-r--r--pkg/bindings/connection.go6
-rw-r--r--pkg/bindings/containers/archive.go6
-rw-r--r--pkg/bindings/containers/attach.go8
-rw-r--r--pkg/bindings/containers/checkpoint.go4
-rw-r--r--pkg/bindings/containers/commit.go2
-rw-r--r--pkg/bindings/containers/containers.go34
-rw-r--r--pkg/bindings/containers/create.go2
-rw-r--r--pkg/bindings/containers/diff.go2
-rw-r--r--pkg/bindings/containers/exec.go6
-rw-r--r--pkg/bindings/containers/healthcheck.go2
-rw-r--r--pkg/bindings/containers/logs.go2
-rw-r--r--pkg/bindings/containers/mount.go6
-rw-r--r--pkg/bindings/containers/rename.go2
-rw-r--r--pkg/bindings/generate/generate.go4
-rw-r--r--pkg/bindings/generate/types.go2
-rw-r--r--pkg/bindings/generate/types_systemd_options.go15
-rw-r--r--pkg/bindings/images/build.go2
-rw-r--r--pkg/bindings/images/diff.go2
-rw-r--r--pkg/bindings/images/images.go26
-rw-r--r--pkg/bindings/images/pull.go2
-rw-r--r--pkg/bindings/images/rm.go2
-rw-r--r--pkg/bindings/manifests/manifests.go14
-rw-r--r--pkg/bindings/network/network.go16
-rw-r--r--pkg/bindings/play/play.go4
-rw-r--r--pkg/bindings/pods/pods.go28
-rw-r--r--pkg/bindings/secrets/secrets.go8
-rw-r--r--pkg/bindings/system/info.go2
-rw-r--r--pkg/bindings/system/system.go8
-rw-r--r--pkg/bindings/test/connection_test.go68
-rw-r--r--pkg/bindings/volumes/volumes.go12
-rw-r--r--pkg/domain/entities/containers.go14
-rw-r--r--pkg/domain/entities/generate.go2
-rw-r--r--pkg/domain/infra/abi/containers.go18
-rw-r--r--pkg/domain/infra/tunnel/generate.go2
-rw-r--r--pkg/systemd/generate/common.go4
-rw-r--r--pkg/systemd/generate/containers.go52
-rw-r--r--pkg/systemd/generate/containers_test.go119
-rw-r--r--pkg/systemd/generate/pods.go5
-rw-r--r--test/e2e/checkpoint_test.go95
-rw-r--r--test/system/035-logs.bats21
-rw-r--r--test/system/090-events.bats11
-rw-r--r--test/system/250-systemd.bats59
-rw-r--r--test/system/500-networking.bats2
60 files changed, 1017 insertions, 259 deletions
diff --git a/cmd/podman/containers/checkpoint.go b/cmd/podman/containers/checkpoint.go
index 4fa72d520..d92bc3e5e 100644
--- a/cmd/podman/containers/checkpoint.go
+++ b/cmd/podman/containers/checkpoint.go
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"strings"
+ "time"
"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v3/cmd/podman/common"
@@ -40,6 +41,11 @@ var (
var checkpointOptions entities.CheckpointOptions
+type checkpointStatistics struct {
+ PodmanDuration int64 `json:"podman_checkpoint_duration"`
+ ContainerStatistics []*entities.CheckpointReport `json:"container_statistics"`
+}
+
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Command: checkpointCommand,
@@ -63,11 +69,19 @@ func init() {
flags.StringP("compress", "c", "zstd", "Select compression algorithm (gzip, none, zstd) for checkpoint archive.")
_ = checkpointCommand.RegisterFlagCompletionFunc("compress", common.AutocompleteCheckpointCompressType)
+ flags.BoolVar(
+ &checkpointOptions.PrintStats,
+ "print-stats",
+ false,
+ "Display checkpoint statistics",
+ )
+
validate.AddLatestFlag(checkpointCommand, &checkpointOptions.Latest)
}
func checkpoint(cmd *cobra.Command, args []string) error {
var errs utils.OutputErrors
+ podmanStart := time.Now()
if cmd.Flags().Changed("compress") {
if checkpointOptions.Export == "" {
return errors.Errorf("--compress can only be used with --export")
@@ -102,12 +116,30 @@ func checkpoint(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
+ podmanFinished := time.Now()
+
+ var statistics checkpointStatistics
+
for _, r := range responses {
if r.Err == nil {
- fmt.Println(r.Id)
+ if checkpointOptions.PrintStats {
+ statistics.ContainerStatistics = append(statistics.ContainerStatistics, r)
+ } else {
+ fmt.Println(r.Id)
+ }
} else {
errs = append(errs, r.Err)
}
}
+
+ if checkpointOptions.PrintStats {
+ statistics.PodmanDuration = podmanFinished.Sub(podmanStart).Microseconds()
+ j, err := json.MarshalIndent(statistics, "", " ")
+ if err != nil {
+ return err
+ }
+ fmt.Println(string(j))
+ }
+
return errs.PrintErrors()
}
diff --git a/cmd/podman/containers/restore.go b/cmd/podman/containers/restore.go
index 05214f32c..217adc887 100644
--- a/cmd/podman/containers/restore.go
+++ b/cmd/podman/containers/restore.go
@@ -3,6 +3,7 @@ package containers
import (
"context"
"fmt"
+ "time"
"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v3/cmd/podman/common"
@@ -39,6 +40,11 @@ var (
var restoreOptions entities.RestoreOptions
+type restoreStatistics struct {
+ PodmanDuration int64 `json:"podman_restore_duration"`
+ ContainerStatistics []*entities.RestoreReport `json:"container_statistics"`
+}
+
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Command: restoreCommand,
@@ -75,11 +81,19 @@ func init() {
flags.StringVar(&restoreOptions.Pod, "pod", "", "Restore container into existing Pod (only works with --import)")
_ = restoreCommand.RegisterFlagCompletionFunc("pod", common.AutocompletePodsRunning)
+ flags.BoolVar(
+ &restoreOptions.PrintStats,
+ "print-stats",
+ false,
+ "Display restore statistics",
+ )
+
validate.AddLatestFlag(restoreCommand, &restoreOptions.Latest)
}
func restore(cmd *cobra.Command, args []string) error {
var errs utils.OutputErrors
+ podmanStart := time.Now()
if rootless.IsRootless() {
return errors.New("restoring a container requires root")
}
@@ -132,12 +146,30 @@ func restore(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
+ podmanFinished := time.Now()
+
+ var statistics restoreStatistics
+
for _, r := range responses {
if r.Err == nil {
- fmt.Println(r.Id)
+ if restoreOptions.PrintStats {
+ statistics.ContainerStatistics = append(statistics.ContainerStatistics, r)
+ } else {
+ fmt.Println(r.Id)
+ }
} else {
errs = append(errs, r.Err)
}
}
+
+ if restoreOptions.PrintStats {
+ statistics.PodmanDuration = podmanFinished.Sub(podmanStart).Microseconds()
+ j, err := json.MarshalIndent(statistics, "", " ")
+ if err != nil {
+ return err
+ }
+ fmt.Println(string(j))
+ }
+
return errs.PrintErrors()
}
diff --git a/cmd/podman/generate/systemd.go b/cmd/podman/generate/systemd.go
index 2ab33c26b..cdc103865 100644
--- a/cmd/podman/generate/systemd.go
+++ b/cmd/podman/generate/systemd.go
@@ -21,6 +21,7 @@ import (
const (
restartPolicyFlagName = "restart-policy"
timeFlagName = "time"
+ newFlagName = "new"
)
var (
@@ -53,10 +54,11 @@ func init() {
flags := systemdCmd.Flags()
flags.BoolVarP(&systemdOptions.Name, "name", "n", false, "Use container/pod names instead of IDs")
flags.BoolVarP(&files, "files", "f", false, "Generate .service files instead of printing to stdout")
+ flags.BoolVar(&systemdOptions.TemplateUnitFile, "template", false, "Make it a template file and use %i and %I specifiers. Working only for containers")
flags.UintVarP(&systemdTimeout, timeFlagName, "t", containerConfig.Engine.StopTimeout, "Stop timeout override")
_ = systemdCmd.RegisterFlagCompletionFunc(timeFlagName, completion.AutocompleteNone)
- flags.BoolVarP(&systemdOptions.New, "new", "", false, "Create a new container or pod instead of starting an existing one")
+ flags.BoolVar(&systemdOptions.New, newFlagName, false, "Create a new container or pod instead of starting an existing one")
flags.BoolVarP(&systemdOptions.NoHeader, "no-header", "", false, "Skip header generation")
containerPrefixFlagName := "container-prefix"
@@ -93,6 +95,13 @@ func systemd(cmd *cobra.Command, args []string) error {
logrus.Warnln("The generated units should be placed on your remote system")
}
+ if cmd.Flags().Changed(newFlagName) && !systemdOptions.New && systemdOptions.TemplateUnitFile {
+ return errors.New("--template cannot be set with --new=false")
+ }
+ if !systemdOptions.New && systemdOptions.TemplateUnitFile {
+ systemdOptions.New = true
+ }
+
reports, err := registry.ContainerEngine().GenerateSystemd(registry.GetContext(), args[0], systemdOptions)
if err != nil {
return err
diff --git a/docs/source/markdown/podman-container-checkpoint.1.md b/docs/source/markdown/podman-container-checkpoint.1.md
index 56fd848ac..1faa40a94 100644
--- a/docs/source/markdown/podman-container-checkpoint.1.md
+++ b/docs/source/markdown/podman-container-checkpoint.1.md
@@ -68,6 +68,40 @@ Dump the *container's* memory information only, leaving the *container* running.
operations will supersede prior dumps. It only works on `runc 1.0-rc3` or `higher`.\
The default is **false**.
+#### **--print-stats**
+
+Print out statistics about checkpointing the container(s). The output is
+rendered in a JSON array and contains information about how much time different
+checkpoint operations required. Many of the checkpoint statistics are created
+by CRIU and just passed through to Podman. The following information is provided
+in the JSON array:
+
+- **podman_checkpoint_duration**: Overall time (in microseconds) needed to create
+ all checkpoints.
+
+- **runtime_checkpoint_duration**: Time (in microseconds) the container runtime
+ needed to create the checkpoint.
+
+- **freezing_time**: Time (in microseconds) CRIU needed to pause (freeze) all
+ processes in the container (measured by CRIU).
+
+- **frozen_time**: Time (in microseconds) all processes in the container were
+ paused (measured by CRIU).
+
+- **memdump_time**: Time (in microseconds) needed to extract all required memory
+ pages from all container processes (measured by CRIU).
+
+- **memwrite_time**: Time (in microseconds) needed to write all required memory
+ pages to the corresponding checkpoint image files (measured by CRIU).
+
+- **pages_scanned**: Number of memory pages scanned to determine if they need
+ to be checkpointed (measured by CRIU).
+
+- **pages_written**: Number of memory pages actually written to the checkpoint
+ image files (measured by CRIU).
+
+The default is **false**.
+
#### **--tcp-established**
Checkpoint a *container* with established TCP connections. If the checkpoint
@@ -106,7 +140,7 @@ Dump the container's memory information of the latest container into an archive
```
## SEE ALSO
-**[podman(1)](podman.1.md)**, **[podman-container-restore(1)](podman-container-restore.1.md)**
+**[podman(1)](podman.1.md)**, **[podman-container-restore(1)](podman-container-restore.1.md)**, **criu(8)**
## HISTORY
September 2018, Originally compiled by Adrian Reber <areber@redhat.com>
diff --git a/docs/source/markdown/podman-container-restore.1.md b/docs/source/markdown/podman-container-restore.1.md
index 856008cc0..962770ea0 100644
--- a/docs/source/markdown/podman-container-restore.1.md
+++ b/docs/source/markdown/podman-container-restore.1.md
@@ -102,6 +102,30 @@ from (see **[podman pod create --share](podman-pod-create.1.md#--share)**).
This option requires at least CRIU 3.16.
+#### **--print-stats**
+
+Print out statistics about restoring the container(s). The output is
+rendered in a JSON array and contains information about how much time different
+restore operations required. Many of the restore statistics are created
+by CRIU and just passed through to Podman. The following information is provided
+in the JSON array:
+
+- **podman_restore_duration**: Overall time (in microseconds) needed to restore
+ all checkpoints.
+
+- **runtime_restore_duration**: Time (in microseconds) the container runtime
+ needed to restore the checkpoint.
+
+- **forking_time**: Time (in microseconds) CRIU needed to create (fork) all
+ processes in the restored container (measured by CRIU).
+
+- **restore_time**: Time (in microseconds) CRIU needed to restore all processes
+ in the container (measured by CRIU).
+
+- **pages_restored**: Number of memory pages restored (measured by CRIU).
+
+The default is **false**.
+
#### **--publish**, **-p**=*port*
Replaces the ports that the *container* publishes, as configured during the
@@ -137,7 +161,7 @@ $ podman run --rm -p 2345:80 -d webserver
```
## SEE ALSO
-**[podman(1)](podman.1.md)**, **[podman-container-checkpoint(1)](podman-container-checkpoint.1.md)**, **[podman-run(1)](podman-run.1.md)**, **[podman-pod-create(1)](podman-pod-create.1.md)**
+**[podman(1)](podman.1.md)**, **[podman-container-checkpoint(1)](podman-container-checkpoint.1.md)**, **[podman-run(1)](podman-run.1.md)**, **[podman-pod-create(1)](podman-pod-create.1.md)**, **criu(8)**
## HISTORY
September 2018, Originally compiled by Adrian Reber <areber@redhat.com>
diff --git a/docs/source/markdown/podman-generate-systemd.1.md b/docs/source/markdown/podman-generate-systemd.1.md
index 0acbb9d8c..356ac0629 100644
--- a/docs/source/markdown/podman-generate-systemd.1.md
+++ b/docs/source/markdown/podman-generate-systemd.1.md
@@ -59,6 +59,12 @@ Set the systemd unit name prefix for pods. The default is *pod*.
Set the systemd unit name separator between the name/id of a container/pod and the prefix. The default is *-*.
+#### **--template**
+
+Add template specifiers to run multiple services from the systemd unit file.
+
+Note that if `--new` was not set to true, it is set to true by default. However, if `--new` is set to `false` explicitly the command will fail.
+
## EXAMPLES
### Generate and print a systemd unit file for a container
diff --git a/libpod/container_api.go b/libpod/container_api.go
index 38223316e..a41bb03df 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -794,21 +794,29 @@ type ContainerCheckpointOptions struct {
// container no PID 1 will be in the namespace and that is not
// possible.
Pod string
+ // PrintStats tells the API to fill out the statistics about
+ // how much time each component in the stack requires to
+ // checkpoint a container.
+ PrintStats bool
}
// Checkpoint checkpoints a container
-func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointOptions) error {
+// The return values *define.CRIUCheckpointRestoreStatistics and int64 (time
+// the runtime needs to checkpoint the container) are only set if
+// options.PrintStats is set to true. Not setting options.PrintStats to true
+// will return nil and 0.
+func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointOptions) (*define.CRIUCheckpointRestoreStatistics, int64, error) {
logrus.Debugf("Trying to checkpoint container %s", c.ID())
if options.TargetFile != "" {
if err := c.prepareCheckpointExport(); err != nil {
- return err
+ return nil, 0, err
}
}
if options.WithPrevious {
if err := c.canWithPrevious(); err != nil {
- return err
+ return nil, 0, err
}
}
@@ -817,14 +825,18 @@ func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointO
defer c.lock.Unlock()
if err := c.syncContainer(); err != nil {
- return err
+ return nil, 0, err
}
}
return c.checkpoint(ctx, options)
}
// Restore restores a container
-func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOptions) error {
+// The return values *define.CRIUCheckpointRestoreStatistics and int64 (time
+// the runtime needs to restore the container) are only set if
+// options.PrintStats is set to true. Not setting options.PrintStats to true
+// will return nil and 0.
+func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOptions) (*define.CRIUCheckpointRestoreStatistics, int64, error) {
if options.Pod == "" {
logrus.Debugf("Trying to restore container %s", c.ID())
} else {
@@ -835,7 +847,7 @@ func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOpti
defer c.lock.Unlock()
if err := c.syncContainer(); err != nil {
- return err
+ return nil, 0, err
}
}
defer c.newContainerEvent(events.Restore)
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index de23a4aeb..4bf15be86 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -1089,7 +1089,7 @@ func (c *Container) init(ctx context.Context, retainRetries bool) error {
}
// With the spec complete, do an OCI create
- if err := c.ociRuntime.CreateContainer(c, nil); err != nil {
+ if _, err = c.ociRuntime.CreateContainer(c, nil); err != nil {
// Fedora 31 is carrying a patch to display improved error
// messages to better handle the V2 transition. This is NOT
// upstream in any OCI runtime.
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 3363ceb9b..1a4508448 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -1129,25 +1129,26 @@ func (c *Container) checkpointRestoreSupported(version int) error {
return nil
}
-func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointOptions) error {
+func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointOptions) (*define.CRIUCheckpointRestoreStatistics, int64, error) {
if err := c.checkpointRestoreSupported(criu.MinCriuVersion); err != nil {
- return err
+ return nil, 0, err
}
if c.state.State != define.ContainerStateRunning {
- return errors.Wrapf(define.ErrCtrStateInvalid, "%q is not running, cannot checkpoint", c.state.State)
+ return nil, 0, errors.Wrapf(define.ErrCtrStateInvalid, "%q is not running, cannot checkpoint", c.state.State)
}
if c.AutoRemove() && options.TargetFile == "" {
- return errors.Errorf("cannot checkpoint containers that have been started with '--rm' unless '--export' is used")
+ return nil, 0, errors.Errorf("cannot checkpoint containers that have been started with '--rm' unless '--export' is used")
}
if err := crutils.CRCreateFileWithLabel(c.bundlePath(), "dump.log", c.MountLabel()); err != nil {
- return err
+ return nil, 0, err
}
- if err := c.ociRuntime.CheckpointContainer(c, options); err != nil {
- return err
+ runtimeCheckpointDuration, err := c.ociRuntime.CheckpointContainer(c, options)
+ if err != nil {
+ return nil, 0, err
}
// Save network.status. This is needed to restore the container with
@@ -1155,7 +1156,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO
// with one interface.
// FIXME: will this break something?
if _, err := metadata.WriteJSONFile(c.getNetworkStatus(), c.bundlePath(), metadata.NetworkStatusFile); err != nil {
- return err
+ return nil, 0, err
}
defer c.newContainerEvent(events.Checkpoint)
@@ -1165,13 +1166,13 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO
if options.WithPrevious {
os.Remove(path.Join(c.CheckpointPath(), "parent"))
if err := os.Symlink("../pre-checkpoint", path.Join(c.CheckpointPath(), "parent")); err != nil {
- return err
+ return nil, 0, err
}
}
if options.TargetFile != "" {
if err := c.exportCheckpoint(options); err != nil {
- return err
+ return nil, 0, err
}
}
@@ -1183,10 +1184,37 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO
// Cleanup Storage and Network
if err := c.cleanup(ctx); err != nil {
- return err
+ return nil, 0, err
}
}
+ criuStatistics, err := func() (*define.CRIUCheckpointRestoreStatistics, error) {
+ if !options.PrintStats {
+ return nil, nil
+ }
+ statsDirectory, err := os.Open(c.bundlePath())
+ if err != nil {
+ return nil, errors.Wrapf(err, "Not able to open %q", c.bundlePath())
+ }
+
+ dumpStatistics, err := stats.CriuGetDumpStats(statsDirectory)
+ if err != nil {
+ return nil, errors.Wrap(err, "Displaying checkpointing statistics not possible")
+ }
+
+ return &define.CRIUCheckpointRestoreStatistics{
+ FreezingTime: dumpStatistics.GetFreezingTime(),
+ FrozenTime: dumpStatistics.GetFrozenTime(),
+ MemdumpTime: dumpStatistics.GetMemdumpTime(),
+ MemwriteTime: dumpStatistics.GetMemwriteTime(),
+ PagesScanned: dumpStatistics.GetPagesScanned(),
+ PagesWritten: dumpStatistics.GetPagesWritten(),
+ }, nil
+ }()
+ if err != nil {
+ return nil, 0, err
+ }
+
if !options.Keep && !options.PreCheckPoint {
cleanup := []string{
"dump.log",
@@ -1203,7 +1231,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO
}
c.state.FinishedTime = time.Now()
- return c.save()
+ return criuStatistics, runtimeCheckpointDuration, c.save()
}
func (c *Container) importCheckpoint(input string) error {
@@ -1236,7 +1264,7 @@ func (c *Container) importPreCheckpoint(input string) error {
return nil
}
-func (c *Container) restore(ctx context.Context, options ContainerCheckpointOptions) (retErr error) {
+func (c *Container) restore(ctx context.Context, options ContainerCheckpointOptions) (criuStatistics *define.CRIUCheckpointRestoreStatistics, runtimeRestoreDuration int64, retErr error) {
minCriuVersion := func() int {
if options.Pod == "" {
return criu.MinCriuVersion
@@ -1244,37 +1272,37 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
return criu.PodCriuVersion
}()
if err := c.checkpointRestoreSupported(minCriuVersion); err != nil {
- return err
+ return nil, 0, err
}
if options.Pod != "" && !crutils.CRRuntimeSupportsPodCheckpointRestore(c.ociRuntime.Path()) {
- return errors.Errorf("runtime %s does not support pod restore", c.ociRuntime.Path())
+ return nil, 0, errors.Errorf("runtime %s does not support pod restore", c.ociRuntime.Path())
}
if !c.ensureState(define.ContainerStateConfigured, define.ContainerStateExited) {
- return errors.Wrapf(define.ErrCtrStateInvalid, "container %s is running or paused, cannot restore", c.ID())
+ return nil, 0, errors.Wrapf(define.ErrCtrStateInvalid, "container %s is running or paused, cannot restore", c.ID())
}
if options.ImportPrevious != "" {
if err := c.importPreCheckpoint(options.ImportPrevious); err != nil {
- return err
+ return nil, 0, err
}
}
if options.TargetFile != "" {
if err := c.importCheckpoint(options.TargetFile); err != nil {
- return err
+ return nil, 0, err
}
}
// Let's try to stat() CRIU's inventory file. If it does not exist, it makes
// no sense to try a restore. This is a minimal check if a checkpoint exist.
if _, err := os.Stat(filepath.Join(c.CheckpointPath(), "inventory.img")); os.IsNotExist(err) {
- return errors.Wrapf(err, "a complete checkpoint for this container cannot be found, cannot restore")
+ return nil, 0, errors.Wrapf(err, "a complete checkpoint for this container cannot be found, cannot restore")
}
if err := crutils.CRCreateFileWithLabel(c.bundlePath(), "restore.log", c.MountLabel()); err != nil {
- return err
+ return nil, 0, err
}
// If a container is restored multiple times from an exported checkpoint with
@@ -1311,7 +1339,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
// container with the same networks settings as during checkpointing.
aliases, err := c.GetAllNetworkAliases()
if err != nil {
- return err
+ return nil, 0, err
}
netOpts := make(map[string]types.PerNetworkOptions, len(netStatus))
for network, status := range netStatus {
@@ -1336,7 +1364,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
if perNetOpts.InterfaceName == "" {
eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(network)
if !exists {
- return errors.Errorf("no network interface name for container %s on network %s", c.config.ID, network)
+ return nil, 0, errors.Errorf("no network interface name for container %s on network %s", c.config.ID, network)
}
perNetOpts.InterfaceName = eth
}
@@ -1354,7 +1382,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
}()
if err := c.prepare(); err != nil {
- return err
+ return nil, 0, err
}
// Read config
@@ -1363,7 +1391,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
g, err := generate.NewFromFile(jsonPath)
if err != nil {
logrus.Debugf("generate.NewFromFile failed with %v", err)
- return err
+ return nil, 0, err
}
// Restoring from an import means that we are doing migration
@@ -1379,7 +1407,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
}
if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), netNSPath); err != nil {
- return err
+ return nil, 0, err
}
}
@@ -1388,23 +1416,23 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
// the ones from the infrastructure container.
pod, err := c.runtime.LookupPod(options.Pod)
if err != nil {
- return errors.Wrapf(err, "pod %q cannot be retrieved", options.Pod)
+ return nil, 0, errors.Wrapf(err, "pod %q cannot be retrieved", options.Pod)
}
infraContainer, err := pod.InfraContainer()
if err != nil {
- return errors.Wrapf(err, "cannot retrieved infra container from pod %q", options.Pod)
+ return nil, 0, errors.Wrapf(err, "cannot retrieved infra container from pod %q", options.Pod)
}
infraContainer.lock.Lock()
if err := infraContainer.syncContainer(); err != nil {
infraContainer.lock.Unlock()
- return errors.Wrapf(err, "Error syncing infrastructure container %s status", infraContainer.ID())
+ return nil, 0, errors.Wrapf(err, "Error syncing infrastructure container %s status", infraContainer.ID())
}
if infraContainer.state.State != define.ContainerStateRunning {
if err := infraContainer.initAndStart(ctx); err != nil {
infraContainer.lock.Unlock()
- return errors.Wrapf(err, "Error starting infrastructure container %s status", infraContainer.ID())
+ return nil, 0, errors.Wrapf(err, "Error starting infrastructure container %s status", infraContainer.ID())
}
}
infraContainer.lock.Unlock()
@@ -1412,56 +1440,56 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
if c.config.IPCNsCtr != "" {
nsPath, err := infraContainer.namespacePath(IPCNS)
if err != nil {
- return errors.Wrapf(err, "cannot retrieve IPC namespace path for Pod %q", options.Pod)
+ return nil, 0, errors.Wrapf(err, "cannot retrieve IPC namespace path for Pod %q", options.Pod)
}
if err := g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), nsPath); err != nil {
- return err
+ return nil, 0, err
}
}
if c.config.NetNsCtr != "" {
nsPath, err := infraContainer.namespacePath(NetNS)
if err != nil {
- return errors.Wrapf(err, "cannot retrieve network namespace path for Pod %q", options.Pod)
+ return nil, 0, errors.Wrapf(err, "cannot retrieve network namespace path for Pod %q", options.Pod)
}
if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), nsPath); err != nil {
- return err
+ return nil, 0, err
}
}
if c.config.PIDNsCtr != "" {
nsPath, err := infraContainer.namespacePath(PIDNS)
if err != nil {
- return errors.Wrapf(err, "cannot retrieve PID namespace path for Pod %q", options.Pod)
+ return nil, 0, errors.Wrapf(err, "cannot retrieve PID namespace path for Pod %q", options.Pod)
}
if err := g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), nsPath); err != nil {
- return err
+ return nil, 0, err
}
}
if c.config.UTSNsCtr != "" {
nsPath, err := infraContainer.namespacePath(UTSNS)
if err != nil {
- return errors.Wrapf(err, "cannot retrieve UTS namespace path for Pod %q", options.Pod)
+ return nil, 0, errors.Wrapf(err, "cannot retrieve UTS namespace path for Pod %q", options.Pod)
}
if err := g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), nsPath); err != nil {
- return err
+ return nil, 0, err
}
}
if c.config.CgroupNsCtr != "" {
nsPath, err := infraContainer.namespacePath(CgroupNS)
if err != nil {
- return errors.Wrapf(err, "cannot retrieve Cgroup namespace path for Pod %q", options.Pod)
+ return nil, 0, errors.Wrapf(err, "cannot retrieve Cgroup namespace path for Pod %q", options.Pod)
}
if err := g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), nsPath); err != nil {
- return err
+ return nil, 0, err
}
}
}
if err := c.makeBindMounts(); err != nil {
- return err
+ return nil, 0, err
}
if options.TargetFile != "" {
@@ -1483,12 +1511,12 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
// Cleanup for a working restore.
if err := c.removeConmonFiles(); err != nil {
- return err
+ return nil, 0, err
}
// Save the OCI spec to disk
if err := c.saveSpec(g.Config); err != nil {
- return err
+ return nil, 0, err
}
// When restoring from an imported archive, allow restoring the content of volumes.
@@ -1499,24 +1527,24 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
volumeFile, err := os.Open(volumeFilePath)
if err != nil {
- return errors.Wrapf(err, "failed to open volume file %s", volumeFilePath)
+ return nil, 0, errors.Wrapf(err, "failed to open volume file %s", volumeFilePath)
}
defer volumeFile.Close()
volume, err := c.runtime.GetVolume(v.Name)
if err != nil {
- return errors.Wrapf(err, "failed to retrieve volume %s", v.Name)
+ return nil, 0, errors.Wrapf(err, "failed to retrieve volume %s", v.Name)
}
mountPoint, err := volume.MountPoint()
if err != nil {
- return err
+ return nil, 0, err
}
if mountPoint == "" {
- return errors.Wrapf(err, "unable to import volume %s as it is not mounted", volume.Name())
+ return nil, 0, errors.Wrapf(err, "unable to import volume %s as it is not mounted", volume.Name())
}
if err := archive.UntarUncompressed(volumeFile, mountPoint, nil); err != nil {
- return errors.Wrapf(err, "Failed to extract volume %s to %s", volumeFilePath, mountPoint)
+ return nil, 0, errors.Wrapf(err, "Failed to extract volume %s to %s", volumeFilePath, mountPoint)
}
}
}
@@ -1524,16 +1552,43 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
// Before actually restarting the container, apply the root file-system changes
if !options.IgnoreRootfs {
if err := crutils.CRApplyRootFsDiffTar(c.bundlePath(), c.state.Mountpoint); err != nil {
- return err
+ return nil, 0, err
}
if err := crutils.CRRemoveDeletedFiles(c.ID(), c.bundlePath(), c.state.Mountpoint); err != nil {
- return err
+ return nil, 0, err
}
}
- if err := c.ociRuntime.CreateContainer(c, &options); err != nil {
- return err
+ runtimeRestoreDuration, err = c.ociRuntime.CreateContainer(c, &options)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ criuStatistics, err = func() (*define.CRIUCheckpointRestoreStatistics, error) {
+ if !options.PrintStats {
+ return nil, nil
+ }
+ statsDirectory, err := os.Open(c.bundlePath())
+ if err != nil {
+ return nil, errors.Wrapf(err, "Not able to open %q", c.bundlePath())
+ }
+
+ restoreStatistics, err := stats.CriuGetRestoreStats(statsDirectory)
+ if err != nil {
+ return nil, errors.Wrap(err, "Displaying restore statistics not possible")
+ }
+
+ return &define.CRIUCheckpointRestoreStatistics{
+ PagesCompared: restoreStatistics.GetPagesCompared(),
+ PagesSkippedCow: restoreStatistics.GetPagesSkippedCow(),
+ ForkingTime: restoreStatistics.GetForkingTime(),
+ RestoreTime: restoreStatistics.GetRestoreTime(),
+ PagesRestored: restoreStatistics.GetPagesRestored(),
+ }, nil
+ }()
+ if err != nil {
+ return nil, 0, err
}
logrus.Debugf("Restored container %s", c.ID())
@@ -1572,7 +1627,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
}
}
- return c.save()
+ return criuStatistics, runtimeRestoreDuration, c.save()
}
// Retrieves a container's "root" net namespace container dependency.
diff --git a/libpod/container_log_linux.go b/libpod/container_log_linux.go
index 4029d0af7..d3d7c8397 100644
--- a/libpod/container_log_linux.go
+++ b/libpod/container_log_linux.go
@@ -37,13 +37,21 @@ func (c *Container) initializeJournal(ctx context.Context) error {
m := make(map[string]string)
m["SYSLOG_IDENTIFIER"] = "podman"
m["PODMAN_ID"] = c.ID()
- m["CONTAINER_ID_FULL"] = c.ID()
history := events.History
m["PODMAN_EVENT"] = history.String()
+ container := events.Container
+ m["PODMAN_TYPE"] = container.String()
+ m["PODMAN_TIME"] = time.Now().Format(time.RFC3339Nano)
return journal.Send("", journal.PriInfo, m)
}
func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine) error {
+ // We need the container's events in the same journal to guarantee
+ // consistency, see #10323.
+ if options.Follow && c.runtime.config.Engine.EventsLogger != "journald" {
+ return errors.Errorf("using --follow with the journald --log-driver but without the journald --events-backend (%s) is not supported", c.runtime.config.Engine.EventsLogger)
+ }
+
journal, err := sdjournal.NewJournal()
if err != nil {
return err
@@ -89,6 +97,7 @@ func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOption
// exponential backoff.
var cursor string
var cursorError error
+ var containerCouldBeLogging bool
for i := 1; i <= 3; i++ {
cursor, cursorError = journal.GetCursor()
hundreds := 1
@@ -105,12 +114,6 @@ func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOption
return errors.Wrap(cursorError, "initial journal cursor")
}
- // We need the container's events in the same journal to guarantee
- // consistency, see #10323.
- if options.Follow && c.runtime.config.Engine.EventsLogger != "journald" {
- return errors.Errorf("using --follow with the journald --log-driver but without the journald --events-backend (%s) is not supported", c.runtime.config.Engine.EventsLogger)
- }
-
options.WaitGroup.Add(1)
go func() {
defer func() {
@@ -172,7 +175,7 @@ func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOption
doTailFunc()
}
// Unless we follow, quit.
- if !options.Follow {
+ if !options.Follow || !containerCouldBeLogging {
return
}
// Sleep until something's happening on the journal.
@@ -201,11 +204,14 @@ func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOption
logrus.Errorf("Failed to translate event: %v", err)
return
}
- if status == events.Exited {
+ switch status {
+ case events.History, events.Init, events.Start, events.Restart:
+ containerCouldBeLogging = true
+ case events.Exited:
+ containerCouldBeLogging = false
if doTail {
doTailFunc()
}
- return
}
continue
}
diff --git a/libpod/define/checkpoint_restore.go b/libpod/define/checkpoint_restore.go
new file mode 100644
index 000000000..536bdde9a
--- /dev/null
+++ b/libpod/define/checkpoint_restore.go
@@ -0,0 +1,32 @@
+package define
+
+// This contains values reported by CRIU during
+// checkpointing or restoring.
+// All names are the same as reported by CRIU.
+type CRIUCheckpointRestoreStatistics struct {
+ // Checkpoint values
+ // Time required to freeze/pause/quiesce the processes
+ FreezingTime uint32 `json:"freezing_time,omitempty"`
+ // Time the processes are actually not running during checkpointing
+ FrozenTime uint32 `json:"frozen_time,omitempty"`
+ // Time required to extract memory pages from the processes
+ MemdumpTime uint32 `json:"memdump_time,omitempty"`
+ // Time required to write memory pages to disk
+ MemwriteTime uint32 `json:"memwrite_time,omitempty"`
+ // Number of memory pages CRIU analyzed
+ PagesScanned uint64 `json:"pages_scanned,omitempty"`
+ // Number of memory pages written
+ PagesWritten uint64 `json:"pages_written,omitempty"`
+
+ // Restore values
+ // Number of pages compared during restore
+ PagesCompared uint64 `json:"pages_compared,omitempty"`
+ // Number of COW pages skipped during restore
+ PagesSkippedCow uint64 `json:"pages_skipped_cow,omitempty"`
+ // Time required to fork processes
+ ForkingTime uint32 `json:"forking_time,omitempty"`
+ // Time required to restore
+ RestoreTime uint32 `json:"restore_time,omitempty"`
+ // Number of memory pages restored
+ PagesRestored uint64 `json:"pages_restored,omitempty"`
+}
diff --git a/libpod/oci.go b/libpod/oci.go
index c92d9a077..f45c1a105 100644
--- a/libpod/oci.go
+++ b/libpod/oci.go
@@ -23,7 +23,10 @@ type OCIRuntime interface {
Path() string
// CreateContainer creates the container in the OCI runtime.
- CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) error
+ // The returned int64 contains the microseconds needed to restore
+ // the given container if it is a restore and if restoreOptions.PrintStats
+ // is true. In all other cases the returned int64 is 0.
+ CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (int64, error)
// UpdateContainerStatus updates the status of the given container.
UpdateContainerStatus(ctr *Container) error
// StartContainer starts the given container.
@@ -101,8 +104,10 @@ type OCIRuntime interface {
// CheckpointContainer checkpoints the given container.
// Some OCI runtimes may not support this - if SupportsCheckpoint()
// returns false, this is not implemented, and will always return an
- // error.
- CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) error
+ // error. If CheckpointOptions.PrintStats is true the first return parameter
+ // contains the number of microseconds the runtime needed to checkpoint
+ // the given container.
+ CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) (int64, error)
// CheckConmonRunning verifies that the given container's Conmon
// instance is still running. Runtimes without Conmon, or systems where
diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go
index db906fabb..533a0d78b 100644
--- a/libpod/oci_conmon_linux.go
+++ b/libpod/oci_conmon_linux.go
@@ -183,35 +183,39 @@ func hasCurrentUserMapped(ctr *Container) bool {
}
// CreateContainer creates a container.
-func (r *ConmonOCIRuntime) CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) error {
+func (r *ConmonOCIRuntime) CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (int64, error) {
// always make the run dir accessible to the current user so that the PID files can be read without
// being in the rootless user namespace.
if err := makeAccessible(ctr.state.RunDir, 0, 0); err != nil {
- return err
+ return 0, err
}
if !hasCurrentUserMapped(ctr) {
for _, i := range []string{ctr.state.RunDir, ctr.runtime.config.Engine.TmpDir, ctr.config.StaticDir, ctr.state.Mountpoint, ctr.runtime.config.Engine.VolumePath} {
if err := makeAccessible(i, ctr.RootUID(), ctr.RootGID()); err != nil {
- return err
+ return 0, err
}
}
// if we are running a non privileged container, be sure to umount some kernel paths so they are not
// bind mounted inside the container at all.
if !ctr.config.Privileged && !rootless.IsRootless() {
- ch := make(chan error)
+ type result struct {
+ restoreDuration int64
+ err error
+ }
+ ch := make(chan result)
go func() {
runtime.LockOSThread()
- err := func() error {
+ restoreDuration, err := func() (int64, error) {
fd, err := os.Open(fmt.Sprintf("/proc/%d/task/%d/ns/mnt", os.Getpid(), unix.Gettid()))
if err != nil {
- return err
+ return 0, err
}
defer errorhandling.CloseQuiet(fd)
// create a new mountns on the current thread
if err = unix.Unshare(unix.CLONE_NEWNS); err != nil {
- return err
+ return 0, err
}
defer func() {
if err := unix.Setns(int(fd.Fd()), unix.CLONE_NEWNS); err != nil {
@@ -224,12 +228,12 @@ func (r *ConmonOCIRuntime) CreateContainer(ctr *Container, restoreOptions *Conta
// changes are propagated to the host.
err = unix.Mount("/sys", "/sys", "none", unix.MS_REC|unix.MS_SLAVE, "")
if err != nil {
- return errors.Wrapf(err, "cannot make /sys slave")
+ return 0, errors.Wrapf(err, "cannot make /sys slave")
}
mounts, err := pmount.GetMounts()
if err != nil {
- return err
+ return 0, err
}
for _, m := range mounts {
if !strings.HasPrefix(m.Mountpoint, "/sys/kernel") {
@@ -237,15 +241,18 @@ func (r *ConmonOCIRuntime) CreateContainer(ctr *Container, restoreOptions *Conta
}
err = unix.Unmount(m.Mountpoint, 0)
if err != nil && !os.IsNotExist(err) {
- return errors.Wrapf(err, "cannot unmount %s", m.Mountpoint)
+ return 0, errors.Wrapf(err, "cannot unmount %s", m.Mountpoint)
}
}
return r.createOCIContainer(ctr, restoreOptions)
}()
- ch <- err
+ ch <- result{
+ restoreDuration: restoreDuration,
+ err: err,
+ }
}()
- err := <-ch
- return err
+ r := <-ch
+ return r.restoreDuration, r.err
}
}
return r.createOCIContainer(ctr, restoreOptions)
@@ -760,9 +767,9 @@ func (r *ConmonOCIRuntime) AttachResize(ctr *Container, newSize define.TerminalS
}
// CheckpointContainer checkpoints the given container.
-func (r *ConmonOCIRuntime) CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) error {
+func (r *ConmonOCIRuntime) CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) (int64, error) {
if err := label.SetSocketLabel(ctr.ProcessLabel()); err != nil {
- return err
+ return 0, err
}
// imagePath is used by CRIU to store the actual checkpoint files
imagePath := ctr.CheckpointPath()
@@ -802,14 +809,25 @@ func (r *ConmonOCIRuntime) CheckpointContainer(ctr *Container, options Container
}
runtimeDir, err := util.GetRuntimeDir()
if err != nil {
- return err
+ return 0, err
}
if err = os.Setenv("XDG_RUNTIME_DIR", runtimeDir); err != nil {
- return errors.Wrapf(err, "cannot set XDG_RUNTIME_DIR")
+ return 0, errors.Wrapf(err, "cannot set XDG_RUNTIME_DIR")
}
args = append(args, ctr.ID())
logrus.Debugf("the args to checkpoint: %s %s", r.path, strings.Join(args, " "))
- return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, args...)
+
+ runtimeCheckpointStarted := time.Now()
+ err = utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, args...)
+
+ runtimeCheckpointDuration := func() int64 {
+ if options.PrintStats {
+ return time.Since(runtimeCheckpointStarted).Microseconds()
+ }
+ return 0
+ }()
+
+ return runtimeCheckpointDuration, err
}
func (r *ConmonOCIRuntime) CheckConmonRunning(ctr *Container) (bool, error) {
@@ -984,23 +1002,23 @@ func (r *ConmonOCIRuntime) getLogTag(ctr *Container) (string, error) {
}
// createOCIContainer generates this container's main conmon instance and prepares it for starting
-func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) error {
+func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (int64, error) {
var stderrBuf bytes.Buffer
runtimeDir, err := util.GetRuntimeDir()
if err != nil {
- return err
+ return 0, err
}
parentSyncPipe, childSyncPipe, err := newPipe()
if err != nil {
- return errors.Wrapf(err, "error creating socket pair")
+ return 0, errors.Wrapf(err, "error creating socket pair")
}
defer errorhandling.CloseQuiet(parentSyncPipe)
childStartPipe, parentStartPipe, err := newPipe()
if err != nil {
- return errors.Wrapf(err, "error creating socket pair for start pipe")
+ return 0, errors.Wrapf(err, "error creating socket pair for start pipe")
}
defer errorhandling.CloseQuiet(parentStartPipe)
@@ -1012,12 +1030,12 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
logTag, err := r.getLogTag(ctr)
if err != nil {
- return err
+ return 0, err
}
if ctr.config.CgroupsMode == cgroupSplit {
if err := utils.MoveUnderCgroupSubtree("runtime"); err != nil {
- return err
+ return 0, err
}
}
@@ -1068,7 +1086,7 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
} else {
fds, err := strconv.Atoi(val)
if err != nil {
- return fmt.Errorf("converting LISTEN_FDS=%s: %w", val, err)
+ return 0, fmt.Errorf("converting LISTEN_FDS=%s: %w", val, err)
}
preserveFDs = uint(fds)
}
@@ -1149,7 +1167,7 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
if r.reservePorts && !rootless.IsRootless() && !ctr.config.NetMode.IsSlirp4netns() {
ports, err := bindPorts(ctr.config.PortMappings)
if err != nil {
- return err
+ return 0, err
}
filesToClose = append(filesToClose, ports...)
@@ -1165,12 +1183,12 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
if havePortMapping {
ctr.rootlessPortSyncR, ctr.rootlessPortSyncW, err = os.Pipe()
if err != nil {
- return errors.Wrapf(err, "failed to create rootless port sync pipe")
+ return 0, errors.Wrapf(err, "failed to create rootless port sync pipe")
}
}
ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe()
if err != nil {
- return errors.Wrapf(err, "failed to create rootless network sync pipe")
+ return 0, errors.Wrapf(err, "failed to create rootless network sync pipe")
}
} else {
if ctr.rootlessSlirpSyncR != nil {
@@ -1189,22 +1207,25 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessPortSyncW)
}
}
-
+ var runtimeRestoreStarted time.Time
+ if restoreOptions != nil {
+ runtimeRestoreStarted = time.Now()
+ }
err = startCommandGivenSelinux(cmd, ctr)
// regardless of whether we errored or not, we no longer need the children pipes
childSyncPipe.Close()
childStartPipe.Close()
if err != nil {
- return err
+ return 0, err
}
if err := r.moveConmonToCgroupAndSignal(ctr, cmd, parentStartPipe); err != nil {
- return err
+ return 0, err
}
/* Wait for initial setup and fork, and reap child */
err = cmd.Wait()
if err != nil {
- return err
+ return 0, err
}
pid, err := readConmonPipeData(parentSyncPipe, ociLog)
@@ -1212,7 +1233,7 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
if err2 := r.DeleteContainer(ctr); err2 != nil {
logrus.Errorf("Removing container %s from runtime after creation failed", ctr.ID())
}
- return err
+ return 0, err
}
ctr.state.PID = pid
@@ -1238,13 +1259,20 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
}
}
+ runtimeRestoreDuration := func() int64 {
+ if restoreOptions != nil && restoreOptions.PrintStats {
+ return time.Since(runtimeRestoreStarted).Microseconds()
+ }
+ return 0
+ }()
+
// These fds were passed down to the runtime. Close them
// and not interfere
for _, f := range filesToClose {
errorhandling.CloseQuiet(f)
}
- return nil
+ return runtimeRestoreDuration, nil
}
// configureConmonEnv gets the environment values to add to conmon's exec struct
diff --git a/libpod/oci_missing.go b/libpod/oci_missing.go
index fcf2ffca8..65ff818b4 100644
--- a/libpod/oci_missing.go
+++ b/libpod/oci_missing.go
@@ -66,8 +66,8 @@ func (r *MissingRuntime) Path() string {
}
// CreateContainer is not available as the runtime is missing
-func (r *MissingRuntime) CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) error {
- return r.printError()
+func (r *MissingRuntime) CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) (int64, error) {
+ return 0, r.printError()
}
// UpdateContainerStatus is not available as the runtime is missing
@@ -153,8 +153,8 @@ func (r *MissingRuntime) ExecUpdateStatus(ctr *Container, sessionID string) (boo
}
// CheckpointContainer is not available as the runtime is missing
-func (r *MissingRuntime) CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) error {
- return r.printError()
+func (r *MissingRuntime) CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) (int64, error) {
+ return 0, r.printError()
}
// CheckConmonRunning is not available as the runtime is missing
diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go
index 343c0d0b3..3aeebc334 100644
--- a/pkg/api/handlers/libpod/containers.go
+++ b/pkg/api/handlers/libpod/containers.go
@@ -214,6 +214,7 @@ func Checkpoint(w http.ResponseWriter, r *http.Request) {
TCPEstablished bool `schema:"tcpEstablished"`
Export bool `schema:"export"`
IgnoreRootFS bool `schema:"ignoreRootFS"`
+ PrintStats bool `schema:"printStats"`
}{
// override any golang type defaults
}
@@ -248,11 +249,12 @@ func Checkpoint(w http.ResponseWriter, r *http.Request) {
KeepRunning: query.LeaveRunning,
TCPEstablished: query.TCPEstablished,
IgnoreRootfs: query.IgnoreRootFS,
+ PrintStats: query.PrintStats,
}
if query.Export {
options.TargetFile = targetFile
}
- err = ctr.Checkpoint(r.Context(), options)
+ criuStatistics, runtimeCheckpointDuration, err := ctr.Checkpoint(r.Context(), options)
if err != nil {
utils.InternalServerError(w, err)
return
@@ -267,7 +269,15 @@ func Checkpoint(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, f)
return
}
- utils.WriteResponse(w, http.StatusOK, entities.CheckpointReport{Id: ctr.ID()})
+ utils.WriteResponse(
+ w,
+ http.StatusOK,
+ entities.CheckpointReport{
+ Id: ctr.ID(),
+ RuntimeDuration: runtimeCheckpointDuration,
+ CRIUStatistics: criuStatistics,
+ },
+ )
}
func Restore(w http.ResponseWriter, r *http.Request) {
@@ -284,6 +294,7 @@ func Restore(w http.ResponseWriter, r *http.Request) {
IgnoreVolumes bool `schema:"ignoreVolumes"`
IgnoreStaticIP bool `schema:"ignoreStaticIP"`
IgnoreStaticMAC bool `schema:"ignoreStaticMAC"`
+ PrintStats bool `schema:"printStats"`
}{
// override any golang type defaults
}
@@ -319,17 +330,26 @@ func Restore(w http.ResponseWriter, r *http.Request) {
IgnoreRootfs: query.IgnoreRootFS,
IgnoreStaticIP: query.IgnoreStaticIP,
IgnoreStaticMAC: query.IgnoreStaticMAC,
+ PrintStats: query.PrintStats,
}
if query.Import {
options.TargetFile = targetFile
options.Name = query.Name
}
- err = ctr.Restore(r.Context(), options)
+ criuStatistics, runtimeRestoreDuration, err := ctr.Restore(r.Context(), options)
if err != nil {
utils.InternalServerError(w, err)
return
}
- utils.WriteResponse(w, http.StatusOK, entities.RestoreReport{Id: ctr.ID()})
+ utils.WriteResponse(
+ w,
+ http.StatusOK,
+ entities.RestoreReport{
+ Id: ctr.ID(),
+ RuntimeDuration: runtimeRestoreDuration,
+ CRIUStatistics: criuStatistics,
+ },
+ )
}
func InitContainer(w http.ResponseWriter, r *http.Request) {
diff --git a/pkg/api/handlers/libpod/generate.go b/pkg/api/handlers/libpod/generate.go
index 117c5e2aa..5205d875d 100644
--- a/pkg/api/handlers/libpod/generate.go
+++ b/pkg/api/handlers/libpod/generate.go
@@ -17,14 +17,15 @@ func GenerateSystemd(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
query := struct {
- Name bool `schema:"useName"`
- New bool `schema:"new"`
- NoHeader bool `schema:"noHeader"`
- RestartPolicy *string `schema:"restartPolicy"`
- StopTimeout uint `schema:"stopTimeout"`
- ContainerPrefix string `schema:"containerPrefix"`
- PodPrefix string `schema:"podPrefix"`
- Separator string `schema:"separator"`
+ Name bool `schema:"useName"`
+ New bool `schema:"new"`
+ NoHeader bool `schema:"noHeader"`
+ TemplateUnitFile bool `schema:"templateUnitFile"`
+ RestartPolicy *string `schema:"restartPolicy"`
+ StopTimeout uint `schema:"stopTimeout"`
+ ContainerPrefix string `schema:"containerPrefix"`
+ PodPrefix string `schema:"podPrefix"`
+ Separator string `schema:"separator"`
}{
StopTimeout: util.DefaultContainerConfig().Engine.StopTimeout,
ContainerPrefix: "container",
@@ -40,14 +41,15 @@ func GenerateSystemd(w http.ResponseWriter, r *http.Request) {
containerEngine := abi.ContainerEngine{Libpod: runtime}
options := entities.GenerateSystemdOptions{
- Name: query.Name,
- New: query.New,
- NoHeader: query.NoHeader,
- RestartPolicy: query.RestartPolicy,
- StopTimeout: &query.StopTimeout,
- ContainerPrefix: query.ContainerPrefix,
- PodPrefix: query.PodPrefix,
- Separator: query.Separator,
+ Name: query.Name,
+ New: query.New,
+ NoHeader: query.NoHeader,
+ TemplateUnitFile: query.TemplateUnitFile,
+ RestartPolicy: query.RestartPolicy,
+ StopTimeout: &query.StopTimeout,
+ ContainerPrefix: query.ContainerPrefix,
+ PodPrefix: query.PodPrefix,
+ Separator: query.Separator,
}
report, err := containerEngine.GenerateSystemd(r.Context(), utils.GetName(r), options)
diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go
index c4919182b..601e1251b 100644
--- a/pkg/api/server/register_containers.go
+++ b/pkg/api/server/register_containers.go
@@ -1441,6 +1441,10 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// name: ignoreRootFS
// type: boolean
// description: do not include root file-system changes when exporting
+ // - in: query
+ // name: printStats
+ // type: boolean
+ // description: add checkpoint statistics to the returned CheckpointReport
// produces:
// - application/json
// responses:
@@ -1495,6 +1499,10 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// name: ignoreStaticMAC
// type: boolean
// description: ignore MAC address if set statically
+ // - in: query
+ // name: printStats
+ // type: boolean
+ // description: add restore statistics to the returned RestoreReport
// produces:
// - application/json
// responses:
diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go
index a2be44ab4..b2e949f67 100644
--- a/pkg/bindings/connection.go
+++ b/pkg/bindings/connection.go
@@ -145,7 +145,7 @@ func pingNewConnection(ctx context.Context) error {
return err
}
// the ping endpoint sits at / in this case
- response, err := client.DoRequest(nil, http.MethodGet, "/_ping", nil, nil)
+ response, err := client.DoRequest(ctx, nil, http.MethodGet, "/_ping", nil, nil)
if err != nil {
return err
}
@@ -306,7 +306,7 @@ func unixClient(_url *url.URL) Connection {
}
// DoRequest assembles the http request and returns the response
-func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, queryParams url.Values, header map[string]string, pathValues ...string) (*APIResponse, error) {
+func (c *Connection) DoRequest(ctx context.Context, httpBody io.Reader, httpMethod, endpoint string, queryParams url.Values, header map[string]string, pathValues ...string) (*APIResponse, error) {
var (
err error
response *http.Response
@@ -328,7 +328,7 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string,
uri := fmt.Sprintf("http://d/v%d.%d.%d/libpod"+endpoint, params...)
logrus.Debugf("DoRequest Method: %s URI: %v", httpMethod, uri)
- req, err := http.NewRequestWithContext(context.WithValue(context.Background(), clientKey, c), httpMethod, uri, httpBody)
+ req, err := http.NewRequestWithContext(ctx, httpMethod, uri, httpBody)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/containers/archive.go b/pkg/bindings/containers/archive.go
index 876f5340b..d64fbffd6 100644
--- a/pkg/bindings/containers/archive.go
+++ b/pkg/bindings/containers/archive.go
@@ -23,7 +23,7 @@ func Stat(ctx context.Context, nameOrID string, path string) (*entities.Containe
params := url.Values{}
params.Set("path", path)
- response, err := conn.DoRequest(nil, http.MethodHead, "/containers/%s/archive", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodHead, "/containers/%s/archive", params, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -71,7 +71,7 @@ func CopyFromArchiveWithOptions(ctx context.Context, nameOrID string, path strin
params.Set("path", path)
return func() error {
- response, err := conn.DoRequest(reader, http.MethodPut, "/containers/%s/archive", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, reader, http.MethodPut, "/containers/%s/archive", params, nil, nameOrID)
if err != nil {
return err
}
@@ -92,7 +92,7 @@ func CopyToArchive(ctx context.Context, nameOrID string, path string, writer io.
params := url.Values{}
params.Set("path", path)
- response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/archive", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/archive", params, nil, nameOrID)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/containers/attach.go b/pkg/bindings/containers/attach.go
index 47de89b33..baa3f182e 100644
--- a/pkg/bindings/containers/attach.go
+++ b/pkg/bindings/containers/attach.go
@@ -130,7 +130,7 @@ func Attach(ctx context.Context, nameOrID string, stdin io.Reader, stdout io.Wri
IdleConnTimeout: time.Duration(0),
}
conn.Client.Transport = t
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/attach", params, headers, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/attach", params, headers, nameOrID)
if err != nil {
return err
}
@@ -322,7 +322,7 @@ func resizeTTY(ctx context.Context, endpoint string, height *int, width *int) er
params.Set("w", strconv.Itoa(*width))
}
params.Set("running", "true")
- rsp, err := conn.DoRequest(nil, http.MethodPost, endpoint, params, nil)
+ rsp, err := conn.DoRequest(ctx, nil, http.MethodPost, endpoint, params, nil)
if err != nil {
return err
}
@@ -407,7 +407,7 @@ func ExecStartAndAttach(ctx context.Context, sessionID string, options *ExecStar
// We need to inspect the exec session first to determine whether to use
// -t.
- resp, err := conn.DoRequest(nil, http.MethodGet, "/exec/%s/json", nil, nil, sessionID)
+ resp, err := conn.DoRequest(ctx, nil, http.MethodGet, "/exec/%s/json", nil, nil, sessionID)
if err != nil {
return err
}
@@ -478,7 +478,7 @@ func ExecStartAndAttach(ctx context.Context, sessionID string, options *ExecStar
IdleConnTimeout: time.Duration(0),
}
conn.Client.Transport = t
- response, err := conn.DoRequest(bytes.NewReader(bodyJSON), http.MethodPost, "/exec/%s/start", nil, nil, sessionID)
+ response, err := conn.DoRequest(ctx, bytes.NewReader(bodyJSON), http.MethodPost, "/exec/%s/start", nil, nil, sessionID)
if err != nil {
return err
}
diff --git a/pkg/bindings/containers/checkpoint.go b/pkg/bindings/containers/checkpoint.go
index 7f7080f13..2ad2c6931 100644
--- a/pkg/bindings/containers/checkpoint.go
+++ b/pkg/bindings/containers/checkpoint.go
@@ -23,7 +23,7 @@ func Checkpoint(ctx context.Context, nameOrID string, options *CheckpointOptions
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/checkpoint", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/checkpoint", params, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -52,7 +52,7 @@ func Restore(ctx context.Context, nameOrID string, options *RestoreOptions) (*en
if i := options.GetImportAchive(); options.Changed("ImportArchive") {
params.Set("import", i)
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restore", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/restore", params, nil, nameOrID)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/containers/commit.go b/pkg/bindings/containers/commit.go
index a4adebb1f..372a99d32 100644
--- a/pkg/bindings/containers/commit.go
+++ b/pkg/bindings/containers/commit.go
@@ -24,7 +24,7 @@ func Commit(ctx context.Context, nameOrID string, options *CommitOptions) (handl
return handlers.IDResponse{}, err
}
params.Set("container", nameOrID)
- response, err := conn.DoRequest(nil, http.MethodPost, "/commit", params, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/commit", params, nil)
if err != nil {
return id, err
}
diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go
index aafb83f65..14a173025 100644
--- a/pkg/bindings/containers/containers.go
+++ b/pkg/bindings/containers/containers.go
@@ -38,7 +38,7 @@ func List(ctx context.Context, options *ListOptions) ([]entities.ListContainer,
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/containers/json", params, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/json", params, nil)
if err != nil {
return containers, err
}
@@ -64,7 +64,7 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*reports.PruneReport,
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/prune", params, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/prune", params, nil)
if err != nil {
return nil, err
}
@@ -90,7 +90,7 @@ func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) error
if err != nil {
return err
}
- response, err := conn.DoRequest(nil, http.MethodDelete, "/containers/%s", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/containers/%s", params, nil, nameOrID)
if err != nil {
return err
}
@@ -115,7 +115,7 @@ func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*de
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/json", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/json", params, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -140,7 +140,7 @@ func Kill(ctx context.Context, nameOrID string, options *KillOptions) error {
if err != nil {
return err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/kill", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/kill", params, nil, nameOrID)
if err != nil {
return err
}
@@ -160,7 +160,7 @@ func Pause(ctx context.Context, nameOrID string, options *PauseOptions) error {
if err != nil {
return err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/pause", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/pause", nil, nil, nameOrID)
if err != nil {
return err
}
@@ -184,7 +184,7 @@ func Restart(ctx context.Context, nameOrID string, options *RestartOptions) erro
if err != nil {
return err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restart", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/restart", params, nil, nameOrID)
if err != nil {
return err
}
@@ -209,7 +209,7 @@ func Start(ctx context.Context, nameOrID string, options *StartOptions) error {
if err != nil {
return err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/start", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/start", params, nil, nameOrID)
if err != nil {
return err
}
@@ -235,7 +235,7 @@ func Stats(ctx context.Context, containers []string, options *StatsOptions) (cha
params.Add("containers", c)
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/containers/stats", params, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/stats", params, nil)
if err != nil {
return nil, err
}
@@ -293,7 +293,7 @@ func Top(ctx context.Context, nameOrID string, options *TopOptions) ([]string, e
psArgs := strings.Join(options.GetDescriptors(), ",")
params.Add("ps_args", psArgs)
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/top", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/top", params, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -326,7 +326,7 @@ func Unpause(ctx context.Context, nameOrID string, options *UnpauseOptions) erro
if err != nil {
return err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unpause", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/unpause", nil, nil, nameOrID)
if err != nil {
return err
}
@@ -351,7 +351,7 @@ func Wait(ctx context.Context, nameOrID string, options *WaitOptions) (int32, er
if err != nil {
return exitCode, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/wait", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/wait", params, nil, nameOrID)
if err != nil {
return exitCode, err
}
@@ -372,7 +372,7 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool,
if err != nil {
return false, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/exists", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/exists", params, nil, nameOrID)
if err != nil {
return false, err
}
@@ -395,7 +395,7 @@ func Stop(ctx context.Context, nameOrID string, options *StopOptions) error {
if err != nil {
return err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/stop", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/stop", params, nil, nameOrID)
if err != nil {
return err
}
@@ -416,7 +416,7 @@ func Export(ctx context.Context, nameOrID string, w io.Writer, options *ExportOp
if err != nil {
return err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/export", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/export", params, nil, nameOrID)
if err != nil {
return err
}
@@ -441,7 +441,7 @@ func ContainerInit(ctx context.Context, nameOrID string, options *InitOptions) e
if err != nil {
return err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/init", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/init", nil, nil, nameOrID)
if err != nil {
return err
}
@@ -462,7 +462,7 @@ func ShouldRestart(ctx context.Context, nameOrID string, options *ShouldRestartO
if err != nil {
return false, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/shouldrestart", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/shouldrestart", nil, nil, nameOrID)
if err != nil {
return false, err
}
diff --git a/pkg/bindings/containers/create.go b/pkg/bindings/containers/create.go
index c0b9538a6..83b5b5ac7 100644
--- a/pkg/bindings/containers/create.go
+++ b/pkg/bindings/containers/create.go
@@ -26,7 +26,7 @@ func CreateWithSpec(ctx context.Context, s *specgen.SpecGenerator, options *Crea
return ccr, err
}
stringReader := strings.NewReader(specgenString)
- response, err := conn.DoRequest(stringReader, http.MethodPost, "/containers/create", nil, nil)
+ response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/containers/create", nil, nil)
if err != nil {
return ccr, err
}
diff --git a/pkg/bindings/containers/diff.go b/pkg/bindings/containers/diff.go
index e4ec49809..65a4f81bb 100644
--- a/pkg/bindings/containers/diff.go
+++ b/pkg/bindings/containers/diff.go
@@ -22,7 +22,7 @@ func Diff(ctx context.Context, nameOrID string, options *DiffOptions) ([]archive
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/changes", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/changes", params, nil, nameOrID)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/containers/exec.go b/pkg/bindings/containers/exec.go
index 12b31aba3..e41ed66c6 100644
--- a/pkg/bindings/containers/exec.go
+++ b/pkg/bindings/containers/exec.go
@@ -35,7 +35,7 @@ func ExecCreate(ctx context.Context, nameOrID string, config *handlers.ExecCreat
}
jsonReader := strings.NewReader(string(requestJSON))
- resp, err := conn.DoRequest(jsonReader, http.MethodPost, "/containers/%s/exec", nil, nil, nameOrID)
+ resp, err := conn.DoRequest(ctx, jsonReader, http.MethodPost, "/containers/%s/exec", nil, nil, nameOrID)
if err != nil {
return "", err
}
@@ -63,7 +63,7 @@ func ExecInspect(ctx context.Context, sessionID string, options *ExecInspectOpti
logrus.Debugf("Inspecting session ID %q", sessionID)
- resp, err := conn.DoRequest(nil, http.MethodGet, "/exec/%s/json", nil, nil, sessionID)
+ resp, err := conn.DoRequest(ctx, nil, http.MethodGet, "/exec/%s/json", nil, nil, sessionID)
if err != nil {
return nil, err
}
@@ -101,7 +101,7 @@ func ExecStart(ctx context.Context, sessionID string, options *ExecStartOptions)
return err
}
- resp, err := conn.DoRequest(bytes.NewReader(bodyJSON), http.MethodPost, "/exec/%s/start", nil, nil, sessionID)
+ resp, err := conn.DoRequest(ctx, bytes.NewReader(bodyJSON), http.MethodPost, "/exec/%s/start", nil, nil, sessionID)
if err != nil {
return err
}
diff --git a/pkg/bindings/containers/healthcheck.go b/pkg/bindings/containers/healthcheck.go
index 0e65a5a46..990d8dc69 100644
--- a/pkg/bindings/containers/healthcheck.go
+++ b/pkg/bindings/containers/healthcheck.go
@@ -22,7 +22,7 @@ func RunHealthCheck(ctx context.Context, nameOrID string, options *HealthCheckOp
var (
status define.HealthCheckResults
)
- response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/healthcheck", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/healthcheck", nil, nil, nameOrID)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/containers/logs.go b/pkg/bindings/containers/logs.go
index 37ffdf0a5..df1dd22ea 100644
--- a/pkg/bindings/containers/logs.go
+++ b/pkg/bindings/containers/logs.go
@@ -29,7 +29,7 @@ func Logs(ctx context.Context, nameOrID string, options *LogOptions, stdoutChan,
if options.Stdout == nil && options.Stderr == nil {
params.Set("stdout", strconv.FormatBool(true))
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/logs", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/%s/logs", params, nil, nameOrID)
if err != nil {
return err
}
diff --git a/pkg/bindings/containers/mount.go b/pkg/bindings/containers/mount.go
index c07998fd3..5756c4cab 100644
--- a/pkg/bindings/containers/mount.go
+++ b/pkg/bindings/containers/mount.go
@@ -21,7 +21,7 @@ func Mount(ctx context.Context, nameOrID string, options *MountOptions) (string,
var (
path string
)
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/mount", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/mount", nil, nil, nameOrID)
if err != nil {
return path, err
}
@@ -41,7 +41,7 @@ func Unmount(ctx context.Context, nameOrID string, options *UnmountOptions) erro
if err != nil {
return err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unmount", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/unmount", nil, nil, nameOrID)
if err != nil {
return err
}
@@ -61,7 +61,7 @@ func GetMountedContainerPaths(ctx context.Context, options *MountedContainerPath
return nil, err
}
mounts := make(map[string]string)
- response, err := conn.DoRequest(nil, http.MethodGet, "/containers/showmounted", nil, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/containers/showmounted", nil, nil)
if err != nil {
return mounts, err
}
diff --git a/pkg/bindings/containers/rename.go b/pkg/bindings/containers/rename.go
index 172d7838a..29dfc581b 100644
--- a/pkg/bindings/containers/rename.go
+++ b/pkg/bindings/containers/rename.go
@@ -20,7 +20,7 @@ func Rename(ctx context.Context, nameOrID string, options *RenameOptions) error
if err != nil {
return err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/rename", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/rename", params, nil, nameOrID)
if err != nil {
return err
}
diff --git a/pkg/bindings/generate/generate.go b/pkg/bindings/generate/generate.go
index 742956515..641c14231 100644
--- a/pkg/bindings/generate/generate.go
+++ b/pkg/bindings/generate/generate.go
@@ -22,7 +22,7 @@ func Systemd(ctx context.Context, nameOrID string, options *SystemdOptions) (*en
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/generate/%s/systemd", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/generate/%s/systemd", params, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -54,7 +54,7 @@ func Kube(ctx context.Context, nameOrIDs []string, options *KubeOptions) (*entit
for _, name := range nameOrIDs {
params.Add("names", name)
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/generate/kube", params, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/generate/kube", params, nil)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/generate/types.go b/pkg/bindings/generate/types.go
index 3c9ea87d4..6f2594604 100644
--- a/pkg/bindings/generate/types.go
+++ b/pkg/bindings/generate/types.go
@@ -16,6 +16,8 @@ type SystemdOptions struct {
New *bool
// NoHeader - Removes autogenerated by Podman and timestamp if set to true
NoHeader *bool
+ // TemplateUnitFile - Create a template unit file that uses the identity specifiers
+ TemplateUnitFile *bool
// RestartPolicy - systemd restart policy.
RestartPolicy *string
// StopTimeout - time when stopping the container.
diff --git a/pkg/bindings/generate/types_systemd_options.go b/pkg/bindings/generate/types_systemd_options.go
index 7a778a52b..b26aa7fc2 100644
--- a/pkg/bindings/generate/types_systemd_options.go
+++ b/pkg/bindings/generate/types_systemd_options.go
@@ -62,6 +62,21 @@ func (o *SystemdOptions) GetNoHeader() bool {
return *o.NoHeader
}
+// WithTemplateUnitFile set field TemplateUnitFile to given value
+func (o *SystemdOptions) WithTemplateUnitFile(value bool) *SystemdOptions {
+ o.TemplateUnitFile = &value
+ return o
+}
+
+// GetTemplateUnitFile returns value of field TemplateUnitFile
+func (o *SystemdOptions) GetTemplateUnitFile() bool {
+ if o.TemplateUnitFile == nil {
+ var z bool
+ return z
+ }
+ return *o.TemplateUnitFile
+}
+
// WithRestartPolicy set field RestartPolicy to given value
func (o *SystemdOptions) WithRestartPolicy(value string) *SystemdOptions {
o.RestartPolicy = &value
diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go
index 403d90721..3b0bebe9f 100644
--- a/pkg/bindings/images/build.go
+++ b/pkg/bindings/images/build.go
@@ -392,7 +392,7 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(tarfile, http.MethodPost, "/build", params, headers)
+ response, err := conn.DoRequest(ctx, tarfile, http.MethodPost, "/build", params, headers)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/images/diff.go b/pkg/bindings/images/diff.go
index 671b73089..3df0b9615 100644
--- a/pkg/bindings/images/diff.go
+++ b/pkg/bindings/images/diff.go
@@ -19,7 +19,7 @@ func Diff(ctx context.Context, nameOrID string, options *DiffOptions) ([]archive
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/changes", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/%s/changes", nil, nil, nameOrID)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go
index 959481e0d..dfb500772 100644
--- a/pkg/bindings/images/images.go
+++ b/pkg/bindings/images/images.go
@@ -23,7 +23,7 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool,
if err != nil {
return false, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/exists", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/%s/exists", nil, nil, nameOrID)
if err != nil {
return false, err
}
@@ -47,7 +47,7 @@ func List(ctx context.Context, options *ListOptions) ([]*entities.ImageSummary,
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/images/json", params, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/json", params, nil)
if err != nil {
return imageSummary, err
}
@@ -71,7 +71,7 @@ func GetImage(ctx context.Context, nameOrID string, options *GetOptions) (*entit
return nil, err
}
inspectedData := entities.ImageInspectReport{}
- response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/json", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/%s/json", params, nil, nameOrID)
if err != nil {
return &inspectedData, err
}
@@ -94,7 +94,7 @@ func Tree(ctx context.Context, nameOrID string, options *TreeOptions) (*entities
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/tree", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/%s/tree", params, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -114,7 +114,7 @@ func History(ctx context.Context, nameOrID string, options *HistoryOptions) ([]*
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/history", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/%s/history", nil, nil, nameOrID)
if err != nil {
return history, err
}
@@ -129,7 +129,7 @@ func Load(ctx context.Context, r io.Reader) (*entities.ImageLoadReport, error) {
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(r, http.MethodPost, "/images/load", nil, nil)
+ response, err := conn.DoRequest(ctx, r, http.MethodPost, "/images/load", nil, nil)
if err != nil {
return nil, err
}
@@ -155,7 +155,7 @@ func Export(ctx context.Context, nameOrIDs []string, w io.Writer, options *Expor
for _, ref := range nameOrIDs {
params.Add("references", ref)
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/images/export", params, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/export", params, nil)
if err != nil {
return err
}
@@ -185,7 +185,7 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*reports.PruneReport,
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/images/prune", params, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/images/prune", params, nil)
if err != nil {
return deleted, err
}
@@ -207,7 +207,7 @@ func Tag(ctx context.Context, nameOrID, tag, repo string, options *TagOptions) e
params := url.Values{}
params.Set("tag", tag)
params.Set("repo", repo)
- response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/tag", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/images/%s/tag", params, nil, nameOrID)
if err != nil {
return err
}
@@ -229,7 +229,7 @@ func Untag(ctx context.Context, nameOrID, tag, repo string, options *UntagOption
params := url.Values{}
params.Set("tag", tag)
params.Set("repo", repo)
- response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/untag", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/images/%s/untag", params, nil, nameOrID)
if err != nil {
return err
}
@@ -257,7 +257,7 @@ func Import(ctx context.Context, r io.Reader, options *ImportOptions) (*entities
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(r, http.MethodPost, "/images/import", params, nil)
+ response, err := conn.DoRequest(ctx, r, http.MethodPost, "/images/import", params, nil)
if err != nil {
return nil, err
}
@@ -298,7 +298,7 @@ func Push(ctx context.Context, source string, destination string, options *PushO
params.Set("destination", destination)
path := fmt.Sprintf("/images/%s/push", source)
- response, err := conn.DoRequest(nil, http.MethodPost, path, params, header)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, path, params, header)
if err != nil {
return err
}
@@ -334,7 +334,7 @@ func Search(ctx context.Context, term string, options *SearchOptions) ([]entitie
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/images/search", params, header)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/images/search", params, header)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/images/pull.go b/pkg/bindings/images/pull.go
index 7dfe9560c..be21aa593 100644
--- a/pkg/bindings/images/pull.go
+++ b/pkg/bindings/images/pull.go
@@ -47,7 +47,7 @@ func Pull(ctx context.Context, rawImage string, options *PullOptions) ([]string,
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/images/pull", params, header)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/images/pull", params, header)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/images/rm.go b/pkg/bindings/images/rm.go
index 461eb7729..47d7c2a4b 100644
--- a/pkg/bindings/images/rm.go
+++ b/pkg/bindings/images/rm.go
@@ -32,7 +32,7 @@ func Remove(ctx context.Context, images []string, options *RemoveOptions) (*enti
for _, image := range images {
params.Add("images", image)
}
- response, err := conn.DoRequest(nil, http.MethodDelete, "/images/remove", params, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/images/remove", params, nil)
if err != nil {
return nil, []error{err}
}
diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go
index 6aa4961f1..af74eb406 100644
--- a/pkg/bindings/manifests/manifests.go
+++ b/pkg/bindings/manifests/manifests.go
@@ -42,7 +42,7 @@ func Create(ctx context.Context, names, images []string, options *CreateOptions)
params.Add("image", i)
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/create", params, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/manifests/create", params, nil)
if err != nil {
return "", err
}
@@ -57,7 +57,7 @@ func Exists(ctx context.Context, name string, options *ExistsOptions) (bool, err
if err != nil {
return false, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/manifests/%s/exists", nil, nil, name)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/manifests/%s/exists", nil, nil, name)
if err != nil {
return false, err
}
@@ -77,7 +77,7 @@ func Inspect(ctx context.Context, name string, options *InspectOptions) (*manife
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/manifests/%s/json", nil, nil, name)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/manifests/%s/json", nil, nil, name)
if err != nil {
return nil, err
}
@@ -102,7 +102,7 @@ func Add(ctx context.Context, name string, options *AddOptions) (string, error)
return "", err
}
stringReader := strings.NewReader(optionsString)
- response, err := conn.DoRequest(stringReader, http.MethodPost, "/manifests/%s/add", nil, nil, name)
+ response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/manifests/%s/add", nil, nil, name)
if err != nil {
return "", err
}
@@ -125,7 +125,7 @@ func Remove(ctx context.Context, name, digest string, options *RemoveOptions) (s
}
params := url.Values{}
params.Set("digest", digest)
- response, err := conn.DoRequest(nil, http.MethodDelete, "/manifests/%s", params, nil, name)
+ response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/manifests/%s", params, nil, name)
if err != nil {
return "", err
}
@@ -163,7 +163,7 @@ func Push(ctx context.Context, name, destination string, options *images.PushOpt
}
params.Set("image", name)
params.Set("destination", destination)
- response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/%s/push", params, nil, name)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/manifests/%s/push", params, nil, name)
if err != nil {
return "", err
}
@@ -187,7 +187,7 @@ func Push(ctx context.Context, name, destination string, options *images.PushOpt
// return "", err
// }
// stringReader := strings.NewReader(optionsString)
-// response, err := conn.DoRequest(stringReader, http.MethodPost, "/manifests/%s/annotate", params, name)
+// response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/manifests/%s/annotate", params, name)
// if err != nil {
// return "", err
// }
diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go
index 5a0a34f56..172598be1 100644
--- a/pkg/bindings/network/network.go
+++ b/pkg/bindings/network/network.go
@@ -28,7 +28,7 @@ func Create(ctx context.Context, network *types.Network) (types.Network, error)
return report, err
}
reader := strings.NewReader(networkConfig)
- response, err := conn.DoRequest(reader, http.MethodPost, "/networks/create", nil, nil)
+ response, err := conn.DoRequest(ctx, reader, http.MethodPost, "/networks/create", nil, nil)
if err != nil {
return report, err
}
@@ -44,7 +44,7 @@ func Inspect(ctx context.Context, nameOrID string, _ *InspectOptions) (types.Net
if err != nil {
return net, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/networks/%s/json", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/networks/%s/json", nil, nil, nameOrID)
if err != nil {
return net, err
}
@@ -69,7 +69,7 @@ func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) ([]*en
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodDelete, "/networks/%s", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/networks/%s", params, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -92,7 +92,7 @@ func List(ctx context.Context, options *ListOptions) ([]types.Network, error) {
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/networks/json", params, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/networks/json", params, nil)
if err != nil {
return netList, err
}
@@ -128,7 +128,7 @@ func Disconnect(ctx context.Context, networkName string, ContainerNameOrID strin
return err
}
stringReader := strings.NewReader(body)
- response, err := conn.DoRequest(stringReader, http.MethodPost, "/networks/%s/disconnect", params, nil, networkName)
+ response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/networks/%s/disconnect", params, nil, networkName)
if err != nil {
return err
}
@@ -163,7 +163,7 @@ func Connect(ctx context.Context, networkName string, ContainerNameOrID string,
return err
}
stringReader := strings.NewReader(body)
- response, err := conn.DoRequest(stringReader, http.MethodPost, "/networks/%s/connect", params, nil, networkName)
+ response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/networks/%s/connect", params, nil, networkName)
if err != nil {
return err
}
@@ -178,7 +178,7 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool,
if err != nil {
return false, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/networks/%s/exists", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/networks/%s/exists", nil, nil, nameOrID)
if err != nil {
return false, err
}
@@ -204,7 +204,7 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*entities.NetworkPrune
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/networks/prune", params, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/networks/prune", params, nil)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/play/play.go b/pkg/bindings/play/play.go
index bdd13d03d..2cd7c3997 100644
--- a/pkg/bindings/play/play.go
+++ b/pkg/bindings/play/play.go
@@ -45,7 +45,7 @@ func Kube(ctx context.Context, path string, options *KubeOptions) (*entities.Pla
return nil, err
}
- response, err := conn.DoRequest(f, http.MethodPost, "/play/kube", params, header)
+ response, err := conn.DoRequest(ctx, f, http.MethodPost, "/play/kube", params, header)
if err != nil {
return nil, err
}
@@ -74,7 +74,7 @@ func KubeDown(ctx context.Context, path string) (*entities.PlayKubeReport, error
logrus.Warn(err)
}
}()
- response, err := conn.DoRequest(f, http.MethodDelete, "/play/kube", nil, nil)
+ response, err := conn.DoRequest(ctx, f, http.MethodDelete, "/play/kube", nil, nil)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go
index 3b5832373..9e32f766d 100644
--- a/pkg/bindings/pods/pods.go
+++ b/pkg/bindings/pods/pods.go
@@ -29,7 +29,7 @@ func CreatePodFromSpec(ctx context.Context, spec *entities.PodSpec) (*entities.P
return nil, err
}
stringReader := strings.NewReader(specString)
- response, err := conn.DoRequest(stringReader, http.MethodPost, "/pods/create", nil, nil)
+ response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/pods/create", nil, nil)
if err != nil {
return nil, err
}
@@ -44,7 +44,7 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool,
if err != nil {
return false, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/exists", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/pods/%s/exists", nil, nil, nameOrID)
if err != nil {
return false, err
}
@@ -66,7 +66,7 @@ func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*en
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/json", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/pods/%s/json", nil, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -92,7 +92,7 @@ func Kill(ctx context.Context, nameOrID string, options *KillOptions) (*entities
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/kill", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/%s/kill", params, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -112,7 +112,7 @@ func Pause(ctx context.Context, nameOrID string, options *PauseOptions) (*entiti
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/pause", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/%s/pause", nil, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -133,7 +133,7 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*entities.PodPruneRepo
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/pods/prune", nil, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/prune", nil, nil)
if err != nil {
return nil, err
}
@@ -159,7 +159,7 @@ func List(ctx context.Context, options *ListOptions) ([]*entities.ListPodsReport
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/pods/json", params, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/pods/json", params, nil)
if err != nil {
return podsReports, err
}
@@ -179,7 +179,7 @@ func Restart(ctx context.Context, nameOrID string, options *RestartOptions) (*en
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/restart", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/%s/restart", nil, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -203,7 +203,7 @@ func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) (*enti
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodDelete, "/pods/%s", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/pods/%s", params, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -223,7 +223,7 @@ func Start(ctx context.Context, nameOrID string, options *StartOptions) (*entiti
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/start", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/%s/start", nil, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -252,7 +252,7 @@ func Stop(ctx context.Context, nameOrID string, options *StopOptions) (*entities
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/stop", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/%s/stop", params, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -279,7 +279,7 @@ func Top(ctx context.Context, nameOrID string, options *TopOptions) ([]string, e
if descriptors := options.GetDescriptors(); len(descriptors) > 0 {
params.Set("ps_args", strings.Join(descriptors, ","))
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/top", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/pods/%s/top", params, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -312,7 +312,7 @@ func Unpause(ctx context.Context, nameOrID string, options *UnpauseOptions) (*en
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/unpause", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/pods/%s/unpause", nil, nil, nameOrID)
if err != nil {
return nil, err
}
@@ -339,7 +339,7 @@ func Stats(ctx context.Context, namesOrIDs []string, options *StatsOptions) ([]*
}
var reports []*entities.PodStatsReport
- response, err := conn.DoRequest(nil, http.MethodGet, "/pods/stats", params, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/pods/stats", params, nil)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/secrets/secrets.go b/pkg/bindings/secrets/secrets.go
index c439971c9..3847188a5 100644
--- a/pkg/bindings/secrets/secrets.go
+++ b/pkg/bindings/secrets/secrets.go
@@ -22,7 +22,7 @@ func List(ctx context.Context, options *ListOptions) ([]*entities.SecretInfoRepo
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/secrets/json", params, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/secrets/json", params, nil)
if err != nil {
return secrs, err
}
@@ -40,7 +40,7 @@ func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*en
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/secrets/%s/json", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/secrets/%s/json", nil, nil, nameOrID)
if err != nil {
return inspect, err
}
@@ -56,7 +56,7 @@ func Remove(ctx context.Context, nameOrID string) error {
return err
}
- response, err := conn.DoRequest(nil, http.MethodDelete, "/secrets/%s", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/secrets/%s", nil, nil, nameOrID)
if err != nil {
return err
}
@@ -80,7 +80,7 @@ func Create(ctx context.Context, reader io.Reader, options *CreateOptions) (*ent
return nil, err
}
- response, err := conn.DoRequest(reader, http.MethodPost, "/secrets/create", params, nil)
+ response, err := conn.DoRequest(ctx, reader, http.MethodPost, "/secrets/create", params, nil)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/system/info.go b/pkg/bindings/system/info.go
index 8a307a4ca..8d7c30b26 100644
--- a/pkg/bindings/system/info.go
+++ b/pkg/bindings/system/info.go
@@ -14,7 +14,7 @@ func Info(ctx context.Context, _ *InfoOptions) (*define.Info, error) {
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/info", nil, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/info", nil, nil)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/system/system.go b/pkg/bindings/system/system.go
index 719cde52e..3f59b3d7e 100644
--- a/pkg/bindings/system/system.go
+++ b/pkg/bindings/system/system.go
@@ -27,7 +27,7 @@ func Events(ctx context.Context, eventChan chan entities.Event, cancelChan chan
if err != nil {
return err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/events", params, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/events", params, nil)
if err != nil {
return err
}
@@ -73,7 +73,7 @@ func Prune(ctx context.Context, options *PruneOptions) (*entities.SystemPruneRep
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/system/prune", params, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/system/prune", params, nil)
if err != nil {
return nil, err
}
@@ -101,7 +101,7 @@ func Version(ctx context.Context, options *VersionOptions) (*entities.SystemVers
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/version", nil, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/version", nil, nil)
if err != nil {
return nil, err
}
@@ -142,7 +142,7 @@ func DiskUsage(ctx context.Context, options *DiskOptions) (*entities.SystemDfRep
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/system/df", nil, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/system/df", nil, nil)
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/test/connection_test.go b/pkg/bindings/test/connection_test.go
new file mode 100644
index 000000000..561cf32b5
--- /dev/null
+++ b/pkg/bindings/test/connection_test.go
@@ -0,0 +1,68 @@
+package test_bindings
+
+import (
+ "context"
+ "time"
+
+ "github.com/containers/podman/v3/pkg/bindings/containers"
+ "github.com/containers/podman/v3/pkg/bindings/system"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Podman connection", func() {
+ var (
+ bt *bindingTest
+ s *gexec.Session
+ )
+
+ BeforeEach(func() {
+ bt = newBindingTest()
+ bt.RestoreImagesFromCache()
+ s = bt.startAPIService()
+ time.Sleep(1 * time.Second)
+ err := bt.NewConnection()
+ Expect(err).To(BeNil())
+ })
+
+ AfterEach(func() {
+ s.Kill()
+ bt.cleanup()
+ })
+
+ It("request on cancelled context results in error", func() {
+ ctx, cancel := context.WithCancel(bt.conn)
+ cancel()
+ _, err := system.Version(ctx, nil)
+ Expect(err).To(MatchError(ctx.Err()))
+ })
+
+ It("cancel request in flight reports cancelled context", func() {
+ var name = "top"
+ _, err := bt.RunTopContainer(&name, nil)
+ Expect(err).To(BeNil())
+
+ errChan := make(chan error)
+ ctx, cancel := context.WithCancel(bt.conn)
+
+ go func() {
+ defer close(errChan)
+ _, err := containers.Wait(ctx, name, nil)
+ errChan <- err
+ }()
+
+ // Wait for the goroutine to fire the request
+ time.Sleep(1 * time.Second)
+
+ cancel()
+
+ select {
+ case err, ok := <-errChan:
+ Expect(ok).To(BeTrue())
+ Expect(err).To(MatchError(ctx.Err()))
+ case <-time.NewTimer(1 * time.Second).C:
+ Fail("cancelled request did not return in less than 1 second")
+ }
+ })
+})
diff --git a/pkg/bindings/volumes/volumes.go b/pkg/bindings/volumes/volumes.go
index 56cf13ade..ce5a01c49 100644
--- a/pkg/bindings/volumes/volumes.go
+++ b/pkg/bindings/volumes/volumes.go
@@ -29,7 +29,7 @@ func Create(ctx context.Context, config entities.VolumeCreateOptions, options *C
return nil, err
}
stringReader := strings.NewReader(createString)
- response, err := conn.DoRequest(stringReader, http.MethodPost, "/volumes/create", nil, nil)
+ response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/volumes/create", nil, nil)
if err != nil {
return nil, err
}
@@ -51,7 +51,7 @@ func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*en
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/%s/json", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/volumes/%s/json", nil, nil, nameOrID)
if err != nil {
return &inspect, err
}
@@ -74,7 +74,7 @@ func List(ctx context.Context, options *ListOptions) ([]*entities.VolumeListRepo
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/json", params, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/volumes/json", params, nil)
if err != nil {
return vols, err
}
@@ -96,7 +96,7 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*reports.PruneReport,
if err != nil {
return nil, err
}
- response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", params, nil)
+ response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/volumes/prune", params, nil)
if err != nil {
return nil, err
}
@@ -116,7 +116,7 @@ func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) error
if err != nil {
return err
}
- response, err := conn.DoRequest(nil, http.MethodDelete, "/volumes/%s", params, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/volumes/%s", params, nil, nameOrID)
if err != nil {
return err
}
@@ -131,7 +131,7 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool,
if err != nil {
return false, err
}
- response, err := conn.DoRequest(nil, http.MethodGet, "/volumes/%s/exists", nil, nil, nameOrID)
+ response, err := conn.DoRequest(ctx, nil, http.MethodGet, "/volumes/%s/exists", nil, nil, nameOrID)
if err != nil {
return false, err
}
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index 869c616ea..8b7cd62d9 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -190,11 +190,14 @@ type CheckpointOptions struct {
PreCheckPoint bool
WithPrevious bool
Compression archive.Compression
+ PrintStats bool
}
type CheckpointReport struct {
- Err error
- Id string //nolint
+ Err error `json:"-"`
+ Id string `json:"Id` //nolint
+ RuntimeDuration int64 `json:"runtime_checkpoint_duration"`
+ CRIUStatistics *define.CRIUCheckpointRestoreStatistics `json:"criu_statistics"`
}
type RestoreOptions struct {
@@ -211,11 +214,14 @@ type RestoreOptions struct {
ImportPrevious string
PublishPorts []nettypes.PortMapping
Pod string
+ PrintStats bool
}
type RestoreReport struct {
- Err error
- Id string //nolint
+ Err error `json:"-"`
+ Id string `json:"Id` //nolint
+ RuntimeDuration int64 `json:"runtime_restore_duration"`
+ CRIUStatistics *define.CRIUCheckpointRestoreStatistics `json:"criu_statistics"`
}
type ContainerCreateReport struct {
diff --git a/pkg/domain/entities/generate.go b/pkg/domain/entities/generate.go
index 7809c5241..dfb5bfc6c 100644
--- a/pkg/domain/entities/generate.go
+++ b/pkg/domain/entities/generate.go
@@ -20,6 +20,8 @@ type GenerateSystemdOptions struct {
Separator string
// NoHeader - skip header generation
NoHeader bool
+ // TemplateUnitFile - make use of %i and %I to differentiate between the different instances of the unit
+ TemplateUnitFile bool
}
// GenerateSystemdReport
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index c30129001..69c628669 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -515,6 +515,7 @@ func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds [
PreCheckPoint: options.PreCheckPoint,
WithPrevious: options.WithPrevious,
Compression: options.Compression,
+ PrintStats: options.PrintStats,
}
if options.All {
@@ -531,10 +532,12 @@ func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds [
}
reports := make([]*entities.CheckpointReport, 0, len(cons))
for _, con := range cons {
- err = con.Checkpoint(ctx, checkOpts)
+ criuStatistics, runtimeCheckpointDuration, err := con.Checkpoint(ctx, checkOpts)
reports = append(reports, &entities.CheckpointReport{
- Err: err,
- Id: con.ID(),
+ Err: err,
+ Id: con.ID(),
+ RuntimeDuration: runtimeCheckpointDuration,
+ CRIUStatistics: criuStatistics,
})
}
return reports, nil
@@ -557,6 +560,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
IgnoreStaticMAC: options.IgnoreStaticMAC,
ImportPrevious: options.ImportPrevious,
Pod: options.Pod,
+ PrintStats: options.PrintStats,
}
filterFuncs := []libpod.ContainerFilter{
@@ -579,10 +583,12 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
}
reports := make([]*entities.RestoreReport, 0, len(cons))
for _, con := range cons {
- err := con.Restore(ctx, restoreOptions)
+ criuStatistics, runtimeRestoreDuration, err := con.Restore(ctx, restoreOptions)
reports = append(reports, &entities.RestoreReport{
- Err: err,
- Id: con.ID(),
+ Err: err,
+ Id: con.ID(),
+ RuntimeDuration: runtimeRestoreDuration,
+ CRIUStatistics: criuStatistics,
})
}
return reports, nil
diff --git a/pkg/domain/infra/tunnel/generate.go b/pkg/domain/infra/tunnel/generate.go
index 9f69abb1a..3a35dd59c 100644
--- a/pkg/domain/infra/tunnel/generate.go
+++ b/pkg/domain/infra/tunnel/generate.go
@@ -8,7 +8,7 @@ import (
)
func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, opts entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) {
- options := new(generate.SystemdOptions).WithUseName(opts.Name).WithContainerPrefix(opts.ContainerPrefix).WithNew(opts.New).WithNoHeader(opts.NoHeader)
+ options := new(generate.SystemdOptions).WithUseName(opts.Name).WithContainerPrefix(opts.ContainerPrefix).WithNew(opts.New).WithNoHeader(opts.NoHeader).WithTemplateUnitFile(opts.TemplateUnitFile)
options.WithPodPrefix(opts.PodPrefix).WithSeparator(opts.Separator)
if opts.RestartPolicy != nil {
options.WithRestartPolicy(*opts.RestartPolicy)
diff --git a/pkg/systemd/generate/common.go b/pkg/systemd/generate/common.go
index 3515bb3b7..24c85a27e 100644
--- a/pkg/systemd/generate/common.go
+++ b/pkg/systemd/generate/common.go
@@ -23,7 +23,7 @@ func validateRestartPolicy(restart string) error {
return errors.Errorf("%s is not a valid restart policy", restart)
}
-const headerTemplate = `# {{{{.ServiceName}}}}.service
+const headerTemplate = `# {{{{.ServiceName}}}}{{{{- if (eq .IdentifySpecifier true) }}}}@{{{{- end}}}}.service
{{{{- if (eq .GenerateNoHeader false) }}}}
# autogenerated by Podman {{{{.PodmanVersion}}}}
{{{{- if .TimeStamp}}}}
@@ -32,7 +32,7 @@ const headerTemplate = `# {{{{.ServiceName}}}}.service
{{{{- end}}}}
[Unit]
-Description=Podman {{{{.ServiceName}}}}.service
+Description=Podman {{{{.ServiceName}}}}.service{{{{- if (eq .IdentifySpecifier true) }}}} for %I{{{{- end}}}}
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go
index 037652a6d..95ff13371 100644
--- a/pkg/systemd/generate/containers.go
+++ b/pkg/systemd/generate/containers.go
@@ -90,6 +90,8 @@ type containerInfo struct {
// Location of the RunRoot for the container. Required for ensuring the tmpfs
// or volume exists and is mounted when coming online at boot.
RunRoot string
+ // Add %i and %I to description and execute parts
+ IdentifySpecifier bool
}
const containerTemplate = headerTemplate + `
@@ -99,7 +101,7 @@ After={{{{- range $index, $value := .BoundToServices -}}}}{{{{if $index}}}} {{{{
{{{{- end}}}}
[Service]
-Environment={{{{.EnvVariable}}}}=%n
+Environment={{{{.EnvVariable}}}}=%n{{{{- if (eq .IdentifySpecifier true) }}}}-%i{{{{- end}}}}
{{{{- if .ExtraEnvs}}}}
Environment={{{{- range $index, $value := .ExtraEnvs -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}{{{{end}}}}
{{{{- end}}}}
@@ -204,6 +206,46 @@ func containerServiceName(ctr *libpod.Container, options entities.GenerateSystem
return nameOrID, serviceName
}
+// setContainerNameForTemplate updates startCommand to contain the name argument with
+// a value that includes the identify specifier.
+// In case startCommand doesn't contain that argument it's added after "run" and its
+// value will be set to info.ServiceName concated with the identify specifier %i.
+func setContainerNameForTemplate(startCommand []string, info *containerInfo) ([]string, error) {
+ // find the index of "--name" in the command slice
+ nameIx := -1
+ for argIx, arg := range startCommand {
+ if arg == "--name" {
+ nameIx = argIx + 1
+ break
+ }
+ if strings.HasPrefix(arg, "--name=") {
+ nameIx = argIx
+ break
+ }
+ }
+ switch {
+ case nameIx == -1:
+ // if not found, add --name argument in the command slice before the "run" argument.
+ // it's assumed that the command slice contains this argument.
+ runIx := -1
+ for argIx, arg := range startCommand {
+ if arg == "run" {
+ runIx = argIx
+ break
+ }
+ }
+ if runIx == -1 {
+ return startCommand, fmt.Errorf("\"run\" is missing in the command arguments")
+ }
+ startCommand = append(startCommand[:runIx+1], startCommand[runIx:]...)
+ startCommand[runIx+1] = fmt.Sprintf("--name=%s-%%i", info.ServiceName)
+ default:
+ // append the identity specifier (%i) to the end of the --name value
+ startCommand[nameIx] = fmt.Sprintf("%s-%%i", startCommand[nameIx])
+ }
+ return startCommand, nil
+}
+
// executeContainerTemplate executes the container template on the specified
// containerInfo. Note that the containerInfo is also post processed and
// completed, which allows for an easier unit testing.
@@ -273,7 +315,6 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst
"--rm",
)
remainingCmd := info.CreateCommand[index:]
-
// Presence check for certain flags/options.
fs := pflag.NewFlagSet("args", pflag.ContinueOnError)
fs.ParseErrorsWhitelist.UnknownFlags = true
@@ -389,6 +430,13 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst
startCommand = append(startCommand, remainingCmd...)
startCommand = escapeSystemdArguments(startCommand)
+ if options.TemplateUnitFile {
+ info.IdentifySpecifier = true
+ startCommand, err = setContainerNameForTemplate(startCommand, info)
+ if err != nil {
+ return "", err
+ }
+ }
info.ExecStart = strings.Join(startCommand, " ")
}
diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/containers_test.go
index f46513459..eab2c2e67 100644
--- a/pkg/systemd/generate/containers_test.go
+++ b/pkg/systemd/generate/containers_test.go
@@ -1,6 +1,7 @@
package generate
import (
+ "fmt"
"testing"
"github.com/containers/podman/v3/pkg/domain/entities"
@@ -522,6 +523,32 @@ NotifyAccess=all
[Install]
WantedBy=multi-user.target default.target
`
+
+ templateGood := `# container-foo@.service
+# autogenerated by Podman CI
+
+[Unit]
+Description=Podman container-foo.service for %I
+Documentation=man:podman-generate-systemd(1)
+Wants=network-online.target
+After=network-online.target
+RequiresMountsFor=/var/run/containers/storage
+
+[Service]
+Environment=PODMAN_SYSTEMD_UNIT=%n-%i
+Restart=on-failure
+StartLimitBurst=42
+TimeoutStopSec=70
+ExecStartPre=/bin/rm -f %t/%n.ctr-id
+ExecStart=/usr/bin/podman run --name=container-foo-%i --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --sdnotify=conmon -d awesome-image:latest
+ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id
+ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id
+Type=notify
+NotifyAccess=all
+
+[Install]
+WantedBy=multi-user.target default.target
+`
tests := []struct {
name string
info containerInfo
@@ -529,6 +556,7 @@ WantedBy=multi-user.target default.target
new bool
noHeader bool
wantErr bool
+ template bool
}{
{"good with id",
@@ -547,6 +575,7 @@ WantedBy=multi-user.target default.target
false,
false,
false,
+ false,
},
{"good with noHeader",
containerInfo{
@@ -564,6 +593,7 @@ WantedBy=multi-user.target default.target
false,
true,
false,
+ false,
},
{"good with name",
containerInfo{
@@ -581,6 +611,7 @@ WantedBy=multi-user.target default.target
false,
false,
false,
+ false,
},
{"good with name and bound to",
containerInfo{
@@ -599,6 +630,7 @@ WantedBy=multi-user.target default.target
false,
false,
false,
+ false,
},
{"good with name and generic",
containerInfo{
@@ -617,6 +649,7 @@ WantedBy=multi-user.target default.target
true,
false,
false,
+ false,
},
{"good with name and sdnotify",
containerInfo{
@@ -635,6 +668,7 @@ WantedBy=multi-user.target default.target
true,
false,
false,
+ false,
},
{"good with explicit short detach param",
containerInfo{
@@ -653,6 +687,7 @@ WantedBy=multi-user.target default.target
true,
false,
false,
+ false,
},
{"good with explicit short detach param and podInfo",
containerInfo{
@@ -674,6 +709,7 @@ WantedBy=multi-user.target default.target
true,
false,
false,
+ false,
},
{"good with explicit full detach param",
containerInfo{
@@ -692,6 +728,7 @@ WantedBy=multi-user.target default.target
true,
false,
false,
+ false,
},
{"good with id and no param",
containerInfo{
@@ -710,6 +747,7 @@ WantedBy=multi-user.target default.target
true,
false,
false,
+ false,
},
{"good with explicit detach=true param",
containerInfo{
@@ -728,6 +766,7 @@ WantedBy=multi-user.target default.target
true,
false,
false,
+ false,
},
{"good with explicit detach=false param",
containerInfo{
@@ -746,6 +785,7 @@ WantedBy=multi-user.target default.target
true,
false,
false,
+ false,
},
{"good with explicit detach=false param",
containerInfo{
@@ -764,6 +804,7 @@ WantedBy=multi-user.target default.target
true,
false,
false,
+ false,
},
{"good with multiple detach=false params",
containerInfo{
@@ -782,6 +823,7 @@ WantedBy=multi-user.target default.target
true,
false,
false,
+ false,
},
{"good with multiple shorthand params detach first",
containerInfo{
@@ -800,6 +842,7 @@ WantedBy=multi-user.target default.target
true,
false,
false,
+ false,
},
{"good with multiple shorthand params detach last",
containerInfo{
@@ -818,6 +861,7 @@ WantedBy=multi-user.target default.target
true,
false,
false,
+ false,
},
{"good with container create",
containerInfo{
@@ -836,6 +880,7 @@ WantedBy=multi-user.target default.target
true,
false,
false,
+ false,
},
{"good with journald log tag (see #9034)",
containerInfo{
@@ -854,6 +899,7 @@ WantedBy=multi-user.target default.target
true,
false,
false,
+ false,
},
{"good with special chars",
containerInfo{
@@ -872,6 +918,7 @@ WantedBy=multi-user.target default.target
true,
false,
false,
+ false,
},
{"good with ID files",
containerInfo{
@@ -890,6 +937,7 @@ WantedBy=multi-user.target default.target
true,
false,
false,
+ false,
},
{"good with pod ID files",
containerInfo{
@@ -911,6 +959,7 @@ WantedBy=multi-user.target default.target
true,
false,
false,
+ false,
},
{"good with environment variables",
containerInfo{
@@ -930,6 +979,7 @@ WantedBy=multi-user.target default.target
true,
false,
false,
+ false,
},
{"good with restart policy",
containerInfo{
@@ -948,14 +998,34 @@ WantedBy=multi-user.target default.target
true,
false,
false,
+ false,
+ },
+ {"good template",
+ containerInfo{
+ Executable: "/usr/bin/podman",
+ ServiceName: "container-foo",
+ ContainerNameOrID: "foo",
+ PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
+ StopTimeout: 10,
+ PodmanVersion: "CI",
+ GraphRoot: "/var/lib/containers/storage",
+ RunRoot: "/var/run/containers/storage",
+ CreateCommand: []string{"I'll get stripped", "create", "--restart", "on-failure:42", "awesome-image:latest"},
+ },
+ templateGood,
+ true,
+ false,
+ false,
+ true,
},
}
for _, tt := range tests {
test := tt
t.Run(tt.name, func(t *testing.T) {
opts := entities.GenerateSystemdOptions{
- New: test.new,
- NoHeader: test.noHeader,
+ New: test.new,
+ NoHeader: test.noHeader,
+ TemplateUnitFile: test.template,
}
test.info.RestartPolicy = define.DefaultRestartPolicy
got, err := executeContainerTemplate(&test.info, opts)
@@ -967,3 +1037,48 @@ WantedBy=multi-user.target default.target
})
}
}
+
+func TestSetContainerNameForTemplate(t *testing.T) {
+ tt := []struct {
+ name string
+ startCommand []string
+ info *containerInfo
+ expected []string
+ err error
+ }{
+ {
+ name: "no name argument is set",
+ startCommand: []string{"/usr/bin/podman", "run", "busybox", "top"},
+ info: &containerInfo{ServiceName: "container-122"},
+ expected: []string{"/usr/bin/podman", "run", "--name=container-122-%i", "busybox", "top"},
+ err: nil,
+ },
+ {
+ name: "--name=value is used in arguments",
+ startCommand: []string{"/usr/bin/podman", "run", "--name=lovely_james", "busybox", "top"},
+ info: &containerInfo{},
+ expected: []string{"/usr/bin/podman", "run", "--name=lovely_james-%i", "busybox", "top"},
+ err: nil,
+ },
+ {
+ name: "--name value is used in arguments",
+ startCommand: []string{"/usr/bin/podman", "run", "--name", "lovely_james", "busybox", "top"},
+ info: &containerInfo{},
+ expected: []string{"/usr/bin/podman", "run", "--name", "lovely_james-%i", "busybox", "top"},
+ err: nil,
+ },
+ {
+ name: "--name value is used in arguments",
+ startCommand: []string{"/usr/bin/podman", "create", "busybox", "top"},
+ info: &containerInfo{},
+ expected: []string{"/usr/bin/podman", "create", "busybox", "top"},
+ err: fmt.Errorf("\"run\" is missing in the command arguments"),
+ },
+ }
+
+ for _, te := range tt {
+ res, err := setContainerNameForTemplate(te.startCommand, te.info)
+ assert.Equal(t, te.err, err)
+ assert.Equal(t, te.expected, res)
+ }
+}
diff --git a/pkg/systemd/generate/pods.go b/pkg/systemd/generate/pods.go
index e755b8eea..38f7e8e3e 100644
--- a/pkg/systemd/generate/pods.go
+++ b/pkg/systemd/generate/pods.go
@@ -79,6 +79,8 @@ type podInfo struct {
// Location of the RunRoot for the pod. Required for ensuring the tmpfs
// or volume exists and is mounted when coming online at boot.
RunRoot string
+ // Add %i and %I to description and execute parts - this should not be used
+ IdentifySpecifier bool
}
const podTemplate = headerTemplate + `Requires={{{{- range $index, $value := .RequiredServices -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}.service{{{{end}}}}
@@ -108,6 +110,9 @@ WantedBy=multi-user.target default.target
// Based on the options, the return value might be the content of all units or
// the files they been written to.
func PodUnits(pod *libpod.Pod, options entities.GenerateSystemdOptions) (map[string]string, error) {
+ if options.TemplateUnitFile {
+ return nil, errors.New("--template is not supported for pods")
+ }
// Error out if the pod has no infra container, which we require to be the
// main service.
if !pod.HasInfraContainer() {
diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go
index be6b782b5..6b294802d 100644
--- a/test/e2e/checkpoint_test.go
+++ b/test/e2e/checkpoint_test.go
@@ -1,6 +1,7 @@
package integration
import (
+ "encoding/json"
"fmt"
"net"
"os"
@@ -12,6 +13,7 @@ import (
"github.com/checkpoint-restore/go-criu/v5/stats"
"github.com/containers/podman/v3/pkg/checkpoint/crutils"
"github.com/containers/podman/v3/pkg/criu"
+ "github.com/containers/podman/v3/pkg/domain/entities"
. "github.com/containers/podman/v3/test/utils"
"github.com/containers/podman/v3/utils"
. "github.com/onsi/ginkgo"
@@ -1244,4 +1246,97 @@ var _ = Describe("Podman checkpoint", func() {
// Remove exported checkpoint
os.Remove(fileName)
})
+
+ It("podman checkpoint and restore containers with --print-stats", func() {
+ session1 := podmanTest.Podman(getRunString([]string{redis}))
+ session1.WaitWithDefaultTimeout()
+ Expect(session1).Should(Exit(0))
+
+ session2 := podmanTest.Podman(getRunString([]string{redis, "top"}))
+ session2.WaitWithDefaultTimeout()
+ Expect(session2).Should(Exit(0))
+
+ result := podmanTest.Podman([]string{
+ "container",
+ "checkpoint",
+ "-a",
+ "--print-stats",
+ })
+ result.WaitWithDefaultTimeout()
+
+ Expect(result).Should(Exit(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
+
+ type checkpointStatistics struct {
+ PodmanDuration int64 `json:"podman_checkpoint_duration"`
+ ContainerStatistics []*entities.CheckpointReport `json:"container_statistics"`
+ }
+
+ cS := new(checkpointStatistics)
+ err := json.Unmarshal([]byte(result.OutputToString()), cS)
+ Expect(err).ShouldNot(HaveOccurred())
+
+ Expect(len(cS.ContainerStatistics)).To(Equal(2))
+ Expect(cS.PodmanDuration).To(BeNumerically(">", cS.ContainerStatistics[0].RuntimeDuration))
+ Expect(cS.PodmanDuration).To(BeNumerically(">", cS.ContainerStatistics[1].RuntimeDuration))
+ Expect(cS.ContainerStatistics[0].RuntimeDuration).To(
+ BeNumerically(">", cS.ContainerStatistics[0].CRIUStatistics.FrozenTime),
+ )
+ Expect(cS.ContainerStatistics[1].RuntimeDuration).To(
+ BeNumerically(">", cS.ContainerStatistics[1].CRIUStatistics.FrozenTime),
+ )
+
+ ps := podmanTest.Podman([]string{
+ "ps",
+ "-q",
+ "--no-trunc",
+ })
+ ps.WaitWithDefaultTimeout()
+ Expect(ps).Should(Exit(0))
+ Expect(ps.LineInOutputContains(session1.OutputToString())).To(BeFalse())
+ Expect(ps.LineInOutputContains(session2.OutputToString())).To(BeFalse())
+
+ result = podmanTest.Podman([]string{
+ "container",
+ "restore",
+ "-a",
+ "--print-stats",
+ })
+ result.WaitWithDefaultTimeout()
+
+ Expect(result).Should(Exit(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(2))
+ Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up"))
+ Expect(podmanTest.GetContainerStatus()).To(Not(ContainSubstring("Exited")))
+
+ type restoreStatistics struct {
+ PodmanDuration int64 `json:"podman_restore_duration"`
+ ContainerStatistics []*entities.RestoreReport `json:"container_statistics"`
+ }
+
+ rS := new(restoreStatistics)
+ err = json.Unmarshal([]byte(result.OutputToString()), rS)
+ Expect(err).ShouldNot(HaveOccurred())
+
+ Expect(len(cS.ContainerStatistics)).To(Equal(2))
+ Expect(cS.PodmanDuration).To(BeNumerically(">", cS.ContainerStatistics[0].RuntimeDuration))
+ Expect(cS.PodmanDuration).To(BeNumerically(">", cS.ContainerStatistics[1].RuntimeDuration))
+ Expect(cS.ContainerStatistics[0].RuntimeDuration).To(
+ BeNumerically(">", cS.ContainerStatistics[0].CRIUStatistics.RestoreTime),
+ )
+ Expect(cS.ContainerStatistics[1].RuntimeDuration).To(
+ BeNumerically(">", cS.ContainerStatistics[1].CRIUStatistics.RestoreTime),
+ )
+
+ result = podmanTest.Podman([]string{
+ "rm",
+ "-t",
+ "0",
+ "-fa",
+ })
+ result.WaitWithDefaultTimeout()
+ Expect(result).Should(Exit(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
+ })
+
})
diff --git a/test/system/035-logs.bats b/test/system/035-logs.bats
index 7fb3e62e4..44984eaad 100644
--- a/test/system/035-logs.bats
+++ b/test/system/035-logs.bats
@@ -89,6 +89,27 @@ ${cid[0]} d" "Sequential output from logs"
_log_test_multi journald
}
+function _log_test_restarted() {
+ run_podman run --log-driver=$1 --name logtest $IMAGE sh -c 'start=0; if test -s log; then start=`tail -n 1 log`; fi; seq `expr $start + 1` `expr $start + 10` | tee -a log'
+ run_podman start -a logtest
+ logfile=$(mktemp -p ${PODMAN_TMPDIR} logfileXXXXXXXX)
+ $PODMAN $_PODMAN_TEST_OPTS logs -f logtest > $logfile
+ expected=$(mktemp -p ${PODMAN_TMPDIR} expectedXXXXXXXX)
+ seq 1 20 > $expected
+ diff -u ${expected} ${logfile}
+}
+
+@test "podman logs restarted - k8s-file" {
+ _log_test_restarted k8s-file
+}
+
+@test "podman logs restarted journald" {
+ # We can't use journald on RHEL as rootless: rhbz#1895105
+ skip_if_journald_unavailable
+
+ _log_test_restarted journald
+}
+
@test "podman logs - journald log driver requires journald events backend" {
skip_if_remote "remote does not support --events-backend"
# We can't use journald on RHEL as rootless: rhbz#1895105
diff --git a/test/system/090-events.bats b/test/system/090-events.bats
index 1fb542ccd..5af6a3793 100644
--- a/test/system/090-events.bats
+++ b/test/system/090-events.bats
@@ -102,6 +102,17 @@ function _events_disjunctive_filters() {
_events_disjunctive_filters --events-backend=journald
}
+@test "events with file backend and journald logdriver with --follow failure" {
+ skip_if_remote "remote does not support --events-backend"
+ skip_if_journald_unavailable "system does not support journald events"
+ run_podman --events-backend=file run --log-driver=journald --name=test $IMAGE echo hi
+ is "$output" "hi" "Should support events-backend=file"
+
+ run_podman 125 --events-backend=file logs --follow test
+ is "$output" "Error: using --follow with the journald --log-driver but without the journald --events-backend (file) is not supported" "Should fail with reasonable error message when events-backend and events-logger do not match"
+
+}
+
@test "events with disjunctive filters - default" {
_events_disjunctive_filters ""
}
diff --git a/test/system/250-systemd.bats b/test/system/250-systemd.bats
index 98241c309..1c778a5e3 100644
--- a/test/system/250-systemd.bats
+++ b/test/system/250-systemd.bats
@@ -9,6 +9,7 @@ load helpers.systemd
SERVICE_NAME="podman_test_$(random_string)"
UNIT_FILE="$UNIT_DIR/$SERVICE_NAME.service"
+TEMPLATE_FILE_PREFIX="$UNIT_DIR/$SERVICE_NAME"
function setup() {
skip_if_remote "systemd tests are meaningless over remote"
@@ -201,4 +202,62 @@ LISTEN_FDNAMES=listen_fdnames" "LISTEN Environment passed: $context"
check_listen_env "$stdenv" "podman start"
}
+@test "podman generate - systemd template" {
+ cname=$(random_string)
+ run_podman create --name $cname $IMAGE top
+
+ run_podman generate systemd --template -n $cname
+ echo "$output" > "$TEMPLATE_FILE_PREFIX@.service"
+ run_podman rm -f $cname
+
+ systemctl daemon-reload
+
+ INSTANCE="$SERVICE_NAME@1.service"
+ run systemctl start "$INSTANCE"
+ if [ $status -ne 0 ]; then
+ die "Error starting systemd unit $INSTANCE, output: $output"
+ fi
+
+ run systemctl status "$INSTANCE"
+ if [ $status -ne 0 ]; then
+ die "Non-zero status of systemd unit $INSTANCE, output: $output"
+ fi
+
+ run systemctl stop "$INSTANCE"
+ if [ $status -ne 0 ]; then
+ die "Error stopping systemd unit $INSTANCE, output: $output"
+ fi
+
+ if [[ -z "$status" ]]; then
+ run systemctl is-active "$INSTANCE"
+ if [ $status -ne 0 ]; then
+ die "Error checking stauts of systemd unit $INSTANCE, output: $output"
+ fi
+ is "$output" "$status" "$INSTANCE not in expected state"
+ fi
+
+ rm -f "$TEMPLATE_FILE_PREFIX@.service"
+ systemctl daemon-reload
+}
+
+@test "podman generate - systemd template no support for pod" {
+ cname=$(random_string)
+ podname=$(random_string)
+ run_podman pod create --name $podname
+ run_podman run --pod $podname -dt --name $cname $IMAGE top
+
+ run_podman 125 generate systemd --new --template -n $podname
+ is "$output" ".*--template is not supported for pods.*" "Error message contains 'not supported'"
+
+ run_podman rm -f $cname
+ run_podman pod rm -f $podname
+}
+
+@test "podman generate - systemd template only used on --new" {
+ cname=$(random_string)
+ run_podman create --name $cname $IMAGE top
+ run_podman 125 generate systemd --new=false --template -n $cname
+ is "$output" ".*--template cannot be set" "Error message should be '--template requires --new'"
+}
+
# vim: filetype=sh
diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats
index 21350ed36..deadfa90a 100644
--- a/test/system/500-networking.bats
+++ b/test/system/500-networking.bats
@@ -172,7 +172,7 @@ load helpers
# FIXME: debugging for #11871
run_podman exec $cid cat /etc/resolv.conf
- if is_rootless; then
+ if is_rootless && ! is_remote; then
run_podman unshare --rootless-cni cat /etc/resolv.conf
fi
ps uxww