mirror of
https://github.com/containers/podman.git
synced 2025-05-21 00:56:36 +08:00
podmanv2 checkpoint and restore
add the ability to checkpoint and restore containers on v2podman Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
79
cmd/podmanV2/containers/checkpoint.go
Normal file
79
cmd/podmanV2/containers/checkpoint.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package containers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/containers/libpod/cmd/podmanV2/parse"
|
||||||
|
"github.com/containers/libpod/cmd/podmanV2/registry"
|
||||||
|
"github.com/containers/libpod/cmd/podmanV2/utils"
|
||||||
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
|
"github.com/containers/libpod/pkg/rootless"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
checkpointDescription = `
|
||||||
|
podman container checkpoint
|
||||||
|
|
||||||
|
Checkpoints one or more running containers. The container name or ID can be used.
|
||||||
|
`
|
||||||
|
checkpointCommand = &cobra.Command{
|
||||||
|
Use: "checkpoint [flags] CONTAINER [CONTAINER...]",
|
||||||
|
Short: "Checkpoints one or more containers",
|
||||||
|
Long: checkpointDescription,
|
||||||
|
RunE: checkpoint,
|
||||||
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return parse.CheckAllLatestAndCIDFile(cmd, args, false, false)
|
||||||
|
},
|
||||||
|
Example: `podman container checkpoint --keep ctrID
|
||||||
|
podman container checkpoint --all
|
||||||
|
podman container checkpoint --leave-running --latest`,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
checkpointOptions entities.CheckpointOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||||
|
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
|
||||||
|
Command: checkpointCommand,
|
||||||
|
Parent: containerCmd,
|
||||||
|
})
|
||||||
|
flags := checkpointCommand.Flags()
|
||||||
|
flags.BoolVarP(&checkpointOptions.Keep, "keep", "k", false, "Keep all temporary checkpoint files")
|
||||||
|
flags.BoolVarP(&checkpointOptions.LeaveRuninng, "leave-running", "R", false, "Leave the container running after writing checkpoint to disk")
|
||||||
|
flags.BoolVar(&checkpointOptions.TCPEstablished, "tcp-established", false, "Checkpoint a container with established TCP connections")
|
||||||
|
flags.BoolVarP(&checkpointOptions.All, "all", "a", false, "Checkpoint all running containers")
|
||||||
|
flags.BoolVarP(&checkpointOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
|
||||||
|
flags.StringVarP(&checkpointOptions.Export, "export", "e", "", "Export the checkpoint image to a tar.gz")
|
||||||
|
flags.BoolVar(&checkpointOptions.IgnoreRootFS, "ignore-rootfs", false, "Do not include root file-system changes when exporting")
|
||||||
|
if registry.IsRemote() {
|
||||||
|
_ = flags.MarkHidden("latest")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkpoint(cmd *cobra.Command, args []string) error {
|
||||||
|
var errs utils.OutputErrors
|
||||||
|
if rootless.IsRootless() {
|
||||||
|
return errors.New("checkpointing a container requires root")
|
||||||
|
}
|
||||||
|
if checkpointOptions.Export == "" && checkpointOptions.IgnoreRootFS {
|
||||||
|
return errors.Errorf("--ignore-rootfs can only be used with --export")
|
||||||
|
}
|
||||||
|
responses, err := registry.ContainerEngine().ContainerCheckpoint(context.Background(), args, checkpointOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, r := range responses {
|
||||||
|
if r.Err == nil {
|
||||||
|
fmt.Println(r.Id)
|
||||||
|
} else {
|
||||||
|
errs = append(errs, r.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errs.PrintErrors()
|
||||||
|
}
|
104
cmd/podmanV2/containers/restore.go
Normal file
104
cmd/podmanV2/containers/restore.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package containers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/containers/libpod/cmd/podmanV2/parse"
|
||||||
|
"github.com/containers/libpod/cmd/podmanV2/registry"
|
||||||
|
"github.com/containers/libpod/cmd/podmanV2/utils"
|
||||||
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
|
"github.com/containers/libpod/pkg/rootless"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
restoreDescription = `
|
||||||
|
podman container restore
|
||||||
|
|
||||||
|
Restores a container from a checkpoint. The container name or ID can be used.
|
||||||
|
`
|
||||||
|
restoreCommand = &cobra.Command{
|
||||||
|
Use: "restore [flags] CONTAINER [CONTAINER...]",
|
||||||
|
Short: "Restores one or more containers from a checkpoint",
|
||||||
|
Long: restoreDescription,
|
||||||
|
RunE: restore,
|
||||||
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return parse.CheckAllLatestAndCIDFile(cmd, args, true, false)
|
||||||
|
},
|
||||||
|
Example: `podman container restore ctrID
|
||||||
|
podman container restore --latest
|
||||||
|
podman container restore --all`,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
restoreOptions entities.RestoreOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||||
|
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
|
||||||
|
Command: restoreCommand,
|
||||||
|
Parent: containerCmd,
|
||||||
|
})
|
||||||
|
flags := restoreCommand.Flags()
|
||||||
|
flags.BoolVarP(&restoreOptions.All, "all", "a", false, "Restore all checkpointed containers")
|
||||||
|
flags.BoolVarP(&restoreOptions.Keep, "keep", "k", false, "Keep all temporary checkpoint files")
|
||||||
|
flags.BoolVarP(&restoreOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
|
||||||
|
flags.BoolVar(&restoreOptions.TCPEstablished, "tcp-established", false, "Restore a container with established TCP connections")
|
||||||
|
flags.StringVarP(&restoreOptions.Import, "import", "i", "", "Restore from exported checkpoint archive (tar.gz)")
|
||||||
|
flags.StringVarP(&restoreOptions.Name, "name", "n", "", "Specify new name for container restored from exported checkpoint (only works with --import)")
|
||||||
|
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")
|
||||||
|
if registry.IsRemote() {
|
||||||
|
_ = flags.MarkHidden("latest")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func restore(cmd *cobra.Command, args []string) error {
|
||||||
|
var errs utils.OutputErrors
|
||||||
|
if rootless.IsRootless() {
|
||||||
|
return errors.New("restoring a container requires root")
|
||||||
|
}
|
||||||
|
if restoreOptions.Import == "" && restoreOptions.IgnoreRootFS {
|
||||||
|
return errors.Errorf("--ignore-rootfs can only be used with --import")
|
||||||
|
}
|
||||||
|
if restoreOptions.Import == "" && restoreOptions.Name != "" {
|
||||||
|
return errors.Errorf("--name can only be used with --import")
|
||||||
|
}
|
||||||
|
if restoreOptions.Name != "" && restoreOptions.TCPEstablished {
|
||||||
|
return errors.Errorf("--tcp-established cannot be used with --name")
|
||||||
|
}
|
||||||
|
|
||||||
|
argLen := len(args)
|
||||||
|
if restoreOptions.Import != "" {
|
||||||
|
if restoreOptions.All || restoreOptions.Latest {
|
||||||
|
return errors.Errorf("Cannot use --import with --all or --latest")
|
||||||
|
}
|
||||||
|
if argLen > 0 {
|
||||||
|
return errors.Errorf("Cannot use --import with positional arguments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (restoreOptions.All || restoreOptions.Latest) && argLen > 0 {
|
||||||
|
return errors.Errorf("no arguments are needed with --all or --latest")
|
||||||
|
}
|
||||||
|
if argLen < 1 && !restoreOptions.All && !restoreOptions.Latest && restoreOptions.Import == "" {
|
||||||
|
return errors.Errorf("you must provide at least one name or id")
|
||||||
|
}
|
||||||
|
responses, err := registry.ContainerEngine().ContainerRestore(context.Background(), args, restoreOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, r := range responses {
|
||||||
|
if r.Err == nil {
|
||||||
|
fmt.Println(r.Id)
|
||||||
|
} else {
|
||||||
|
errs = append(errs, r.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errs.PrintErrors()
|
||||||
|
|
||||||
|
}
|
@ -26,6 +26,7 @@ import (
|
|||||||
"github.com/containers/libpod/libpod/image"
|
"github.com/containers/libpod/libpod/image"
|
||||||
"github.com/containers/libpod/libpod/logs"
|
"github.com/containers/libpod/libpod/logs"
|
||||||
"github.com/containers/libpod/pkg/adapter/shortcuts"
|
"github.com/containers/libpod/pkg/adapter/shortcuts"
|
||||||
|
"github.com/containers/libpod/pkg/checkpoint"
|
||||||
envLib "github.com/containers/libpod/pkg/env"
|
envLib "github.com/containers/libpod/pkg/env"
|
||||||
"github.com/containers/libpod/pkg/systemd/generate"
|
"github.com/containers/libpod/pkg/systemd/generate"
|
||||||
"github.com/containers/storage"
|
"github.com/containers/storage"
|
||||||
@ -625,7 +626,7 @@ func (r *LocalRuntime) Restore(ctx context.Context, c *cliconfig.RestoreValues)
|
|||||||
|
|
||||||
switch {
|
switch {
|
||||||
case c.Import != "":
|
case c.Import != "":
|
||||||
containers, err = crImportCheckpoint(ctx, r.Runtime, c.Import, c.Name)
|
containers, err = checkpoint.CRImportCheckpoint(ctx, r.Runtime, c.Import, c.Name)
|
||||||
case c.All:
|
case c.All:
|
||||||
containers, err = r.GetContainers(filterFuncs...)
|
containers, err = r.GetContainers(filterFuncs...)
|
||||||
default:
|
default:
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
package libpod
|
package libpod
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/containers/libpod/pkg/api/handlers/compat"
|
||||||
|
|
||||||
"github.com/containers/libpod/cmd/podman/shared"
|
"github.com/containers/libpod/cmd/podman/shared"
|
||||||
"github.com/containers/libpod/libpod"
|
"github.com/containers/libpod/libpod"
|
||||||
"github.com/containers/libpod/libpod/define"
|
"github.com/containers/libpod/libpod/define"
|
||||||
"github.com/containers/libpod/pkg/api/handlers/utils"
|
"github.com/containers/libpod/pkg/api/handlers/utils"
|
||||||
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
"github.com/gorilla/schema"
|
"github.com/gorilla/schema"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@ -325,3 +330,129 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts shared.P
|
|||||||
}
|
}
|
||||||
return ps, nil
|
return ps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Checkpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var targetFile string
|
||||||
|
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
||||||
|
query := struct {
|
||||||
|
Keep bool `schema:"keep"`
|
||||||
|
LeaveRunning bool `schema:"leaveRunning"`
|
||||||
|
TCPEstablished bool `schema:"tcpEstablished"`
|
||||||
|
Export bool `schema:"export"`
|
||||||
|
IgnoreRootFS bool `schema:"ignoreRootFS"`
|
||||||
|
}{
|
||||||
|
// override any golang type defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
||||||
|
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
|
||||||
|
errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name := utils.GetName(r)
|
||||||
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||||||
|
ctr, err := runtime.LookupContainer(name)
|
||||||
|
if err != nil {
|
||||||
|
utils.ContainerNotFound(w, name, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if query.Export {
|
||||||
|
tmpFile, err := ioutil.TempFile("", "checkpoint")
|
||||||
|
if err != nil {
|
||||||
|
utils.InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(tmpFile.Name())
|
||||||
|
if err := tmpFile.Close(); err != nil {
|
||||||
|
utils.InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
targetFile = tmpFile.Name()
|
||||||
|
}
|
||||||
|
options := libpod.ContainerCheckpointOptions{
|
||||||
|
Keep: query.Keep,
|
||||||
|
KeepRunning: query.LeaveRunning,
|
||||||
|
TCPEstablished: query.TCPEstablished,
|
||||||
|
IgnoreRootfs: query.IgnoreRootFS,
|
||||||
|
}
|
||||||
|
if query.Export {
|
||||||
|
options.TargetFile = targetFile
|
||||||
|
}
|
||||||
|
err = ctr.Checkpoint(r.Context(), options)
|
||||||
|
if err != nil {
|
||||||
|
utils.InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if query.Export {
|
||||||
|
f, err := os.Open(targetFile)
|
||||||
|
if err != nil {
|
||||||
|
utils.InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
utils.WriteResponse(w, http.StatusOK, f)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.WriteResponse(w, http.StatusOK, entities.CheckpointReport{Id: ctr.ID()})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Restore(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var (
|
||||||
|
targetFile string
|
||||||
|
)
|
||||||
|
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
||||||
|
query := struct {
|
||||||
|
Keep bool `schema:"keep"`
|
||||||
|
TCPEstablished bool `schema:"tcpEstablished"`
|
||||||
|
Import bool `schema:"import"`
|
||||||
|
Name string `schema:"name"`
|
||||||
|
IgnoreRootFS bool `schema:"ignoreRootFS"`
|
||||||
|
IgnoreStaticIP bool `schema:"ignoreStaticIP"`
|
||||||
|
IgnoreStaticMAC bool `schema:"ignoreStaticMAC"`
|
||||||
|
}{
|
||||||
|
// override any golang type defaults
|
||||||
|
}
|
||||||
|
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
||||||
|
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
|
||||||
|
errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name := utils.GetName(r)
|
||||||
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||||||
|
ctr, err := runtime.LookupContainer(name)
|
||||||
|
if err != nil {
|
||||||
|
utils.ContainerNotFound(w, name, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if query.Import {
|
||||||
|
t, err := ioutil.TempFile("", "restore")
|
||||||
|
if err != nil {
|
||||||
|
utils.InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer t.Close()
|
||||||
|
if err := compat.SaveFromBody(t, r); err != nil {
|
||||||
|
utils.InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
targetFile = t.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
options := libpod.ContainerCheckpointOptions{
|
||||||
|
Keep: query.Keep,
|
||||||
|
TCPEstablished: query.TCPEstablished,
|
||||||
|
IgnoreRootfs: query.IgnoreRootFS,
|
||||||
|
IgnoreStaticIP: query.IgnoreStaticIP,
|
||||||
|
IgnoreStaticMAC: query.IgnoreStaticMAC,
|
||||||
|
}
|
||||||
|
if query.Import {
|
||||||
|
options.TargetFile = targetFile
|
||||||
|
options.Name = query.Name
|
||||||
|
}
|
||||||
|
err = ctr.Restore(r.Context(), options)
|
||||||
|
if err != nil {
|
||||||
|
utils.InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.WriteResponse(w, http.StatusOK, entities.RestoreReport{Id: ctr.ID()})
|
||||||
|
}
|
||||||
|
@ -1282,5 +1282,100 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
|
|||||||
// 500:
|
// 500:
|
||||||
// $ref: "#/responses/InternalError"
|
// $ref: "#/responses/InternalError"
|
||||||
r.HandleFunc(VersionedPath("/libpod/containers/{name}/export"), s.APIHandler(compat.ExportContainer)).Methods(http.MethodGet)
|
r.HandleFunc(VersionedPath("/libpod/containers/{name}/export"), s.APIHandler(compat.ExportContainer)).Methods(http.MethodGet)
|
||||||
|
// swagger:operation GET /libpod/containers/{name}/checkout libpod libpodCheckpointContainer
|
||||||
|
// ---
|
||||||
|
// tags:
|
||||||
|
// - containers
|
||||||
|
// summary: Checkpoint a container
|
||||||
|
// parameters:
|
||||||
|
// - in: path
|
||||||
|
// name: name
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// description: the name or ID of the container
|
||||||
|
// - in: query
|
||||||
|
// name: keep
|
||||||
|
// type: boolean
|
||||||
|
// description: keep all temporary checkpoint files
|
||||||
|
// - in: query
|
||||||
|
// name: leaveRunning
|
||||||
|
// type: boolean
|
||||||
|
// description: leave the container running after writing checkpoint to disk
|
||||||
|
// - in: query
|
||||||
|
// name: tcpEstablished
|
||||||
|
// type: boolean
|
||||||
|
// description: checkpoint a container with established TCP connections
|
||||||
|
// - in: query
|
||||||
|
// name: export
|
||||||
|
// type: boolean
|
||||||
|
// description: export the checkpoint image to a tar.gz
|
||||||
|
// - in: query
|
||||||
|
// name: ignoreRootFS
|
||||||
|
// type: boolean
|
||||||
|
// description: do not include root file-system changes when exporting
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// responses:
|
||||||
|
// 200:
|
||||||
|
// description: tarball is returned in body if exported
|
||||||
|
// 404:
|
||||||
|
// $ref: "#/responses/NoSuchContainer"
|
||||||
|
// 500:
|
||||||
|
// $ref: "#/responses/InternalError"
|
||||||
|
r.HandleFunc(VersionedPath("/libpod/containers/{name}/checkpoint"), s.APIHandler(libpod.Checkpoint)).Methods(http.MethodPost)
|
||||||
|
// swagger:operation GET /libpod/containers/{name} restore libpod libpodRestoreContainer
|
||||||
|
// ---
|
||||||
|
// tags:
|
||||||
|
// - containers
|
||||||
|
// summary: Restore a container
|
||||||
|
// description: Restore a container from a checkpoint.
|
||||||
|
// parameters:
|
||||||
|
// - in: path
|
||||||
|
// name: name
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// description: the name or id of the container
|
||||||
|
// - in: query
|
||||||
|
// name: name
|
||||||
|
// type: string
|
||||||
|
// description: the name of the container when restored from a tar. can only be used with import
|
||||||
|
// - in: query
|
||||||
|
// name: keep
|
||||||
|
// type: boolean
|
||||||
|
// description: keep all temporary checkpoint files
|
||||||
|
// - in: query
|
||||||
|
// name: leaveRunning
|
||||||
|
// type: boolean
|
||||||
|
// description: leave the container running after writing checkpoint to disk
|
||||||
|
// - in: query
|
||||||
|
// name: tcpEstablished
|
||||||
|
// type: boolean
|
||||||
|
// description: checkpoint a container with established TCP connections
|
||||||
|
// - in: query
|
||||||
|
// name: import
|
||||||
|
// type: boolean
|
||||||
|
// description: import the restore from a checkpoint tar.gz
|
||||||
|
// - in: query
|
||||||
|
// name: ignoreRootFS
|
||||||
|
// type: boolean
|
||||||
|
// description: do not include root file-system changes when exporting
|
||||||
|
// - in: query
|
||||||
|
// name: ignoreStaticIP
|
||||||
|
// type: boolean
|
||||||
|
// description: ignore IP address if set statically
|
||||||
|
// - in: query
|
||||||
|
// name: ignoreStaticMAC
|
||||||
|
// type: boolean
|
||||||
|
// description: ignore MAC address if set statically
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// responses:
|
||||||
|
// 200:
|
||||||
|
// description: tarball is returned in body if exported
|
||||||
|
// 404:
|
||||||
|
// $ref: "#/responses/NoSuchContainer"
|
||||||
|
// 500:
|
||||||
|
// $ref: "#/responses/InternalError"
|
||||||
|
r.HandleFunc(VersionedPath("/libpod/containers/{name}/restore"), s.APIHandler(libpod.Restore)).Methods(http.MethodPost)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
79
pkg/bindings/containers/checkpoint.go
Normal file
79
pkg/bindings/containers/checkpoint.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package containers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/containers/libpod/pkg/bindings"
|
||||||
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Checkpoint checkpoints the given container (identified by nameOrId). All additional
|
||||||
|
// options are options and allow for more fine grained control of the checkpoint process.
|
||||||
|
func Checkpoint(ctx context.Context, nameOrId string, keep, leaveRunning, tcpEstablished, ignoreRootFS *bool, export *string) (*entities.CheckpointReport, error) {
|
||||||
|
var report entities.CheckpointReport
|
||||||
|
conn, err := bindings.GetClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
params := url.Values{}
|
||||||
|
if keep != nil {
|
||||||
|
params.Set("keep", strconv.FormatBool(*keep))
|
||||||
|
}
|
||||||
|
if leaveRunning != nil {
|
||||||
|
params.Set("leaveRunning", strconv.FormatBool(*leaveRunning))
|
||||||
|
}
|
||||||
|
if tcpEstablished != nil {
|
||||||
|
params.Set("TCPestablished", strconv.FormatBool(*tcpEstablished))
|
||||||
|
}
|
||||||
|
if ignoreRootFS != nil {
|
||||||
|
params.Set("ignoreRootFS", strconv.FormatBool(*ignoreRootFS))
|
||||||
|
}
|
||||||
|
if export != nil {
|
||||||
|
params.Set("export", *export)
|
||||||
|
}
|
||||||
|
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/checkpoint", params, nameOrId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &report, response.Process(&report)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores a checkpointed container to running. The container is identified by the nameOrId option. All
|
||||||
|
// additional options are optional and allow finer control of the restore processs.
|
||||||
|
func Restore(ctx context.Context, nameOrId string, keep, tcpEstablished, ignoreRootFS, ignoreStaticIP, ignoreStaticMAC *bool, name, importArchive *string) (*entities.RestoreReport, error) {
|
||||||
|
var report entities.RestoreReport
|
||||||
|
conn, err := bindings.GetClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
params := url.Values{}
|
||||||
|
if keep != nil {
|
||||||
|
params.Set("keep", strconv.FormatBool(*keep))
|
||||||
|
}
|
||||||
|
if tcpEstablished != nil {
|
||||||
|
params.Set("TCPestablished", strconv.FormatBool(*tcpEstablished))
|
||||||
|
}
|
||||||
|
if ignoreRootFS != nil {
|
||||||
|
params.Set("ignoreRootFS", strconv.FormatBool(*ignoreRootFS))
|
||||||
|
}
|
||||||
|
if ignoreStaticIP != nil {
|
||||||
|
params.Set("ignoreStaticIP", strconv.FormatBool(*ignoreStaticIP))
|
||||||
|
}
|
||||||
|
if ignoreStaticMAC != nil {
|
||||||
|
params.Set("ignoreStaticMAC", strconv.FormatBool(*ignoreStaticMAC))
|
||||||
|
}
|
||||||
|
if name != nil {
|
||||||
|
params.Set("name", *name)
|
||||||
|
}
|
||||||
|
if importArchive != nil {
|
||||||
|
params.Set("import", *importArchive)
|
||||||
|
}
|
||||||
|
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restore", params, nameOrId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &report, response.Process(&report)
|
||||||
|
}
|
@ -1,6 +1,4 @@
|
|||||||
// +build !remoteclient
|
package checkpoint
|
||||||
|
|
||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -42,9 +40,9 @@ func crImportFromJSON(filePath string, v interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, 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)
|
@ -121,3 +121,35 @@ type CommitReport struct {
|
|||||||
type ContainerExportOptions struct {
|
type ContainerExportOptions struct {
|
||||||
Output string
|
Output string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CheckpointOptions struct {
|
||||||
|
All bool
|
||||||
|
Export string
|
||||||
|
IgnoreRootFS bool
|
||||||
|
Keep bool
|
||||||
|
Latest bool
|
||||||
|
LeaveRuninng bool
|
||||||
|
TCPEstablished bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckpointReport struct {
|
||||||
|
Err error
|
||||||
|
Id string
|
||||||
|
}
|
||||||
|
|
||||||
|
type RestoreOptions struct {
|
||||||
|
All bool
|
||||||
|
IgnoreRootFS bool
|
||||||
|
IgnoreStaticIP bool
|
||||||
|
IgnoreStaticMAC bool
|
||||||
|
Import string
|
||||||
|
Keep bool
|
||||||
|
Latest bool
|
||||||
|
Name string
|
||||||
|
TCPEstablished bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type RestoreReport struct {
|
||||||
|
Err error
|
||||||
|
Id string
|
||||||
|
}
|
||||||
|
@ -8,6 +8,8 @@ import (
|
|||||||
|
|
||||||
type ContainerEngine interface {
|
type ContainerEngine interface {
|
||||||
ContainerCommit(ctx context.Context, nameOrId string, options CommitOptions) (*CommitReport, error)
|
ContainerCommit(ctx context.Context, nameOrId string, options CommitOptions) (*CommitReport, error)
|
||||||
|
ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error)
|
||||||
|
ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error)
|
||||||
ContainerExists(ctx context.Context, nameOrId string) (*BoolReport, error)
|
ContainerExists(ctx context.Context, nameOrId string) (*BoolReport, error)
|
||||||
ContainerInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]*ContainerInspectReport, error)
|
ContainerInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]*ContainerInspectReport, error)
|
||||||
ContainerExport(ctx context.Context, nameOrId string, options ContainerExportOptions) error
|
ContainerExport(ctx context.Context, nameOrId string, options ContainerExportOptions) error
|
||||||
|
@ -13,12 +13,42 @@ import (
|
|||||||
"github.com/containers/libpod/libpod/define"
|
"github.com/containers/libpod/libpod/define"
|
||||||
"github.com/containers/libpod/libpod/image"
|
"github.com/containers/libpod/libpod/image"
|
||||||
"github.com/containers/libpod/pkg/adapter/shortcuts"
|
"github.com/containers/libpod/pkg/adapter/shortcuts"
|
||||||
|
"github.com/containers/libpod/pkg/checkpoint"
|
||||||
"github.com/containers/libpod/pkg/domain/entities"
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
"github.com/containers/libpod/pkg/signal"
|
"github.com/containers/libpod/pkg/signal"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// getContainersByContext gets pods whether all, latest, or a slice of names/ids
|
||||||
|
// is specified.
|
||||||
|
func getContainersByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, err error) {
|
||||||
|
var ctr *libpod.Container
|
||||||
|
ctrs = []*libpod.Container{}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case all:
|
||||||
|
ctrs, err = runtime.GetAllContainers()
|
||||||
|
case latest:
|
||||||
|
ctr, err = runtime.GetLatestContainer()
|
||||||
|
ctrs = append(ctrs, ctr)
|
||||||
|
default:
|
||||||
|
for _, n := range names {
|
||||||
|
ctr, e := runtime.LookupContainer(n)
|
||||||
|
if e != nil {
|
||||||
|
// Log all errors here, so callers don't need to.
|
||||||
|
logrus.Debugf("Error looking up container %q: %v", n, e)
|
||||||
|
if err == nil {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctrs = append(ctrs, ctr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Should return *entities.ContainerExistsReport, error
|
// TODO: Should return *entities.ContainerExistsReport, error
|
||||||
func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) {
|
func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) {
|
||||||
_, err := ic.Libpod.LookupContainer(nameOrId)
|
_, err := ic.Libpod.LookupContainer(nameOrId)
|
||||||
@ -333,3 +363,82 @@ func (ic *ContainerEngine) ContainerExport(ctx context.Context, nameOrId string,
|
|||||||
}
|
}
|
||||||
return ctr.Export(options.Output)
|
return ctr.Export(options.Output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds []string, options entities.CheckpointOptions) ([]*entities.CheckpointReport, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
cons []*libpod.Container
|
||||||
|
reports []*entities.CheckpointReport
|
||||||
|
)
|
||||||
|
checkOpts := libpod.ContainerCheckpointOptions{
|
||||||
|
Keep: options.Keep,
|
||||||
|
TCPEstablished: options.TCPEstablished,
|
||||||
|
TargetFile: options.Export,
|
||||||
|
IgnoreRootfs: options.IgnoreRootFS,
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.All {
|
||||||
|
running := func(c *libpod.Container) bool {
|
||||||
|
state, _ := c.State()
|
||||||
|
return state == define.ContainerStateRunning
|
||||||
|
}
|
||||||
|
cons, err = ic.Libpod.GetContainers(running)
|
||||||
|
} else {
|
||||||
|
cons, err = getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, con := range cons {
|
||||||
|
err = con.Checkpoint(ctx, checkOpts)
|
||||||
|
reports = append(reports, &entities.CheckpointReport{
|
||||||
|
Err: err,
|
||||||
|
Id: con.ID(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return reports, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []string, options entities.RestoreOptions) ([]*entities.RestoreReport, error) {
|
||||||
|
var (
|
||||||
|
cons []*libpod.Container
|
||||||
|
err error
|
||||||
|
filterFuncs []libpod.ContainerFilter
|
||||||
|
reports []*entities.RestoreReport
|
||||||
|
)
|
||||||
|
|
||||||
|
restoreOptions := libpod.ContainerCheckpointOptions{
|
||||||
|
Keep: options.Keep,
|
||||||
|
TCPEstablished: options.TCPEstablished,
|
||||||
|
TargetFile: options.Import,
|
||||||
|
Name: options.Name,
|
||||||
|
IgnoreRootfs: options.IgnoreRootFS,
|
||||||
|
IgnoreStaticIP: options.IgnoreStaticIP,
|
||||||
|
IgnoreStaticMAC: options.IgnoreStaticMAC,
|
||||||
|
}
|
||||||
|
|
||||||
|
filterFuncs = append(filterFuncs, func(c *libpod.Container) bool {
|
||||||
|
state, _ := c.State()
|
||||||
|
return state == define.ContainerStateExited
|
||||||
|
})
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case options.Import != "":
|
||||||
|
cons, err = checkpoint.CRImportCheckpoint(ctx, ic.Libpod, options.Import, options.Name)
|
||||||
|
case options.All:
|
||||||
|
cons, err = ic.Libpod.GetContainers(filterFuncs...)
|
||||||
|
default:
|
||||||
|
cons, err = getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, con := range cons {
|
||||||
|
err := con.Restore(ctx, restoreOptions)
|
||||||
|
reports = append(reports, &entities.RestoreReport{
|
||||||
|
Err: err,
|
||||||
|
Id: con.ID(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return reports, nil
|
||||||
|
}
|
||||||
|
@ -6,7 +6,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/containers/image/v5/docker/reference"
|
"github.com/containers/image/v5/docker/reference"
|
||||||
|
"github.com/containers/libpod/libpod/define"
|
||||||
|
"github.com/containers/libpod/pkg/api/handlers/libpod"
|
||||||
"github.com/containers/libpod/pkg/bindings/containers"
|
"github.com/containers/libpod/pkg/bindings/containers"
|
||||||
"github.com/containers/libpod/pkg/domain/entities"
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -226,3 +227,72 @@ func (ic *ContainerEngine) ContainerExport(ctx context.Context, nameOrId string,
|
|||||||
}
|
}
|
||||||
return containers.Export(ic.ClientCxt, nameOrId, w)
|
return containers.Export(ic.ClientCxt, nameOrId, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds []string, options entities.CheckpointOptions) ([]*entities.CheckpointReport, error) {
|
||||||
|
var (
|
||||||
|
reports []*entities.CheckpointReport
|
||||||
|
err error
|
||||||
|
ctrs []libpod.ListContainer
|
||||||
|
)
|
||||||
|
|
||||||
|
if options.All {
|
||||||
|
allCtrs, err := getContainersByContext(ic.ClientCxt, true, []string{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// narrow the list to running only
|
||||||
|
for _, c := range allCtrs {
|
||||||
|
if c.State == define.ContainerStateRunning.String() {
|
||||||
|
ctrs = append(ctrs, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
ctrs, err = getContainersByContext(ic.ClientCxt, false, namesOrIds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, c := range ctrs {
|
||||||
|
report, err := containers.Checkpoint(ic.ClientCxt, c.ID, &options.Keep, &options.LeaveRuninng, &options.TCPEstablished, &options.IgnoreRootFS, &options.Export)
|
||||||
|
if err != nil {
|
||||||
|
reports = append(reports, &entities.CheckpointReport{Id: c.ID, Err: err})
|
||||||
|
}
|
||||||
|
reports = append(reports, report)
|
||||||
|
}
|
||||||
|
return reports, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []string, options entities.RestoreOptions) ([]*entities.RestoreReport, error) {
|
||||||
|
var (
|
||||||
|
reports []*entities.RestoreReport
|
||||||
|
err error
|
||||||
|
ctrs []libpod.ListContainer
|
||||||
|
)
|
||||||
|
if options.All {
|
||||||
|
allCtrs, err := getContainersByContext(ic.ClientCxt, true, []string{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// narrow the list to exited only
|
||||||
|
for _, c := range allCtrs {
|
||||||
|
if c.State == define.ContainerStateExited.String() {
|
||||||
|
ctrs = append(ctrs, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
ctrs, err = getContainersByContext(ic.ClientCxt, false, namesOrIds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, c := range ctrs {
|
||||||
|
report, err := containers.Restore(ic.ClientCxt, c.ID, &options.Keep, &options.TCPEstablished, &options.IgnoreRootFS, &options.IgnoreStaticIP, &options.IgnoreStaticMAC, &options.Name, &options.Import)
|
||||||
|
if err != nil {
|
||||||
|
reports = append(reports, &entities.RestoreReport{Id: c.ID, Err: err})
|
||||||
|
}
|
||||||
|
reports = append(reports, report)
|
||||||
|
}
|
||||||
|
return reports, nil
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user