mirror of
https://github.com/containers/podman.git
synced 2025-06-29 15:08:09 +08:00
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:
@ -428,6 +428,7 @@ type RestoreValues struct {
|
|||||||
Latest bool
|
Latest bool
|
||||||
TcpEstablished bool
|
TcpEstablished bool
|
||||||
Import string
|
Import string
|
||||||
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
type RmValues struct {
|
type RmValues struct {
|
||||||
|
@ -45,6 +45,7 @@ func init() {
|
|||||||
flags.BoolVarP(&restoreCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
|
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.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.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)
|
markFlagHiddenForRemoteClient("latest", flags)
|
||||||
}
|
}
|
||||||
@ -64,6 +65,15 @@ func restoreCmd(c *cliconfig.RestoreValues, cmd *cobra.Command) error {
|
|||||||
Keep: c.Keep,
|
Keep: c.Keep,
|
||||||
TCPEstablished: c.TcpEstablished,
|
TCPEstablished: c.TcpEstablished,
|
||||||
TargetFile: c.Import,
|
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) {
|
if (c.Import != "") && (c.All || c.Latest) {
|
||||||
|
@ -857,6 +857,8 @@ _podman_container_restore() {
|
|||||||
local options_with_args="
|
local options_with_args="
|
||||||
-i
|
-i
|
||||||
--import
|
--import
|
||||||
|
-n
|
||||||
|
--name
|
||||||
"
|
"
|
||||||
local boolean_options="
|
local boolean_options="
|
||||||
-a
|
-a
|
||||||
|
@ -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
|
to import a checkpointed container from another host. It is not necessary to specify
|
||||||
a container when restoring from an exported checkpoint.
|
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
|
## EXAMPLE
|
||||||
|
|
||||||
podman container restore mywebserver
|
podman container restore mywebserver
|
||||||
|
@ -820,6 +820,10 @@ type ContainerCheckpointOptions struct {
|
|||||||
// Import tells the API to read the checkpoint image from
|
// Import tells the API to read the checkpoint image from
|
||||||
// the filename set in TargetFile
|
// the filename set in TargetFile
|
||||||
TargetFile string
|
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
|
// Checkpoint checkpoints a container
|
||||||
|
@ -681,7 +681,13 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
|
|||||||
// Read network configuration from checkpoint
|
// Read network configuration from checkpoint
|
||||||
// Currently only one interface with one IP is supported.
|
// Currently only one interface with one IP is supported.
|
||||||
networkStatusFile, err := os.Open(filepath.Join(c.bundlePath(), "network.status"))
|
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
|
// The file with the network.status does exist. Let's restore the
|
||||||
// container with the same IP address as during checkpointing.
|
// container with the same IP address as during checkpointing.
|
||||||
defer networkStatusFile.Close()
|
defer networkStatusFile.Close()
|
||||||
|
@ -76,6 +76,14 @@ func (r *Runtime) initContainerVariables(rSpec *spec.Spec, config *ContainerConf
|
|||||||
if err := JSONDeepCopy(config, ctr.config); err != nil {
|
if err := JSONDeepCopy(config, ctr.config); err != nil {
|
||||||
return nil, errors.Wrapf(err, "error copying container config for restore")
|
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)
|
ctr.config.Spec = new(spec.Spec)
|
||||||
@ -189,11 +197,16 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container, restore bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if restore {
|
if restore {
|
||||||
// Remove information about /dev/shm mount
|
// Remove information about bind mount
|
||||||
// for new container from imported checkpoint
|
// for new container from imported checkpoint
|
||||||
g := generate.Generator{Config: ctr.config.Spec}
|
g := generate.Generator{Config: ctr.config.Spec}
|
||||||
g.RemoveMount("/dev/shm")
|
g.RemoveMount("/dev/shm")
|
||||||
ctr.config.ShmDir = ""
|
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
|
// Set up storage for the container
|
||||||
|
@ -41,7 +41,7 @@ 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) ([]*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
|
// 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(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")
|
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
|
// The code to load the images is copied from create.go
|
||||||
var writer io.Writer
|
var writer io.Writer
|
||||||
// In create.go this only set if '--quiet' does not exist.
|
// 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
|
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)
|
containers = append(containers, container)
|
||||||
return containers, nil
|
return containers, nil
|
||||||
}
|
}
|
||||||
|
@ -539,7 +539,7 @@ func (r *LocalRuntime) Restore(ctx context.Context, c *cliconfig.RestoreValues,
|
|||||||
})
|
})
|
||||||
|
|
||||||
if c.Import != "" {
|
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 {
|
} else if c.All {
|
||||||
containers, err = r.GetContainers(filterFuncs...)
|
containers, err = r.GetContainers(filterFuncs...)
|
||||||
} else {
|
} else {
|
||||||
|
@ -376,9 +376,20 @@ var _ = Describe("Podman checkpoint", func() {
|
|||||||
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
|
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
|
||||||
Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up"))
|
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 = podmanTest.Podman([]string{"rm", "-fa"})
|
||||||
result.WaitWithDefaultTimeout()
|
result.WaitWithDefaultTimeout()
|
||||||
Expect(result.ExitCode()).To(Equal(0))
|
Expect(result.ExitCode()).To(Equal(0))
|
||||||
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
|
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
|
||||||
|
|
||||||
|
// Remove exported checkpoint
|
||||||
|
os.Remove("/tmp/checkpoint.tar.gz")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user