mirror of
https://github.com/containers/podman.git
synced 2025-08-06 19:44:14 +08:00
Merge pull request #8781 from rst0git/cr-volumes
Add support for checkpoint/restore of containers with volumes
This commit is contained in:
@ -57,6 +57,7 @@ func init() {
|
|||||||
_ = checkpointCommand.RegisterFlagCompletionFunc(exportFlagName, completion.AutocompleteDefault)
|
_ = checkpointCommand.RegisterFlagCompletionFunc(exportFlagName, completion.AutocompleteDefault)
|
||||||
|
|
||||||
flags.BoolVar(&checkpointOptions.IgnoreRootFS, "ignore-rootfs", false, "Do not include root file-system changes when exporting")
|
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)
|
validate.AddLatestFlag(checkpointCommand, &checkpointOptions.Latest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +69,9 @@ func checkpoint(cmd *cobra.Command, args []string) error {
|
|||||||
if checkpointOptions.Export == "" && checkpointOptions.IgnoreRootFS {
|
if checkpointOptions.Export == "" && checkpointOptions.IgnoreRootFS {
|
||||||
return errors.Errorf("--ignore-rootfs can only be used with --export")
|
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)
|
responses, err := registry.ContainerEngine().ContainerCheckpoint(context.Background(), args, checkpointOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -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.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.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.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)
|
validate.AddLatestFlag(restoreCommand, &restoreOptions.Latest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,6 +74,9 @@ func restore(_ *cobra.Command, args []string) error {
|
|||||||
if restoreOptions.Import == "" && restoreOptions.IgnoreRootFS {
|
if restoreOptions.Import == "" && restoreOptions.IgnoreRootFS {
|
||||||
return errors.Errorf("--ignore-rootfs can only be used with --import")
|
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 != "" {
|
if restoreOptions.Import == "" && restoreOptions.Name != "" {
|
||||||
return errors.Errorf("--name can only be used with --import")
|
return errors.Errorf("--name can only be used with --import")
|
||||||
}
|
}
|
||||||
|
@ -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
|
to explicitly disable including changes to the root file-system into
|
||||||
the checkpoint archive file.
|
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
|
## EXAMPLE
|
||||||
|
|
||||||
podman container checkpoint mywebserver
|
podman container checkpoint mywebserver
|
||||||
|
@ -85,6 +85,13 @@ exported checkpoint with **--name, -n**.
|
|||||||
|
|
||||||
Using **--ignore-static-mac** tells Podman to ignore the MAC address if it was
|
Using **--ignore-static-mac** tells Podman to ignore the MAC address if it was
|
||||||
configured with **--mac-address** during container creation.
|
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
|
## EXAMPLE
|
||||||
|
|
||||||
podman container restore mywebserver
|
podman container restore mywebserver
|
||||||
|
@ -703,6 +703,9 @@ type ContainerCheckpointOptions struct {
|
|||||||
// important to be able to restore a container multiple
|
// important to be able to restore a container multiple
|
||||||
// times with '--import --name'.
|
// times with '--import --name'.
|
||||||
IgnoreStaticMAC bool
|
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
|
// Checkpoint checkpoints a container
|
||||||
|
@ -798,11 +798,11 @@ func (c *Container) addNamespaceContainer(g *generate.Generator, ns LinuxNS, ctr
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) error {
|
func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {
|
||||||
if (len(c.config.NamedVolumes) > 0) || (len(c.Dependencies()) > 0) {
|
if len(c.Dependencies()) > 0 {
|
||||||
return errors.Errorf("Cannot export checkpoints of containers with named volumes or dependencies")
|
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{
|
includeFiles := []string{
|
||||||
"checkpoint",
|
"checkpoint",
|
||||||
@ -815,7 +815,7 @@ func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) error {
|
|||||||
// Get root file-system changes included in the checkpoint archive
|
// Get root file-system changes included in the checkpoint archive
|
||||||
rootfsDiffPath := filepath.Join(c.bundlePath(), "rootfs-diff.tar")
|
rootfsDiffPath := filepath.Join(c.bundlePath(), "rootfs-diff.tar")
|
||||||
deleteFilesList := filepath.Join(c.bundlePath(), "deleted.files")
|
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'
|
// To correctly track deleted files, let's go through the output of 'podman diff'
|
||||||
tarFiles, err := c.runtime.GetDiff("", c.ID())
|
tarFiles, err := c.runtime.GetDiff("", c.ID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -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{
|
input, err := archive.TarWithOptions(c.bundlePath(), &archive.TarOptions{
|
||||||
Compression: archive.Gzip,
|
Compression: archive.Gzip,
|
||||||
IncludeSourceDir: true,
|
IncludeSourceDir: true,
|
||||||
@ -888,13 +929,13 @@ func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) error {
|
|||||||
return errors.Wrapf(err, "error reading checkpoint directory %q", c.ID())
|
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 {
|
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()
|
defer outFile.Close()
|
||||||
|
|
||||||
if err := os.Chmod(dest, 0600); err != nil {
|
if err := os.Chmod(options.TargetFile, 0600); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -906,6 +947,10 @@ func (c *Container) exportCheckpoint(dest string, ignoreRootfs bool) error {
|
|||||||
os.Remove(rootfsDiffPath)
|
os.Remove(rootfsDiffPath)
|
||||||
os.Remove(deleteFilesList)
|
os.Remove(deleteFilesList)
|
||||||
|
|
||||||
|
if !options.IgnoreVolumes {
|
||||||
|
os.RemoveAll(expVolDir)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -971,7 +1016,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO
|
|||||||
defer c.newContainerEvent(events.Checkpoint)
|
defer c.newContainerEvent(events.Checkpoint)
|
||||||
|
|
||||||
if options.TargetFile != "" {
|
if options.TargetFile != "" {
|
||||||
if err = c.exportCheckpoint(options.TargetFile, options.IgnoreRootfs); err != nil {
|
if err = c.exportCheckpoint(options); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1201,6 +1246,30 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
|
|||||||
return err
|
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
|
// Before actually restarting the container, apply the root file-system changes
|
||||||
if !options.IgnoreRootfs {
|
if !options.IgnoreRootfs {
|
||||||
rootfsDiffPath := filepath.Join(c.bundlePath(), "rootfs-diff.tar")
|
rootfsDiffPath := filepath.Join(c.bundlePath(), "rootfs-diff.tar")
|
||||||
|
@ -275,6 +275,7 @@ func Restore(w http.ResponseWriter, r *http.Request) {
|
|||||||
Import bool `schema:"import"`
|
Import bool `schema:"import"`
|
||||||
Name string `schema:"name"`
|
Name string `schema:"name"`
|
||||||
IgnoreRootFS bool `schema:"ignoreRootFS"`
|
IgnoreRootFS bool `schema:"ignoreRootFS"`
|
||||||
|
IgnoreVolumes bool `schema:"ignoreVolumes"`
|
||||||
IgnoreStaticIP bool `schema:"ignoreStaticIP"`
|
IgnoreStaticIP bool `schema:"ignoreStaticIP"`
|
||||||
IgnoreStaticMAC bool `schema:"ignoreStaticMAC"`
|
IgnoreStaticMAC bool `schema:"ignoreStaticMAC"`
|
||||||
}{
|
}{
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/containers/podman/v2/libpod"
|
"github.com/containers/podman/v2/libpod"
|
||||||
"github.com/containers/podman/v2/libpod/image"
|
"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/errorhandling"
|
||||||
"github.com/containers/podman/v2/pkg/util"
|
"github.com/containers/podman/v2/pkg/util"
|
||||||
"github.com/containers/storage/pkg/archive"
|
"github.com/containers/storage/pkg/archive"
|
||||||
@ -36,10 +37,10 @@ func crImportFromJSON(filePath string, v interface{}) error {
|
|||||||
|
|
||||||
// CRImportCheckpoint it the function which imports the information
|
// CRImportCheckpoint it the function which imports the information
|
||||||
// from checkpoint tarball and re-creates the container from that 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
|
// First get the container definition from the
|
||||||
// tarball to a temporary directory
|
// tarball to a temporary directory
|
||||||
archiveFile, err := os.Open(input)
|
archiveFile, err := os.Open(restoreOptions.Import)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to open checkpoint archive for import")
|
return nil, errors.Wrap(err, "failed to open checkpoint archive for import")
|
||||||
}
|
}
|
||||||
@ -53,6 +54,7 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input stri
|
|||||||
"rootfs-diff.tar",
|
"rootfs-diff.tar",
|
||||||
"network.status",
|
"network.status",
|
||||||
"deleted.files",
|
"deleted.files",
|
||||||
|
"volumes",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
dir, err := ioutil.TempDir("", "checkpoint")
|
dir, err := ioutil.TempDir("", "checkpoint")
|
||||||
@ -66,7 +68,7 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input stri
|
|||||||
}()
|
}()
|
||||||
err = archive.Untar(archiveFile, dir, options)
|
err = archive.Untar(archiveFile, dir, options)
|
||||||
if err != nil {
|
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
|
// Load spec.dump from temporary directory
|
||||||
@ -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.
|
// This should not happen as checkpoints with these options are not exported.
|
||||||
if (len(config.Dependencies) > 0) || (len(config.NamedVolumes) > 0) {
|
if len(config.Dependencies) > 0 {
|
||||||
return nil, errors.Errorf("Cannot import checkpoints of containers with named volumes or dependencies")
|
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
|
ctrID := config.ID
|
||||||
newName := false
|
newName := false
|
||||||
|
|
||||||
// Check if the restored container gets a new name
|
// Check if the restored container gets a new name
|
||||||
if name != "" {
|
if restoreOptions.Name != "" {
|
||||||
config.ID = ""
|
config.ID = ""
|
||||||
config.Name = name
|
config.Name = restoreOptions.Name
|
||||||
newName = true
|
newName = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,6 +173,7 @@ type CheckpointOptions struct {
|
|||||||
All bool
|
All bool
|
||||||
Export string
|
Export string
|
||||||
IgnoreRootFS bool
|
IgnoreRootFS bool
|
||||||
|
IgnoreVolumes bool
|
||||||
Keep bool
|
Keep bool
|
||||||
Latest bool
|
Latest bool
|
||||||
LeaveRunning bool
|
LeaveRunning bool
|
||||||
@ -187,6 +188,7 @@ type CheckpointReport struct {
|
|||||||
type RestoreOptions struct {
|
type RestoreOptions struct {
|
||||||
All bool
|
All bool
|
||||||
IgnoreRootFS bool
|
IgnoreRootFS bool
|
||||||
|
IgnoreVolumes bool
|
||||||
IgnoreStaticIP bool
|
IgnoreStaticIP bool
|
||||||
IgnoreStaticMAC bool
|
IgnoreStaticMAC bool
|
||||||
Import string
|
Import string
|
||||||
|
@ -487,6 +487,7 @@ func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds [
|
|||||||
TCPEstablished: options.TCPEstablished,
|
TCPEstablished: options.TCPEstablished,
|
||||||
TargetFile: options.Export,
|
TargetFile: options.Export,
|
||||||
IgnoreRootfs: options.IgnoreRootFS,
|
IgnoreRootfs: options.IgnoreRootFS,
|
||||||
|
IgnoreVolumes: options.IgnoreVolumes,
|
||||||
KeepRunning: options.LeaveRunning,
|
KeepRunning: options.LeaveRunning,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -525,6 +526,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
|
|||||||
TargetFile: options.Import,
|
TargetFile: options.Import,
|
||||||
Name: options.Name,
|
Name: options.Name,
|
||||||
IgnoreRootfs: options.IgnoreRootFS,
|
IgnoreRootfs: options.IgnoreRootFS,
|
||||||
|
IgnoreVolumes: options.IgnoreVolumes,
|
||||||
IgnoreStaticIP: options.IgnoreStaticIP,
|
IgnoreStaticIP: options.IgnoreStaticIP,
|
||||||
IgnoreStaticMAC: options.IgnoreStaticMAC,
|
IgnoreStaticMAC: options.IgnoreStaticMAC,
|
||||||
}
|
}
|
||||||
@ -538,7 +540,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st
|
|||||||
|
|
||||||
switch {
|
switch {
|
||||||
case options.Import != "":
|
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:
|
case options.All:
|
||||||
cons, err = ic.Libpod.GetContainers(filterFuncs...)
|
cons, err = ic.Libpod.GetContainers(filterFuncs...)
|
||||||
default:
|
default:
|
||||||
|
2
test/e2e/build/basicalpine/Containerfile.volume
Normal file
2
test/e2e/build/basicalpine/Containerfile.volume
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
FROM alpine
|
||||||
|
VOLUME "/volume0"
|
@ -652,4 +652,99 @@ var _ = Describe("Podman checkpoint", func() {
|
|||||||
// Remove exported checkpoint
|
// Remove exported checkpoint
|
||||||
os.Remove(fileName)
|
os.Remove(fileName)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("podman checkpoint a container with volumes", func() {
|
||||||
|
session := podmanTest.Podman([]string{
|
||||||
|
"build", "-f", "build/basicalpine/Containerfile.volume", "-t", "test-cr-volume",
|
||||||
|
})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
|
// Start the container
|
||||||
|
localRunString := getRunString([]string{
|
||||||
|
"--rm",
|
||||||
|
"-v", "/volume1",
|
||||||
|
"-v", "my-test-vol:/volume2",
|
||||||
|
"test-cr-volume",
|
||||||
|
"top",
|
||||||
|
})
|
||||||
|
session = podmanTest.Podman(localRunString)
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
|
||||||
|
|
||||||
|
cid := session.OutputToString()
|
||||||
|
|
||||||
|
// Add file in volume0
|
||||||
|
result := podmanTest.Podman([]string{
|
||||||
|
"exec", "-l", "/bin/sh", "-c", "echo " + cid + " > /volume0/test.output",
|
||||||
|
})
|
||||||
|
result.WaitWithDefaultTimeout()
|
||||||
|
Expect(result.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
|
// Add file in volume1
|
||||||
|
result = podmanTest.Podman([]string{
|
||||||
|
"exec", "-l", "/bin/sh", "-c", "echo " + cid + " > /volume1/test.output",
|
||||||
|
})
|
||||||
|
result.WaitWithDefaultTimeout()
|
||||||
|
Expect(result.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
|
// Add file in volume2
|
||||||
|
result = podmanTest.Podman([]string{
|
||||||
|
"exec", "-l", "/bin/sh", "-c", "echo " + cid + " > /volume2/test.output",
|
||||||
|
})
|
||||||
|
result.WaitWithDefaultTimeout()
|
||||||
|
Expect(result.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
|
checkpointFileName := "/tmp/checkpoint-" + cid + ".tar.gz"
|
||||||
|
|
||||||
|
// Checkpoint the container
|
||||||
|
result = podmanTest.Podman([]string{"container", "checkpoint", "-l", "-e", checkpointFileName})
|
||||||
|
result.WaitWithDefaultTimeout()
|
||||||
|
Expect(result.ExitCode()).To(Equal(0))
|
||||||
|
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
|
||||||
|
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
|
||||||
|
|
||||||
|
// Restore container should fail because named volume still exists
|
||||||
|
result = podmanTest.Podman([]string{"container", "restore", "-i", checkpointFileName})
|
||||||
|
result.WaitWithDefaultTimeout()
|
||||||
|
Expect(result).To(ExitWithError())
|
||||||
|
Expect(result.ErrorToString()).To(ContainSubstring(
|
||||||
|
"volume with name my-test-vol already exists. Use --ignore-volumes to not restore content of volumes",
|
||||||
|
))
|
||||||
|
|
||||||
|
// Remove named volume
|
||||||
|
session = podmanTest.Podman([]string{"volume", "rm", "my-test-vol"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
|
// Restoring container
|
||||||
|
result = podmanTest.Podman([]string{"container", "restore", "-i", checkpointFileName})
|
||||||
|
result.WaitWithDefaultTimeout()
|
||||||
|
Expect(result.ExitCode()).To(Equal(0))
|
||||||
|
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
|
||||||
|
Expect(podmanTest.NumberOfContainers()).To(Equal(1))
|
||||||
|
Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up"))
|
||||||
|
|
||||||
|
// Validate volume0 content
|
||||||
|
result = podmanTest.Podman([]string{"exec", "-l", "cat", "/volume0/test.output"})
|
||||||
|
result.WaitWithDefaultTimeout()
|
||||||
|
Expect(result.ExitCode()).To(Equal(0))
|
||||||
|
Expect(result.OutputToString()).To(ContainSubstring(cid))
|
||||||
|
|
||||||
|
// Validate volume1 content
|
||||||
|
result = podmanTest.Podman([]string{"exec", "-l", "cat", "/volume1/test.output"})
|
||||||
|
result.WaitWithDefaultTimeout()
|
||||||
|
Expect(result.ExitCode()).To(Equal(0))
|
||||||
|
Expect(result.OutputToString()).To(ContainSubstring(cid))
|
||||||
|
|
||||||
|
// Validate volume2 content
|
||||||
|
result = podmanTest.Podman([]string{"exec", "-l", "cat", "/volume2/test.output"})
|
||||||
|
result.WaitWithDefaultTimeout()
|
||||||
|
Expect(result.ExitCode()).To(Equal(0))
|
||||||
|
Expect(result.OutputToString()).To(ContainSubstring(cid))
|
||||||
|
|
||||||
|
// Remove exported checkpoint
|
||||||
|
os.Remove(checkpointFileName)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user