migration: add possibility to restore a container with a new name

The option to restore a container from an external checkpoint archive
(podman container restore -i /tmp/checkpoint.tar.gz) restores a
container with the same name and same ID as id had before checkpointing.

This commit adds the option '--name,-n' to 'podman container restore'.
With this option the restored container gets the name specified after
'--name,-n' and a new ID. This way it is possible to restore one
container multiple times.

If a container is restored with a new name Podman will not try to
request the same IP address for the container as it had during
checkpointing. This implicitly assumes that if a container is restored
from a checkpoint archive with a different name, that it will be
restored multiple times and restoring a container multiple times with
the same IP address will fail as each IP address can only be used once.

Signed-off-by: Adrian Reber <areber@redhat.com>
This commit is contained in:
Adrian Reber
2019-03-14 07:57:16 +00:00
parent 0e072f9a97
commit bef83c42ea
10 changed files with 93 additions and 4 deletions

View File

@ -428,6 +428,7 @@ type RestoreValues struct {
Latest bool
TcpEstablished bool
Import string
Name string
}
type RmValues struct {

View File

@ -45,6 +45,7 @@ func init() {
flags.BoolVarP(&restoreCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
flags.BoolVar(&restoreCommand.TcpEstablished, "tcp-established", false, "Restore a container with established TCP connections")
flags.StringVarP(&restoreCommand.Import, "import", "i", "", "Restore from exported checkpoint archive (tar.gz)")
flags.StringVarP(&restoreCommand.Name, "name", "n", "", "Specify new name for container restored from exported checkpoint (only works with --import)")
markFlagHiddenForRemoteClient("latest", flags)
}
@ -64,6 +65,15 @@ func restoreCmd(c *cliconfig.RestoreValues, cmd *cobra.Command) error {
Keep: c.Keep,
TCPEstablished: c.TcpEstablished,
TargetFile: c.Import,
Name: c.Name,
}
if c.Import == "" && c.Name != "" {
return errors.Errorf("--name can only used with --import")
}
if c.Name != "" && c.TcpEstablished {
return errors.Errorf("--tcp-established cannot be used with --name")
}
if (c.Import != "") && (c.All || c.Latest) {

View File

@ -857,6 +857,8 @@ _podman_container_restore() {
local options_with_args="
-i
--import
-n
--name
"
local boolean_options="
-a

View File

@ -48,6 +48,18 @@ Import a checkpoint tar.gz file, which was exported by Podman. This can be used
to import a checkpointed container from another host. It is not necessary to specify
a container when restoring from an exported checkpoint.
**--name, -n**
This is only available in combination with **--import, -i**. If a container is restored
from a checkpoint tar.gz file it is possible to rename it with **--name, -n**. This
way it is possible to restore a container from a checkpoint multiple times with different
names.
If the **--name, -n** option is used, Podman will not attempt to assign the same IP
address to the container it was using before checkpointing as each IP address can only
be used once and the restored container will have another IP address. This also means
that **--name, -n** cannot be used in combination with **--tcp-established**.
## EXAMPLE
podman container restore mywebserver

View File

@ -820,6 +820,10 @@ type ContainerCheckpointOptions struct {
// Import tells the API to read the checkpoint image from
// the filename set in TargetFile
TargetFile string
// Name tells the API that during restore from an exported
// checkpoint archive a new name should be used for the
// restored container
Name string
}
// Checkpoint checkpoints a container

View File

@ -681,7 +681,13 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
// Read network configuration from checkpoint
// Currently only one interface with one IP is supported.
networkStatusFile, err := os.Open(filepath.Join(c.bundlePath(), "network.status"))
if err == nil {
// If the restored container should get a new name, the IP address of
// the container will not be restored. This assumes that if a new name is
// specified, the container is restored multiple times.
// TODO: This implicit restoring with or without IP depending on an
// unrelated restore parameter (--name) does not seem like the
// best solution.
if err == nil && options.Name == "" {
// The file with the network.status does exist. Let's restore the
// container with the same IP address as during checkpointing.
defer networkStatusFile.Close()

View File

@ -76,6 +76,14 @@ func (r *Runtime) initContainerVariables(rSpec *spec.Spec, config *ContainerConf
if err := JSONDeepCopy(config, ctr.config); err != nil {
return nil, errors.Wrapf(err, "error copying container config for restore")
}
// If the ID is empty a new name for the restored container was requested
if ctr.config.ID == "" {
ctr.config.ID = stringid.GenerateNonCryptoID()
// Fixup ExitCommand with new ID
ctr.config.ExitCommand[len(ctr.config.ExitCommand)-1] = ctr.config.ID
}
// Reset the log path to point to the default
ctr.config.LogPath = ""
}
ctr.config.Spec = new(spec.Spec)
@ -189,11 +197,16 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container, restore bo
}
if restore {
// Remove information about /dev/shm mount
// Remove information about bind mount
// for new container from imported checkpoint
g := generate.Generator{Config: ctr.config.Spec}
g.RemoveMount("/dev/shm")
ctr.config.ShmDir = ""
g.RemoveMount("/etc/resolv.conf")
g.RemoveMount("/etc/hostname")
g.RemoveMount("/etc/hosts")
g.RemoveMount("/run/.containerenv")
g.RemoveMount("/run/secrets")
}
// Set up storage for the container

View File

@ -41,7 +41,7 @@ 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) ([]*libpod.Container, error) {
func crImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input string, name string) ([]*libpod.Container, error) {
// First get the container definition from the
// tarball to a temporary directory
archiveFile, err := os.Open(input)
@ -85,6 +85,18 @@ func crImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input stri
return nil, errors.Errorf("Cannot import checkpoints of containers with named volumes or dependencies")
}
ctrID := config.ID
newName := false
// Check if the restored container gets a new name
if name != "" {
config.ID = ""
config.Name = name
newName = true
}
ctrName := config.Name
// The code to load the images is copied from create.go
var writer io.Writer
// In create.go this only set if '--quiet' does not exist.
@ -110,6 +122,24 @@ func crImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input stri
return nil, nil
}
containerConfig := container.Config()
if containerConfig.Name != ctrName {
return nil, errors.Errorf("Name of restored container (%s) does not match requested name (%s)", containerConfig.Name, ctrName)
}
if newName == false {
// Only check ID for a restore with the same name.
// Using -n to request a new name for the restored container, will also create a new ID
if containerConfig.ID != ctrID {
return nil, errors.Errorf("ID of restored container (%s) does not match requested ID (%s)", containerConfig.ID, ctrID)
}
}
// Check if the ExitCommand points to the correct container ID
if containerConfig.ExitCommand[len(containerConfig.ExitCommand)-1] != containerConfig.ID {
return nil, errors.Errorf("'ExitCommandID' uses ID %s instead of container ID %s", containerConfig.ExitCommand[len(containerConfig.ExitCommand)-1], containerConfig.ID)
}
containers = append(containers, container)
return containers, nil
}

View File

@ -539,7 +539,7 @@ func (r *LocalRuntime) Restore(ctx context.Context, c *cliconfig.RestoreValues,
})
if c.Import != "" {
containers, err = crImportCheckpoint(ctx, r.Runtime, c.Import)
containers, err = crImportCheckpoint(ctx, r.Runtime, c.Import, c.Name)
} else if c.All {
containers, err = r.GetContainers(filterFuncs...)
} else {

View File

@ -376,9 +376,20 @@ var _ = Describe("Podman checkpoint", func() {
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up"))
// Restore container a second time with different name
result = podmanTest.Podman([]string{"container", "restore", "-i", "/tmp/checkpoint.tar.gz", "-n", "restore_again"})
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(0))
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(2))
Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up"))
result = podmanTest.Podman([]string{"rm", "-fa"})
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(0))
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
// Remove exported checkpoint
os.Remove("/tmp/checkpoint.tar.gz")
})
})