diff --git a/cmd/podman/containers/restore.go b/cmd/podman/containers/restore.go index ee71abb41f..3938c3813d 100644 --- a/cmd/podman/containers/restore.go +++ b/cmd/podman/containers/restore.go @@ -52,6 +52,7 @@ func init() { flags.BoolVarP(&restoreOptions.All, "all", "a", false, "Restore all checkpointed containers") flags.BoolVarP(&restoreOptions.Keep, "keep", "k", false, "Keep all temporary checkpoint files") flags.BoolVar(&restoreOptions.TCPEstablished, "tcp-established", false, "Restore a container with established TCP connections") + flags.BoolVar(&restoreOptions.TCPClose, "tcp-close", false, "Restore a container and close all TCP connections") flags.BoolVar(&restoreOptions.FileLocks, "file-locks", false, "Restore a container with file locks") importFlagName := "import" diff --git a/docs/source/markdown/podman-container-restore.1.md b/docs/source/markdown/podman-container-restore.1.md index cc50e2be69..96ff14aa43 100644 --- a/docs/source/markdown/podman-container-restore.1.md +++ b/docs/source/markdown/podman-container-restore.1.md @@ -150,6 +150,14 @@ initial *container* start, with a new set of port forwarding rules. For more details, see **[podman run --publish](podman-run.1.md#--publish)**. +#### **--tcp-close** + +Restore a *container* and close all TCP connections. This option is useful +when TCP connections are not needed after restore or when connections +will be reestablished by the application. If the checkpoint image was created with +**--tcp-close**, this option should be used during restore.\ +The default is **false**. + #### **--tcp-established** Restore a *container* with established TCP connections. If the checkpoint image diff --git a/libpod/container_api.go b/libpod/container_api.go index 47e1797a5f..5d61d6ca71 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -965,6 +965,8 @@ type ContainerCheckpointOptions struct { // TCPEstablished tells the API to checkpoint a container // even if it contains established TCP connections TCPEstablished bool + // TCPClose tells the API to close all TCP connections during restore + TCPClose bool // TargetFile tells the API to read (or write) the checkpoint image // from (or to) the filename set in TargetFile TargetFile string diff --git a/libpod/oci_conmon_common.go b/libpod/oci_conmon_common.go index 02fecd21f1..7269709412 100644 --- a/libpod/oci_conmon_common.go +++ b/libpod/oci_conmon_common.go @@ -1082,6 +1082,9 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co if restoreOptions.TCPEstablished { args = append(args, "--runtime-opt", "--tcp-established") } + if restoreOptions.TCPClose { + args = append(args, "--runtime-opt", "--tcp-close") + } if restoreOptions.FileLocks { args = append(args, "--runtime-opt", "--file-locks") } diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index 03cecbb8e7..23e79620d5 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -307,6 +307,7 @@ func Restore(w http.ResponseWriter, r *http.Request) { query := struct { Keep bool `schema:"keep"` TCPEstablished bool `schema:"tcpEstablished"` + TCPClose bool `schema:"tcpClose"` Import bool `schema:"import"` Name string `schema:"name"` IgnoreRootFS bool `schema:"ignoreRootFS"` @@ -329,6 +330,7 @@ func Restore(w http.ResponseWriter, r *http.Request) { Name: query.Name, Keep: query.Keep, TCPEstablished: query.TCPEstablished, + TCPClose: query.TCPClose, IgnoreRootFS: query.IgnoreRootFS, IgnoreVolumes: query.IgnoreVolumes, IgnoreStaticIP: query.IgnoreStaticIP, diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index 0ca1c3bec7..03012cc2b6 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -1636,7 +1636,11 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // - in: query // name: tcpEstablished // type: boolean - // description: checkpoint a container with established TCP connections + // description: restore a container with established TCP connections + // - in: query + // name: tcpClose + // type: boolean + // description: restore a container but close the TCP connections // - in: query // name: import // type: boolean diff --git a/pkg/bindings/containers/types.go b/pkg/bindings/containers/types.go index ba3703ceeb..85a8248475 100644 --- a/pkg/bindings/containers/types.go +++ b/pkg/bindings/containers/types.go @@ -83,6 +83,7 @@ type RestoreOptions struct { Keep *bool Name *string TCPEstablished *bool + TCPClose *bool Pod *string PrintStats *bool PublishPorts []string diff --git a/pkg/bindings/containers/types_restore_options.go b/pkg/bindings/containers/types_restore_options.go index cb4e307601..74348e0545 100644 --- a/pkg/bindings/containers/types_restore_options.go +++ b/pkg/bindings/containers/types_restore_options.go @@ -152,6 +152,21 @@ func (o *RestoreOptions) GetTCPEstablished() bool { return *o.TCPEstablished } +// WithTCPClose set field TCPClose to given value +func (o *RestoreOptions) WithTCPClose(value bool) *RestoreOptions { + o.TCPClose = &value + return o +} + +// GetTCPClose returns value of field TCPClose +func (o *RestoreOptions) GetTCPClose() bool { + if o.TCPClose == nil { + var z bool + return z + } + return *o.TCPClose +} + // WithPod set field Pod to given value func (o *RestoreOptions) WithPod(value string) *RestoreOptions { o.Pod = &value diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index cb5a662388..3b166caed2 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -227,6 +227,7 @@ type RestoreOptions struct { Latest bool Name string TCPEstablished bool + TCPClose bool ImportPrevious string PublishPorts []string Pod string diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 6b831d2b7b..cd5620fd2a 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -738,6 +738,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st restoreOptions := libpod.ContainerCheckpointOptions{ Keep: options.Keep, TCPEstablished: options.TCPEstablished, + TCPClose: options.TCPClose, TargetFile: options.Import, Name: options.Name, IgnoreRootfs: options.IgnoreRootFS, diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 4d8c2e6d9a..13c0f9ad46 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -472,6 +472,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st options.WithKeep(opts.Keep) options.WithName(opts.Name) options.WithTCPEstablished(opts.TCPEstablished) + options.WithTCPClose(opts.TCPClose) options.WithPod(opts.Pod) options.WithPrintStats(opts.PrintStats) options.WithPublishPorts(opts.PublishPorts) diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go index 57613d01ec..67a186652e 100644 --- a/test/e2e/checkpoint_test.go +++ b/test/e2e/checkpoint_test.go @@ -402,6 +402,55 @@ var _ = Describe("Podman checkpoint", func() { conn.Close() }) + It("podman restore container with tcp-close", func() { + Skip("FIXME: #26289 - Rawhide only issue, skip for now") + + // Start a container with redis (which listens on tcp port) + localRunString := getRunString([]string{REDIS_IMAGE}) + session := podmanTest.Podman(localRunString) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + cid := session.OutputToString() + if !WaitContainerReady(podmanTest, cid, "Ready to accept connections", 20, 1) { + Fail("Container failed to get ready") + } + + // Get container IP + IP := podmanTest.PodmanExitCleanly("inspect", cid, fmt.Sprintf("--format={{(index .NetworkSettings.Networks \"%s\").IPAddress}}", netname)) + + // Open a network connection to the redis server + conn, err := net.DialTimeout("tcp4", IP.OutputToString()+":6379", time.Duration(3)*time.Second) + Expect(err).ToNot(HaveOccurred()) + defer conn.Close() + + // Checkpoint with --tcp-established since we have an open connection + podmanTest.PodmanExitCleanly("container", "checkpoint", cid, "--tcp-established") + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited")) + + // Restore should fail as the checkpoint image contains established TCP connections + result := podmanTest.Podman([]string{"container", "restore", cid}) + result.WaitWithDefaultTimeout() + + // default message when using crun + expectStderr := "crun: CRIU restoring failed -52. Please check CRIU logfile" + if podmanTest.OCIRuntime == "runc" { + expectStderr = "runc: criu failed: type NOTIFY errno 0" + } + if !IsRemote() { + // This part is only seen with podman local, never remote + expectStderr = "OCI runtime error: " + expectStderr + } + Expect(result).Should(ExitWithError(125, expectStderr)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited")) + + // Now it should work thanks to "--tcp-close" + podmanTest.PodmanExitCleanly("container", "restore", cid, "--tcp-close") + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) + }) + It("podman checkpoint with --leave-running", func() { localRunString := getRunString([]string{ALPINE, "top"}) session := podmanTest.Podman(localRunString)