Skip to content

Commit

Permalink
migration: add possibility to restore a container with a new name
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
adrianreber committed Jun 4, 2019
1 parent 0e072f9 commit 49b624d
Show file tree
Hide file tree
Showing 10 changed files with 93 additions and 4 deletions.
1 change: 1 addition & 0 deletions cmd/podman/cliconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ type RestoreValues struct {
Latest bool
TcpEstablished bool
Import string
Name string
}

type RmValues struct {
Expand Down
10 changes: 10 additions & 0 deletions cmd/podman/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions completions/bash/podman
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,8 @@ _podman_container_restore() {
local options_with_args="
-i
--import
-n
--name
"
local boolean_options="
-a
Expand Down
12 changes: 12 additions & 0 deletions docs/podman-container-restore.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions libpod/container_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion libpod/container_internal_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
15 changes: 14 additions & 1 deletion libpod/runtime_ctr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
32 changes: 31 additions & 1 deletion pkg/adapter/checkpoint_restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand All @@ -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
}
2 changes: 1 addition & 1 deletion pkg/adapter/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
11 changes: 11 additions & 0 deletions test/e2e/checkpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
})
})

0 comments on commit 49b624d

Please sign in to comment.