Merge pull request #10910 from adrianreber/2021-07-12-checkpoint-restore-into-pod

Add support for checkpoint/restore into and out of pods
This commit is contained in:
OpenShift Merge Robot
2021-07-28 14:48:28 +02:00
committed by GitHub
31 changed files with 3118 additions and 1354 deletions

View File

@ -71,6 +71,9 @@ func init() {
)
_ = restoreCommand.RegisterFlagCompletionFunc("publish", completion.AutocompleteNone)
flags.StringVar(&restoreOptions.Pod, "pod", "", "Restore container into existing Pod (only works with --import)")
_ = restoreCommand.RegisterFlagCompletionFunc("pod", common.AutocompletePodsRunning)
validate.AddLatestFlag(restoreCommand, &restoreOptions.Latest)
}
@ -91,6 +94,9 @@ func restore(cmd *cobra.Command, args []string) error {
if restoreOptions.Import == "" && restoreOptions.Name != "" {
return errors.Errorf("--name can only be used with --import")
}
if restoreOptions.Import == "" && restoreOptions.Pod != "" {
return errors.Errorf("--pod can only be used with --import")
}
if restoreOptions.Name != "" && restoreOptions.TCPEstablished {
return errors.Errorf("--tcp-established cannot be used with --name")
}

View File

@ -93,6 +93,15 @@ be used once and the restored *container* will have another IP address. This als
that **--name, -n** cannot be used in combination with **--tcp-established**.\
*IMPORTANT: This OPTION is only available in combination with **--import, -i**.*
#### **--pod**=*name*
Restore a container into the pod *name*. The destination pod for this restore
has to have the same namespaces shared as the pod this container was checkpointed
from (see **[podman pod create --share](podman-pod-create.1.md#--share)**).
*IMPORTANT: This OPTION is only available in combination with **--import, -i**.*
This option requires at least CRIU 3.16.
#### **--publish**, **-p**=*port*
Replaces the ports that the *container* publishes, as configured during the
@ -128,7 +137,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(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)**
## HISTORY
September 2018, Originally compiled by Adrian Reber <areber@redhat.com>

2
go.mod
View File

@ -7,7 +7,7 @@ require (
github.com/blang/semver v3.5.1+incompatible
github.com/buger/goterm v0.0.0-20181115115552-c206103e1f37
github.com/checkpoint-restore/checkpointctl v0.0.0-20210301084134-a2024f5584e7
github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b
github.com/checkpoint-restore/go-criu/v5 v5.1.0
github.com/container-orchestrated-devices/container-device-interface v0.0.0-20210325223243-f99e8b6c10b9
github.com/containernetworking/cni v0.8.1
github.com/containernetworking/plugins v0.9.1

4
go.sum
View File

@ -128,11 +128,11 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/checkpoint-restore/checkpointctl v0.0.0-20210301084134-a2024f5584e7 h1:ZmSAEFFtv3mepC4/Ze6E/hi6vGZlhRvywqp1l+w+qqw=
github.com/checkpoint-restore/checkpointctl v0.0.0-20210301084134-a2024f5584e7/go.mod h1:Kp3ezoDVdhfYxZUtgs4OL8sVvgOLz3txk0sbQD0opvw=
github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b h1:T4nWG1TXIxeor8mAu5bFguPJgSIGhZqv/f0z55KCrJM=
github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b/go.mod h1:TrMrLQfeENAPYPRsJuq3jsqdlRh3lvi6trTZJG8+tho=
github.com/checkpoint-restore/go-criu/v4 v4.0.2/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
github.com/checkpoint-restore/go-criu/v5 v5.1.0 h1:BkVH17kcwgmKMnFArsvLrxuBbMwfvPNYRB7mfJ0lzyI=
github.com/checkpoint-restore/go-criu/v5 v5.1.0/go.mod h1:iaS8bb7p6zKJanp1Qe8mpl7+bnkYBR500psJR6mwma0=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=

View File

@ -780,6 +780,16 @@ type ContainerCheckpointOptions struct {
// Compression tells the API which compression to use for
// the exported checkpoint archive.
Compression archive.Compression
// If Pod is set the container should be restored into the
// given Pod. If Pod is empty it is a restore without a Pod.
// Restoring a non Pod container into a Pod or a Pod container
// without a Pod is theoretically possible, but will
// probably not work if a PID namespace is shared.
// A shared PID namespace means that a Pod container has PID 1
// in the infrastructure container, but without the infrastructure
// container no PID 1 will be in the namespace and that is not
// possible.
Pod string
}
// Checkpoint checkpoints a container
@ -811,7 +821,11 @@ func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointO
// Restore restores a container
func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOptions) error {
logrus.Debugf("Trying to restore container %s", c.ID())
if options.Pod == "" {
logrus.Debugf("Trying to restore container %s", c.ID())
} else {
logrus.Debugf("Trying to restore container %s into pod %s", c.ID(), options.Pod)
}
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()

View File

@ -901,8 +901,27 @@ func (c *Container) addNamespaceContainer(g *generate.Generator, ns LinuxNS, ctr
}
func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {
if len(c.Dependencies()) > 0 {
return errors.Errorf("Cannot export checkpoints of containers with dependencies")
if len(c.Dependencies()) == 1 {
// Check if the dependency is an infra container. If it is we can checkpoint
// the container out of the Pod.
if c.config.Pod == "" {
return errors.Errorf("cannot export checkpoints of containers with dependencies")
}
pod, err := c.runtime.state.Pod(c.config.Pod)
if err != nil {
return errors.Wrapf(err, "container %s is in pod %s, but pod cannot be retrieved", c.ID(), c.config.Pod)
}
infraID, err := pod.InfraContainerID()
if err != nil {
return errors.Wrapf(err, "cannot retrieve infra container ID for pod %s", c.config.Pod)
}
if c.Dependencies()[0] != infraID {
return errors.Errorf("cannot export checkpoints of containers with dependencies")
}
}
if len(c.Dependencies()) > 1 {
return errors.Errorf("cannot export checkpoints of containers with dependencies")
}
logrus.Debugf("Exporting checkpoint image of container %q to %q", c.ID(), options.TargetFile)
@ -1021,9 +1040,9 @@ func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {
return nil
}
func (c *Container) checkpointRestoreSupported() error {
if !criu.CheckForCriu() {
return errors.Errorf("checkpoint/restore requires at least CRIU %d", criu.MinCriuVersion)
func (c *Container) checkpointRestoreSupported(version int) error {
if !criu.CheckForCriu(version) {
return errors.Errorf("checkpoint/restore requires at least CRIU %d", version)
}
if !c.ociRuntime.SupportsCheckpoint() {
return errors.Errorf("configured runtime does not support checkpoint/restore")
@ -1032,7 +1051,7 @@ func (c *Container) checkpointRestoreSupported() error {
}
func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointOptions) error {
if err := c.checkpointRestoreSupported(); err != nil {
if err := c.checkpointRestoreSupported(criu.MinCriuVersion); err != nil {
return err
}
@ -1136,10 +1155,20 @@ func (c *Container) importPreCheckpoint(input string) error {
}
func (c *Container) restore(ctx context.Context, options ContainerCheckpointOptions) (retErr error) {
if err := c.checkpointRestoreSupported(); err != nil {
minCriuVersion := func() int {
if options.Pod == "" {
return criu.MinCriuVersion
}
return criu.PodCriuVersion
}()
if err := c.checkpointRestoreSupported(minCriuVersion); err != nil {
return err
}
if options.Pod != "" && !crutils.CRRuntimeSupportsPodCheckpointRestore(c.ociRuntime.Path()) {
return 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())
}
@ -1247,6 +1276,83 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
}
}
if options.Pod != "" {
// Running in a Pod means that we have to change all namespace settings to
// 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)
}
infraContainer, err := pod.InfraContainer()
if err != nil {
return 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())
}
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())
}
}
infraContainer.lock.Unlock()
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)
}
if err := g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), nsPath); err != nil {
return 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)
}
if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), nsPath); err != nil {
return 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)
}
if err := g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), nsPath); err != nil {
return 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)
}
if err := g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), nsPath); err != nil {
return 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)
}
if err := g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), nsPath); err != nil {
return err
}
}
}
if err := c.makeBindMounts(); err != nil {
return err
}

View File

@ -1064,6 +1064,30 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
if restoreOptions.TCPEstablished {
args = append(args, "--runtime-opt", "--tcp-established")
}
if restoreOptions.Pod != "" {
mountLabel := ctr.config.MountLabel
processLabel := ctr.config.ProcessLabel
if mountLabel != "" {
args = append(
args,
"--runtime-opt",
fmt.Sprintf(
"--lsm-mount-context=%s",
mountLabel,
),
)
}
if processLabel != "" {
args = append(
args,
"--runtime-opt",
fmt.Sprintf(
"--lsm-profile=selinux:%s",
processLabel,
),
)
}
}
}
logrus.WithFields(logrus.Fields{

View File

@ -62,6 +62,7 @@ type RestoreOptions struct {
Keep *bool
Name *string
TCPEstablished *bool
Pod *string
}
//go:generate go run ../generator/generator.go CreateOptions

View File

@ -131,3 +131,19 @@ func (o *RestoreOptions) GetTCPEstablished() bool {
}
return *o.TCPEstablished
}
// WithPod
func (o *RestoreOptions) WithPod(value string) *RestoreOptions {
v := &value
o.Pod = v
return o
}
// GetPod
func (o *RestoreOptions) GetPod() string {
var pod string
if o.Pod == nil {
return pod
}
return *o.Pod
}

View File

@ -9,6 +9,9 @@ import (
"github.com/containers/common/libimage"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v3/libpod"
ann "github.com/containers/podman/v3/pkg/annotations"
"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/pkg/errorhandling"
"github.com/containers/podman/v3/pkg/specgen/generate"
@ -68,6 +71,14 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt
return nil, err
}
if ctrConfig.Pod != "" && restoreOptions.Pod == "" {
return nil, errors.New("cannot restore pod container without --pod")
}
if ctrConfig.Pod == "" && restoreOptions.Pod != "" {
return nil, errors.New("cannot restore non pod container into pod")
}
// This should not happen as checkpoints with these options are not exported.
if len(ctrConfig.Dependencies) > 0 {
return nil, errors.Errorf("Cannot import checkpoints of containers with dependencies")
@ -96,6 +107,91 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt
newName = true
}
if restoreOptions.Pod != "" {
// Restoring into a Pod requires much newer versions of CRIU
if !criu.CheckForCriu(criu.PodCriuVersion) {
return nil, errors.Errorf("restoring containers into pods requires at least CRIU %d", criu.PodCriuVersion)
}
// The runtime also has to support it
if !crutils.CRRuntimeSupportsPodCheckpointRestore(runtime.GetOCIRuntimePath()) {
return nil, errors.Errorf("runtime %s does not support pod restore", runtime.GetOCIRuntimePath())
}
// Restoring into an existing Pod
ctrConfig.Pod = restoreOptions.Pod
// According to podman pod create a pod can share the following namespaces:
// cgroup, ipc, net, pid, uts
// Let's make sure we a restoring into a pod with the same shared namespaces.
pod, err := runtime.LookupPod(ctrConfig.Pod)
if err != nil {
return nil, errors.Wrapf(err, "pod %q cannot be retrieved", ctrConfig.Pod)
}
infraContainer, err := pod.InfraContainer()
if err != nil {
return nil, errors.Wrapf(err, "cannot retrieve infra container from pod %q", ctrConfig.Pod)
}
// If a namespaces was shared (!= "") it needs to be set to the new infrastructure container
// If the infrastructure container does not share the same namespaces as the to be restored
// container we abort.
if ctrConfig.IPCNsCtr != "" {
if !pod.SharesIPC() {
return nil, errors.Errorf("pod %s does not share the IPC namespace", ctrConfig.Pod)
}
ctrConfig.IPCNsCtr = infraContainer.ID()
}
if ctrConfig.NetNsCtr != "" {
if !pod.SharesNet() {
return nil, errors.Errorf("pod %s does not share the network namespace", ctrConfig.Pod)
}
ctrConfig.NetNsCtr = infraContainer.ID()
}
if ctrConfig.PIDNsCtr != "" {
if !pod.SharesPID() {
return nil, errors.Errorf("pod %s does not share the PID namespace", ctrConfig.Pod)
}
ctrConfig.PIDNsCtr = infraContainer.ID()
}
if ctrConfig.UTSNsCtr != "" {
if !pod.SharesUTS() {
return nil, errors.Errorf("pod %s does not share the UTS namespace", ctrConfig.Pod)
}
ctrConfig.UTSNsCtr = infraContainer.ID()
}
if ctrConfig.CgroupNsCtr != "" {
if !pod.SharesCgroup() {
return nil, errors.Errorf("pod %s does not share the cgroup namespace", ctrConfig.Pod)
}
ctrConfig.CgroupNsCtr = infraContainer.ID()
}
// Change SELinux labels to infrastructure container labels
ctrConfig.MountLabel = infraContainer.MountLabel()
ctrConfig.ProcessLabel = infraContainer.ProcessLabel()
// Fix parent cgroup
cgroupPath, err := pod.CgroupPath()
if err != nil {
return nil, errors.Wrapf(err, "cannot retrieve cgroup path from pod %q", ctrConfig.Pod)
}
ctrConfig.CgroupParent = cgroupPath
oldPodID := dumpSpec.Annotations[ann.SandboxID]
// Fix up SandboxID in the annotations
dumpSpec.Annotations[ann.SandboxID] = ctrConfig.Pod
// Fix up CreateCommand
for i, c := range ctrConfig.CreateCommand {
if c == oldPodID {
ctrConfig.CreateCommand[i] = ctrConfig.Pod
}
}
}
if len(restoreOptions.PublishPorts) > 0 {
ports, _, _, err := generate.ParsePortMapping(restoreOptions.PublishPorts)
if err != nil {

View File

@ -1,6 +1,7 @@
package crutils
import (
"bytes"
"io"
"os"
"os/exec"
@ -189,3 +190,13 @@ func CRRuntimeSupportsCheckpointRestore(runtimePath string) bool {
}
return false
}
// CRRuntimeSupportsCheckpointRestore tests if the runtime at 'runtimePath'
// supports restoring into existing Pods. The runtime needs to support
// the CRIU option --lsm-mount-context and the existence of this is checked
// by this function. In addition it is necessary to at least have CRIU 3.16.
func CRRuntimeSupportsPodCheckpointRestore(runtimePath string) bool {
cmd := exec.Command(runtimePath, "restore", "--lsm-mount-context")
out, _ := cmd.CombinedOutput()
return bytes.Contains(out, []byte("flag needs an argument"))
}

View File

@ -1,17 +1,21 @@
package criu
import (
"github.com/checkpoint-restore/go-criu"
"github.com/checkpoint-restore/go-criu/v5"
)
// MinCriuVersion for Podman at least CRIU 3.11 is required
const MinCriuVersion = 31100
// PodCriuVersion is the version of CRIU needed for
// checkpointing and restoring containers out of and into Pods.
const PodCriuVersion = 31600
// CheckForCriu uses CRIU's go bindings to check if the CRIU
// binary exists and if it at least the version Podman needs.
func CheckForCriu() bool {
func CheckForCriu(version int) bool {
c := criu.MakeCriu()
result, err := c.IsCriuAtLeast(MinCriuVersion)
result, err := c.IsCriuAtLeast(version)
if err != nil {
return false
}

View File

@ -209,6 +209,7 @@ type RestoreOptions struct {
TCPEstablished bool
ImportPrevious string
PublishPorts []specgen.PortMapping
Pod string
}
type RestoreReport struct {

View File

@ -529,6 +529,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
IgnoreStaticIP: options.IgnoreStaticIP,
IgnoreStaticMAC: options.IgnoreStaticMAC,
ImportPrevious: options.ImportPrevious,
Pod: options.Pod,
}
filterFuncs := []libpod.ContainerFilter{

View File

@ -1,11 +1,13 @@
package integration
import (
"fmt"
"net"
"os"
"os/exec"
"strings"
"github.com/containers/podman/v3/pkg/checkpoint/crutils"
"github.com/containers/podman/v3/pkg/criu"
. "github.com/containers/podman/v3/test/utils"
. "github.com/onsi/ginkgo"
@ -46,7 +48,7 @@ var _ = Describe("Podman checkpoint", func() {
Skip("OCI runtime does not support checkpoint/restore")
}
if !criu.CheckForCriu() {
if !criu.CheckForCriu(criu.MinCriuVersion) {
Skip("CRIU is missing or too old.")
}
// Only Fedora 29 and newer has a new enough selinux-policy and
@ -977,4 +979,164 @@ var _ = Describe("Podman checkpoint", func() {
// Remove exported checkpoint
os.Remove(fileName)
})
namespaceCombination := []string{
"cgroup,ipc,net,uts,pid",
"cgroup,ipc,net,uts",
"cgroup,ipc,net",
"cgroup,ipc",
"ipc,net,uts,pid",
"ipc,net,uts",
"ipc,net",
"net,uts,pid",
"net,uts",
"uts,pid",
}
for _, share := range namespaceCombination {
testName := fmt.Sprintf(
"podman checkpoint and restore container out of and into pod (%s)",
share,
)
It(testName, func() {
if !criu.CheckForCriu(criu.PodCriuVersion) {
Skip("CRIU is missing or too old.")
}
if !crutils.CRRuntimeSupportsPodCheckpointRestore(podmanTest.OCIRuntime) {
Skip("runtime does not support pod restore")
}
// Create a pod
session := podmanTest.Podman([]string{
"pod",
"create",
"--share",
share,
})
session.WaitWithDefaultTimeout()
Expect(session).To(Exit(0))
podID := session.OutputToString()
session = podmanTest.Podman([]string{
"run",
"-d",
"--rm",
"--pod",
podID,
ALPINE,
"top",
})
session.WaitWithDefaultTimeout()
Expect(session).To(Exit(0))
cid := session.OutputToString()
fileName := "/tmp/checkpoint-" + cid + ".tar.gz"
// Checkpoint the container
result := podmanTest.Podman([]string{
"container",
"checkpoint",
"-e",
fileName,
cid,
})
result.WaitWithDefaultTimeout()
// As the container has been started with '--rm' it will be completely
// cleaned up after checkpointing.
Expect(result).To(Exit(0))
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
Expect(podmanTest.NumberOfContainers()).To(Equal(1))
// Remove the pod and create a new pod
result = podmanTest.Podman([]string{
"pod",
"rm",
podID,
})
result.WaitWithDefaultTimeout()
Expect(result).To(Exit(0))
// First create a pod with different shared namespaces.
// Restore should fail
wrongShare := share[:strings.LastIndex(share, ",")]
session = podmanTest.Podman([]string{
"pod",
"create",
"--share",
wrongShare,
})
session.WaitWithDefaultTimeout()
Expect(session).To(Exit(0))
podID = session.OutputToString()
// Restore container with different port mapping
result = podmanTest.Podman([]string{
"container",
"restore",
"--pod",
podID,
"-i",
fileName,
})
result.WaitWithDefaultTimeout()
Expect(result).To(Exit(125))
Expect(result.ErrorToString()).To(ContainSubstring("does not share the"))
// Remove the pod and create a new pod
result = podmanTest.Podman([]string{
"pod",
"rm",
podID,
})
result.WaitWithDefaultTimeout()
Expect(result).To(Exit(0))
session = podmanTest.Podman([]string{
"pod",
"create",
"--share",
share,
})
session.WaitWithDefaultTimeout()
Expect(session).To(Exit(0))
podID = session.OutputToString()
// Restore container with different port mapping
result = podmanTest.Podman([]string{
"container",
"restore",
"--pod",
podID,
"-i",
fileName,
})
result.WaitWithDefaultTimeout()
Expect(result).To(Exit(0))
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(2))
Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up"))
result = podmanTest.Podman([]string{
"rm",
"-f",
result.OutputToString(),
})
result.WaitWithDefaultTimeout()
Expect(result).To(Exit(0))
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
Expect(podmanTest.NumberOfContainers()).To(Equal(1))
result = podmanTest.Podman([]string{
"pod",
"rm",
"-fa",
})
result.WaitWithDefaultTimeout()
Expect(result).To(Exit(0))
// Remove exported checkpoint
os.Remove(fileName)
})
}
})

View File

@ -1,5 +0,0 @@
test/test
test/piggie
test/phaul
image
rpc/rpc.proto

View File

@ -1,25 +0,0 @@
language: go
sudo: required
os:
- linux
go:
- "1.8"
- "1.9"
- "1.10"
env:
# Run the tests with CRIU master and criu-dev
- CRIU_BRANCH="master"
- CRIU_BRANCH="criu-dev"
install:
- sudo apt-get update
- sudo apt-get install -y libprotobuf-dev libprotobuf-c0-dev protobuf-c-compiler protobuf-compiler python-protobuf libnl-3-dev libnet-dev libcap-dev
- go get github.com/checkpoint-restore/go-criu
- git clone --single-branch -b ${CRIU_BRANCH} https://github.com/checkpoint-restore/criu.git
- cd criu; make
- sudo install -D -m 755 criu/criu /usr/sbin/
- cd ..
script:
# This builds the code without running the tests.
- make build phaul test/test test/phaul test/piggie
# Run actual test as root as it uses CRIU.
- sudo make test phaul-test

View File

@ -1,60 +0,0 @@
GO ?= go
CC ?= gcc
ifeq ($(GOPATH),)
export GOPATH := $(shell $(GO) env GOPATH)
endif
FIRST_GOPATH := $(firstword $(subst :, ,$(GOPATH)))
GOBIN := $(shell $(GO) env GOBIN)
ifeq ($(GOBIN),)
GOBIN := $(FIRST_GOPATH)/bin
endif
all: build test phaul phaul-test
lint:
@golint . test phaul
build:
@$(GO) build -v
test/piggie: test/piggie.c
@$(CC) $^ -o $@
test/test: test/main.go
@$(GO) build -v -o test/test test/main.go
test: test/test test/piggie
mkdir -p image
test/piggie
test/test dump `pidof piggie` image
test/test restore image
pkill -9 piggie || :
phaul:
@cd phaul; go build -v
test/phaul: test/phaul-main.go
@$(GO) build -v -o test/phaul test/phaul-main.go
phaul-test: test/phaul test/piggie
rm -rf image
test/piggie
test/phaul `pidof piggie`
pkill -9 piggie || :
clean:
@rm -f test/test test/piggie test/phaul
@rm -rf image
@rm -f rpc/rpc.proto
install.tools:
if [ ! -x "$(GOBIN)/golint" ]; then \
$(GO) get -u golang.org/x/lint/golint; \
fi
rpc/rpc.proto:
curl -s https://raw.githubusercontent.com/checkpoint-restore/criu/master/images/rpc.proto -o $@
rpc/rpc.pb.go: rpc/rpc.proto
protoc --go_out=. $^
.PHONY: build test clean lint phaul

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
test/test
test/piggie/piggie
test/phaul
image
stats/stats.proto

View File

@ -0,0 +1,12 @@
run:
skip_dirs:
- rpc
- stats
linters:
disable-all: false
presets:
- bugs
- performance
- unused
- format

View File

@ -0,0 +1,62 @@
GO ?= go
CC ?= gcc
all: build test phaul-test
lint:
golangci-lint run ./...
build:
$(GO) build -v ./...
TEST_BINARIES := test/test test/piggie/piggie test/phaul/phaul
test-bin: $(TEST_BINARIES)
test/piggie/piggie: test/piggie/piggie.c
$(CC) $^ -o $@
test/test: test/*.go
$(GO) build -v -o $@ $^
test: $(TEST_BINARIES)
mkdir -p image
PID=$$(test/piggie/piggie) && { \
test/test dump $$PID image && \
test/test restore image; \
pkill -9 piggie; \
}
rm -rf image
test/phaul/phaul: test/phaul/*.go
$(GO) build -v -o $@ $^
phaul-test: $(TEST_BINARIES)
rm -rf image
PID=$$(test/piggie/piggie) && { \
test/phaul/phaul $$PID; \
pkill -9 piggie; \
}
clean:
@rm -f $(TEST_BINARIES)
@rm -rf image
@rm -f rpc/rpc.proto stats/stats.proto
rpc/rpc.proto:
curl -sSL https://raw.githubusercontent.com/checkpoint-restore/criu/master/images/rpc.proto -o $@
stats/stats.proto:
curl -sSL https://raw.githubusercontent.com/checkpoint-restore/criu/master/images/stats.proto -o $@
rpc/rpc.pb.go: rpc/rpc.proto
protoc --go_out=. --go_opt=Mrpc/rpc.proto=rpc/ $^
stats/stats.pb.go: stats/stats.proto
protoc --go_out=. $^
vendor:
GO111MODULE=on $(GO) mod tidy
GO111MODULE=on $(GO) mod vendor
GO111MODULE=on $(GO) mod verify
.PHONY: build test phaul-test test-bin clean lint vendor

View File

@ -1,8 +1,10 @@
[![master](https://travis-ci.org/checkpoint-restore/go-criu.svg?branch=master)](https://travis-ci.org/checkpoint-restore/go-criu)
[![test](https://github.com/checkpoint-restore/go-criu/workflows/ci/badge.svg?branch=master)](https://github.com/checkpoint-restore/go-criu/actions?query=workflow%3Aci)
[![verify](https://github.com/checkpoint-restore/go-criu/workflows/verify/badge.svg?branch=master)](https://github.com/checkpoint-restore/go-criu/actions?query=workflow%3Averify)
[![Go Reference](https://pkg.go.dev/badge/github.com/checkpoint-restore/go-criu.svg)](https://pkg.go.dev/github.com/checkpoint-restore/go-criu)
## go-criu -- Go bindings for [CRIU](https://criu.org/)
## go-criu -- Go bindings for CRIU
This repository provides Go bindings for CRIU. The code is based on the Go based PHaul
This repository provides Go bindings for [CRIU](https://criu.org/). The code is based on the Go-based PHaul
implementation from the CRIU repository. For easier inclusion into other Go projects the
CRIU Go bindings have been moved to this repository.
@ -10,24 +12,46 @@ The Go bindings provide an easy way to use the CRIU RPC calls from Go without th
to set up all the infrastructure to make the actual RPC connection to CRIU.
The following example would print the version of CRIU:
```
```go
import (
"log"
"github.com/checkpoint/restore/go-criu/v5"
)
func main() {
c := criu.MakeCriu()
version, err := c.GetCriuVersion()
fmt.Println(version)
if err != nil {
log.Fatalln(err)
}
log.Println(version)
}
```
or to just check if at least a certain CRIU version is installed:
```
```go
c := criu.MakeCriu()
result, err := c.IsCriuAtLeast(31100)
```
## Releases
go-criu will carry the same version number as CRIU. This implies that each
go-criu release will pull in the necessary changes from CRIU before making a
release.
The first go-criu release was 3.11 based on CRIU 3.11. The initial plan
was to follow CRIU so that go-criu would carry the same version number as
CRIU.
The first go-criu release was 3.11 based on CRIU 3.11.
As go-criu is imported in other projects and as Go modules are expected
to follow Semantic Versioning go-criu will also follow Semantic Versioning
starting with the 4.0.0 release.
The following table shows the relation between go-criu and criu versions:
| Major version | Latest release | CRIU version |
| -------------- | -------------- | ------------ |
| v5             | 5.0.0         | 3.15         |
| v4             | 4.1.0         | 3.14         |
## How to contribute
@ -61,6 +85,11 @@ by adding a "Signed-off-by" line containing the contributor's name and e-mail
to every commit message. Your signature certifies that you wrote the patch or
otherwise have the right to pass it on as an open-source patch.
### License
### License and copyright
The license of go-criu is the Apache 2.0 license.
Unless mentioned otherwise in a specific file's header, all code in
this project is released under the Apache 2.0 license.
The author of a change remains the copyright holder of their code
(no copyright assignment). The list of authors and contributors can be
retrieved from the git commit history and in some cases, the file headers.

View File

@ -0,0 +1,9 @@
module github.com/checkpoint-restore/go-criu/v5
go 1.13
require (
github.com/golang/protobuf v1.5.2
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
google.golang.org/protobuf v1.26.0
)

12
vendor/github.com/checkpoint-restore/go-criu/v5/go.sum generated vendored Normal file
View File

@ -0,0 +1,12 @@
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=

View File

@ -8,19 +8,28 @@ import (
"strconv"
"syscall"
"github.com/checkpoint-restore/go-criu/rpc"
"github.com/golang/protobuf/proto"
"github.com/checkpoint-restore/go-criu/v5/rpc"
"google.golang.org/protobuf/proto"
)
// Criu struct
type Criu struct {
swrkCmd *exec.Cmd
swrkSk *os.File
swrkCmd *exec.Cmd
swrkSk *os.File
swrkPath string
}
// MakeCriu returns the Criu object required for most operations
func MakeCriu() *Criu {
return &Criu{}
return &Criu{
swrkPath: "criu",
}
}
// SetCriuPath allows setting the path to the CRIU binary
// if it is in a non standard location
func (c *Criu) SetCriuPath(path string) {
c.swrkPath = path
}
// Prepare sets up everything for the RPC communication to CRIU
@ -36,7 +45,8 @@ func (c *Criu) Prepare() error {
defer srv.Close()
args := []string{"swrk", strconv.Itoa(fds[1])}
cmd := exec.Command("criu", args...)
// #nosec G204
cmd := exec.Command(c.swrkPath, args...)
err = cmd.Start()
if err != nil {
@ -55,7 +65,7 @@ func (c *Criu) Cleanup() {
if c.swrkCmd != nil {
c.swrkSk.Close()
c.swrkSk = nil
c.swrkCmd.Wait()
_ = c.swrkCmd.Wait()
c.swrkCmd = nil
}
}
@ -178,28 +188,28 @@ func (c *Criu) doSwrkWithResp(reqType rpc.CriuReqType, opts *rpc.CriuOpts, nfy N
}
// Dump dumps a process
func (c *Criu) Dump(opts rpc.CriuOpts, nfy Notify) error {
return c.doSwrk(rpc.CriuReqType_DUMP, &opts, nfy)
func (c *Criu) Dump(opts *rpc.CriuOpts, nfy Notify) error {
return c.doSwrk(rpc.CriuReqType_DUMP, opts, nfy)
}
// Restore restores a process
func (c *Criu) Restore(opts rpc.CriuOpts, nfy Notify) error {
return c.doSwrk(rpc.CriuReqType_RESTORE, &opts, nfy)
func (c *Criu) Restore(opts *rpc.CriuOpts, nfy Notify) error {
return c.doSwrk(rpc.CriuReqType_RESTORE, opts, nfy)
}
// PreDump does a pre-dump
func (c *Criu) PreDump(opts rpc.CriuOpts, nfy Notify) error {
return c.doSwrk(rpc.CriuReqType_PRE_DUMP, &opts, nfy)
func (c *Criu) PreDump(opts *rpc.CriuOpts, nfy Notify) error {
return c.doSwrk(rpc.CriuReqType_PRE_DUMP, opts, nfy)
}
// StartPageServer starts the page server
func (c *Criu) StartPageServer(opts rpc.CriuOpts) error {
return c.doSwrk(rpc.CriuReqType_PAGE_SERVER, &opts, nil)
func (c *Criu) StartPageServer(opts *rpc.CriuOpts) error {
return c.doSwrk(rpc.CriuReqType_PAGE_SERVER, opts, nil)
}
// StartPageServerChld starts the page server and returns PID and port
func (c *Criu) StartPageServerChld(opts rpc.CriuOpts) (int, int, error) {
resp, err := c.doSwrkWithResp(rpc.CriuReqType_PAGE_SERVER_CHLD, &opts, nil)
func (c *Criu) StartPageServerChld(opts *rpc.CriuOpts) (int, int, error) {
resp, err := c.doSwrkWithResp(rpc.CriuReqType_PAGE_SERVER_CHLD, opts, nil)
if err != nil {
return 0, 0, err
}
@ -219,8 +229,8 @@ func (c *Criu) GetCriuVersion() (int, error) {
return 0, fmt.Errorf("Unexpected CRIU RPC response")
}
version := int(*resp.GetVersion().Major) * 10000
version += int(*resp.GetVersion().Minor) * 100
version := int(*resp.GetVersion().MajorNumber) * 10000
version += int(*resp.GetVersion().MinorNumber) * 100
if resp.GetVersion().Sublevel != nil {
version += int(*resp.GetVersion().Sublevel)
}

View File

@ -1,6 +1,6 @@
package criu
//Notify interface
// Notify interface
type Notify interface {
PreDump() error
PostDump() error
@ -14,8 +14,7 @@ type Notify interface {
}
// NoNotify struct
type NoNotify struct {
}
type NoNotify struct{}
// PreDump NoNotify
func (c NoNotify) PreDump() error {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,239 @@
// SPDX-License-Identifier: MIT
syntax = "proto2";
message criu_page_server_info {
optional string address = 1;
optional int32 port = 2;
optional int32 pid = 3;
optional int32 fd = 4;
}
message criu_veth_pair {
required string if_in = 1;
required string if_out = 2;
};
message ext_mount_map {
required string key = 1;
required string val = 2;
};
message join_namespace {
required string ns = 1;
required string ns_file = 2;
optional string extra_opt = 3;
}
message inherit_fd {
required string key = 1;
required int32 fd = 2;
};
message cgroup_root {
optional string ctrl = 1;
required string path = 2;
};
message unix_sk {
required uint32 inode = 1;
};
enum criu_cg_mode {
IGNORE = 0;
CG_NONE = 1;
PROPS = 2;
SOFT = 3;
FULL = 4;
STRICT = 5;
DEFAULT = 6;
};
enum criu_pre_dump_mode {
SPLICE = 1;
VM_READ = 2;
};
message criu_opts {
required int32 images_dir_fd = 1;
optional int32 pid = 2; /* if not set on dump, will dump requesting process */
optional bool leave_running = 3;
optional bool ext_unix_sk = 4;
optional bool tcp_established = 5;
optional bool evasive_devices = 6;
optional bool shell_job = 7;
optional bool file_locks = 8;
optional int32 log_level = 9 [default = 2];
optional string log_file = 10; /* No subdirs are allowed. Consider using work-dir */
optional criu_page_server_info ps = 11;
optional bool notify_scripts = 12;
optional string root = 13;
optional string parent_img = 14;
optional bool track_mem = 15;
optional bool auto_dedup = 16;
optional int32 work_dir_fd = 17;
optional bool link_remap = 18;
repeated criu_veth_pair veths = 19; /* DEPRECATED, use external instead */
optional uint32 cpu_cap = 20 [default = 0xffffffff];
optional bool force_irmap = 21;
repeated string exec_cmd = 22;
repeated ext_mount_map ext_mnt = 23; /* DEPRECATED, use external instead */
optional bool manage_cgroups = 24; /* backward compatibility */
repeated cgroup_root cg_root = 25;
optional bool rst_sibling = 26; /* swrk only */
repeated inherit_fd inherit_fd = 27; /* swrk only */
optional bool auto_ext_mnt = 28;
optional bool ext_sharing = 29;
optional bool ext_masters = 30;
repeated string skip_mnt = 31;
repeated string enable_fs = 32;
repeated unix_sk unix_sk_ino = 33; /* DEPRECATED, use external instead */
optional criu_cg_mode manage_cgroups_mode = 34;
optional uint32 ghost_limit = 35 [default = 0x100000];
repeated string irmap_scan_paths = 36;
repeated string external = 37;
optional uint32 empty_ns = 38;
repeated join_namespace join_ns = 39;
optional string cgroup_props = 41;
optional string cgroup_props_file = 42;
repeated string cgroup_dump_controller = 43;
optional string freeze_cgroup = 44;
optional uint32 timeout = 45;
optional bool tcp_skip_in_flight = 46;
optional bool weak_sysctls = 47;
optional bool lazy_pages = 48;
optional int32 status_fd = 49;
optional bool orphan_pts_master = 50;
optional string config_file = 51;
optional bool tcp_close = 52;
optional string lsm_profile = 53;
optional string tls_cacert = 54;
optional string tls_cacrl = 55;
optional string tls_cert = 56;
optional string tls_key = 57;
optional bool tls = 58;
optional bool tls_no_cn_verify = 59;
optional string cgroup_yard = 60;
optional criu_pre_dump_mode pre_dump_mode = 61 [default = SPLICE];
optional int32 pidfd_store_sk = 62;
optional string lsm_mount_context = 63;
/* optional bool check_mounts = 128; */
}
message criu_dump_resp {
optional bool restored = 1;
}
message criu_restore_resp {
required int32 pid = 1;
}
message criu_notify {
optional string script = 1;
optional int32 pid = 2;
}
enum criu_req_type {
EMPTY = 0;
DUMP = 1;
RESTORE = 2;
CHECK = 3;
PRE_DUMP = 4;
PAGE_SERVER = 5;
NOTIFY = 6;
CPUINFO_DUMP = 7;
CPUINFO_CHECK = 8;
FEATURE_CHECK = 9;
VERSION = 10;
WAIT_PID = 11;
PAGE_SERVER_CHLD = 12;
}
/*
* List of features which can queried via
* CRIU_REQ_TYPE__FEATURE_CHECK
*/
message criu_features {
optional bool mem_track = 1;
optional bool lazy_pages = 2;
optional bool pidfd_store = 3;
}
/*
* Request -- each type corresponds to must-be-there
* request arguments of respective type
*/
message criu_req {
required criu_req_type type = 1;
optional criu_opts opts = 2;
optional bool notify_success = 3;
/*
* When set service won't close the connection but
* will wait for more req-s to appear. Works not
* for all request types.
*/
optional bool keep_open = 4;
/*
* 'features' can be used to query which features
* are supported by the installed criu/kernel
* via RPC.
*/
optional criu_features features = 5;
/* 'pid' is used for WAIT_PID */
optional uint32 pid = 6;
}
/*
* Response -- it states whether the request was served
* and additional request-specific information
*/
message criu_resp {
required criu_req_type type = 1;
required bool success = 2;
optional criu_dump_resp dump = 3;
optional criu_restore_resp restore = 4;
optional criu_notify notify = 5;
optional criu_page_server_info ps = 6;
optional int32 cr_errno = 7;
optional criu_features features = 8;
optional string cr_errmsg = 9;
optional criu_version version = 10;
optional int32 status = 11;
}
/* Answer for criu_req_type.VERSION requests */
message criu_version {
required int32 major_number = 1;
required int32 minor_number = 2;
optional string gitid = 3;
optional int32 sublevel = 4;
optional int32 extra = 5;
optional string name = 6;
}

6
vendor/modules.txt vendored
View File

@ -46,9 +46,9 @@ github.com/buger/goterm
github.com/cespare/xxhash/v2
# github.com/checkpoint-restore/checkpointctl v0.0.0-20210301084134-a2024f5584e7
github.com/checkpoint-restore/checkpointctl/lib
# github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b
github.com/checkpoint-restore/go-criu
github.com/checkpoint-restore/go-criu/rpc
# github.com/checkpoint-restore/go-criu/v5 v5.1.0
github.com/checkpoint-restore/go-criu/v5
github.com/checkpoint-restore/go-criu/v5/rpc
# github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/chzyer/readline
# github.com/container-orchestrated-devices/container-device-interface v0.0.0-20210325223243-f99e8b6c10b9