Skip to content

Commit

Permalink
Merge pull request #8781 from rst0git/cr-volumes
Browse files Browse the repository at this point in the history
Add support for checkpoint/restore of containers with volumes
  • Loading branch information
openshift-merge-robot authored Jan 8, 2021
2 parents a0b432d + 1215bd9 commit 49db79e
Show file tree
Hide file tree
Showing 12 changed files with 227 additions and 17 deletions.
4 changes: 4 additions & 0 deletions cmd/podman/containers/checkpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func init() {
_ = checkpointCommand.RegisterFlagCompletionFunc(exportFlagName, completion.AutocompleteDefault)

flags.BoolVar(&checkpointOptions.IgnoreRootFS, "ignore-rootfs", false, "Do not include root file-system changes when exporting")
flags.BoolVar(&checkpointOptions.IgnoreVolumes, "ignore-volumes", false, "Do not export volumes associated with container")
validate.AddLatestFlag(checkpointCommand, &checkpointOptions.Latest)
}

Expand All @@ -68,6 +69,9 @@ func checkpoint(cmd *cobra.Command, args []string) error {
if checkpointOptions.Export == "" && checkpointOptions.IgnoreRootFS {
return errors.Errorf("--ignore-rootfs can only be used with --export")
}
if checkpointOptions.Export == "" && checkpointOptions.IgnoreVolumes {
return errors.Errorf("--ignore-volumes can only be used with --export")
}
responses, err := registry.ContainerEngine().ContainerCheckpoint(context.Background(), args, checkpointOptions)
if err != nil {
return err
Expand Down
4 changes: 4 additions & 0 deletions cmd/podman/containers/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func init() {
flags.BoolVar(&restoreOptions.IgnoreRootFS, "ignore-rootfs", false, "Do not apply root file-system changes when importing from exported checkpoint")
flags.BoolVar(&restoreOptions.IgnoreStaticIP, "ignore-static-ip", false, "Ignore IP address set via --static-ip")
flags.BoolVar(&restoreOptions.IgnoreStaticMAC, "ignore-static-mac", false, "Ignore MAC address set via --mac-address")
flags.BoolVar(&restoreOptions.IgnoreVolumes, "ignore-volumes", false, "Do not export volumes associated with container")
validate.AddLatestFlag(restoreCommand, &restoreOptions.Latest)
}

Expand All @@ -73,6 +74,9 @@ func restore(_ *cobra.Command, args []string) error {
if restoreOptions.Import == "" && restoreOptions.IgnoreRootFS {
return errors.Errorf("--ignore-rootfs can only be used with --import")
}
if restoreOptions.Import == "" && restoreOptions.IgnoreVolumes {
return errors.Errorf("--ignore-volumes can only be used with --import")
}
if restoreOptions.Import == "" && restoreOptions.Name != "" {
return errors.Errorf("--name can only be used with --import")
}
Expand Down
6 changes: 6 additions & 0 deletions docs/source/markdown/podman-container-checkpoint.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ exported to a tar.gz file it is possible with the help of **--ignore-rootfs**
to explicitly disable including changes to the root file-system into
the checkpoint archive file.

#### **--ignore-volumes**

This option must be used in combination with the **--export, -e** option.
When this option is specified, the content of volumes associated with
the container will not be included into the checkpoint tar.gz file.

## EXAMPLE

podman container checkpoint mywebserver
Expand Down
7 changes: 7 additions & 0 deletions docs/source/markdown/podman-container-restore.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ exported checkpoint with **--name, -n**.

Using **--ignore-static-mac** tells Podman to ignore the MAC address if it was
configured with **--mac-address** during container creation.

#### **--ignore-volumes**

This option must be used in combination with the **--import, -i** option.
When restoring containers from a checkpoint tar.gz file with this option,
the content of associated volumes will not be restored.

## EXAMPLE

podman container restore mywebserver
Expand Down
3 changes: 3 additions & 0 deletions libpod/container_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,9 @@ type ContainerCheckpointOptions struct {
// important to be able to restore a container multiple
// times with '--import --name'.
IgnoreStaticMAC bool
// IgnoreVolumes tells the API to not export or not to import
// the content of volumes associated with the container
IgnoreVolumes bool
}

// Checkpoint checkpoints a container
Expand Down
87 changes: 78 additions & 9 deletions libpod/container_internal_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -798,11 +798,11 @@ func (c *Container) addNamespaceContainer(g *generate.Generator, ns LinuxNS, ctr
return nil
}

func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) error {
if (len(c.config.NamedVolumes) > 0) || (len(c.Dependencies()) > 0) {
return errors.Errorf("Cannot export checkpoints of containers with named volumes or dependencies")
func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {
if len(c.Dependencies()) > 0 {
return errors.Errorf("Cannot export checkpoints of containers with dependencies")
}
logrus.Debugf("Exporting checkpoint image of container %q to %q", c.ID(), dest)
logrus.Debugf("Exporting checkpoint image of container %q to %q", c.ID(), options.TargetFile)

includeFiles := []string{
"checkpoint",
Expand All @@ -815,7 +815,7 @@ func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) error {
// Get root file-system changes included in the checkpoint archive
rootfsDiffPath := filepath.Join(c.bundlePath(), "rootfs-diff.tar")
deleteFilesList := filepath.Join(c.bundlePath(), "deleted.files")
if !ignoreRootfs {
if !options.IgnoreRootfs {
// To correctly track deleted files, let's go through the output of 'podman diff'
tarFiles, err := c.runtime.GetDiff("", c.ID())
if err != nil {
Expand Down Expand Up @@ -878,6 +878,47 @@ func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) error {
}
}

// Folder containing archived volumes that will be included in the export
expVolDir := filepath.Join(c.bundlePath(), "volumes")

// Create an archive for each volume associated with the container
if !options.IgnoreVolumes {
if err := os.MkdirAll(expVolDir, 0700); err != nil {
return errors.Wrapf(err, "error creating volumes export directory %q", expVolDir)
}

for _, v := range c.config.NamedVolumes {
volumeTarFilePath := filepath.Join("volumes", v.Name+".tar")
volumeTarFileFullPath := filepath.Join(c.bundlePath(), volumeTarFilePath)

volumeTarFile, err := os.Create(volumeTarFileFullPath)
if err != nil {
return errors.Wrapf(err, "error creating %q", volumeTarFileFullPath)
}

volume, err := c.runtime.GetVolume(v.Name)
if err != nil {
return err
}

input, err := archive.TarWithOptions(volume.MountPoint(), &archive.TarOptions{
Compression: archive.Uncompressed,
IncludeSourceDir: true,
})
if err != nil {
return errors.Wrapf(err, "error reading volume directory %q", v.Dest)
}

_, err = io.Copy(volumeTarFile, input)
if err != nil {
return err
}
volumeTarFile.Close()

includeFiles = append(includeFiles, volumeTarFilePath)
}
}

input, err := archive.TarWithOptions(c.bundlePath(), &archive.TarOptions{
Compression: archive.Gzip,
IncludeSourceDir: true,
Expand All @@ -888,13 +929,13 @@ func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) error {
return errors.Wrapf(err, "error reading checkpoint directory %q", c.ID())
}

outFile, err := os.Create(dest)
outFile, err := os.Create(options.TargetFile)
if err != nil {
return errors.Wrapf(err, "error creating checkpoint export file %q", dest)
return errors.Wrapf(err, "error creating checkpoint export file %q", options.TargetFile)
}
defer outFile.Close()

if err := os.Chmod(dest, 0600); err != nil {
if err := os.Chmod(options.TargetFile, 0600); err != nil {
return err
}

Expand All @@ -906,6 +947,10 @@ func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) error {
os.Remove(rootfsDiffPath)
os.Remove(deleteFilesList)

if !options.IgnoreVolumes {
os.RemoveAll(expVolDir)
}

return nil
}

Expand Down Expand Up @@ -971,7 +1016,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO
defer c.newContainerEvent(events.Checkpoint)

if options.TargetFile != "" {
if err = c.exportCheckpoint(options.TargetFile, options.IgnoreRootfs); err != nil {
if err = c.exportCheckpoint(options); err != nil {
return err
}
}
Expand Down Expand Up @@ -1201,6 +1246,30 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
return err
}

// When restoring from an imported archive, allow restoring the content of volumes.
// Volumes are created in setupContainer()
if options.TargetFile != "" && !options.IgnoreVolumes {
for _, v := range c.config.NamedVolumes {
volumeFilePath := filepath.Join(c.bundlePath(), "volumes", v.Name+".tar")

volumeFile, err := os.Open(volumeFilePath)
if err != nil {
return 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)
}

mountPoint := volume.MountPoint()
if err := archive.UntarUncompressed(volumeFile, mountPoint, nil); err != nil {
return errors.Wrapf(err, "Failed to extract volume %s to %s", volumeFilePath, mountPoint)
}
}
}

// Before actually restarting the container, apply the root file-system changes
if !options.IgnoreRootfs {
rootfsDiffPath := filepath.Join(c.bundlePath(), "rootfs-diff.tar")
Expand Down
1 change: 1 addition & 0 deletions pkg/api/handlers/libpod/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ func Restore(w http.ResponseWriter, r *http.Request) {
Import bool `schema:"import"`
Name string `schema:"name"`
IgnoreRootFS bool `schema:"ignoreRootFS"`
IgnoreVolumes bool `schema:"ignoreVolumes"`
IgnoreStaticIP bool `schema:"ignoreStaticIP"`
IgnoreStaticMAC bool `schema:"ignoreStaticMAC"`
}{
Expand Down
29 changes: 22 additions & 7 deletions pkg/checkpoint/checkpoint_restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/libpod/image"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/errorhandling"
"github.com/containers/podman/v2/pkg/util"
"github.com/containers/storage/pkg/archive"
Expand Down Expand Up @@ -36,10 +37,10 @@ func crImportFromJSON(filePath string, v interface{}) error {

// CRImportCheckpoint it the function which imports the information
// from checkpoint tarball and re-creates the container from that information
func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input string, name string) ([]*libpod.Container, error) {
func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOptions entities.RestoreOptions) ([]*libpod.Container, error) {
// First get the container definition from the
// tarball to a temporary directory
archiveFile, err := os.Open(input)
archiveFile, err := os.Open(restoreOptions.Import)
if err != nil {
return nil, errors.Wrap(err, "failed to open checkpoint archive for import")
}
Expand All @@ -53,6 +54,7 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input stri
"rootfs-diff.tar",
"network.status",
"deleted.files",
"volumes",
},
}
dir, err := ioutil.TempDir("", "checkpoint")
Expand All @@ -66,7 +68,7 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input stri
}()
err = archive.Untar(archiveFile, dir, options)
if err != nil {
return nil, errors.Wrapf(err, "Unpacking of checkpoint archive %s failed", input)
return nil, errors.Wrapf(err, "Unpacking of checkpoint archive %s failed", restoreOptions.Import)
}

// Load spec.dump from temporary directory
Expand All @@ -82,17 +84,30 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input stri
}

// This should not happen as checkpoints with these options are not exported.
if (len(config.Dependencies) > 0) || (len(config.NamedVolumes) > 0) {
return nil, errors.Errorf("Cannot import checkpoints of containers with named volumes or dependencies")
if len(config.Dependencies) > 0 {
return nil, errors.Errorf("Cannot import checkpoints of containers with dependencies")
}

// Volumes included in the checkpoint should not exist
if !restoreOptions.IgnoreVolumes {
for _, vol := range config.NamedVolumes {
exists, err := runtime.HasVolume(vol.Name)
if err != nil {
return nil, err
}
if exists {
return nil, errors.Errorf("volume with name %s already exists. Use --ignore-volumes to not restore content of volumes", vol.Name)
}
}
}

ctrID := config.ID
newName := false

// Check if the restored container gets a new name
if name != "" {
if restoreOptions.Name != "" {
config.ID = ""
config.Name = name
config.Name = restoreOptions.Name
newName = true
}

Expand Down
2 changes: 2 additions & 0 deletions pkg/domain/entities/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ type CheckpointOptions struct {
All bool
Export string
IgnoreRootFS bool
IgnoreVolumes bool
Keep bool
Latest bool
LeaveRunning bool
Expand All @@ -187,6 +188,7 @@ type CheckpointReport struct {
type RestoreOptions struct {
All bool
IgnoreRootFS bool
IgnoreVolumes bool
IgnoreStaticIP bool
IgnoreStaticMAC bool
Import string
Expand Down
4 changes: 3 additions & 1 deletion pkg/domain/infra/abi/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds [
TCPEstablished: options.TCPEstablished,
TargetFile: options.Export,
IgnoreRootfs: options.IgnoreRootFS,
IgnoreVolumes: options.IgnoreVolumes,
KeepRunning: options.LeaveRunning,
}

Expand Down Expand Up @@ -525,6 +526,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
TargetFile: options.Import,
Name: options.Name,
IgnoreRootfs: options.IgnoreRootFS,
IgnoreVolumes: options.IgnoreVolumes,
IgnoreStaticIP: options.IgnoreStaticIP,
IgnoreStaticMAC: options.IgnoreStaticMAC,
}
Expand All @@ -538,7 +540,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st

switch {
case options.Import != "":
cons, err = checkpoint.CRImportCheckpoint(ctx, ic.Libpod, options.Import, options.Name)
cons, err = checkpoint.CRImportCheckpoint(ctx, ic.Libpod, options)
case options.All:
cons, err = ic.Libpod.GetContainers(filterFuncs...)
default:
Expand Down
2 changes: 2 additions & 0 deletions test/e2e/build/basicalpine/Containerfile.volume
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM alpine
VOLUME "/volume0"
Loading

0 comments on commit 49db79e

Please sign in to comment.