Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix remote checkpoint/restore #12281

Merged
merged 1 commit into from
Nov 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions cmd/podman/containers/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/containers/podman/v3/cmd/podman/validate"
"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/containers/podman/v3/pkg/rootless"
"github.com/containers/podman/v3/pkg/specgenutil"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -120,12 +119,7 @@ func restore(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
if len(inputPorts) > 0 {
restoreOptions.PublishPorts, err = specgenutil.CreatePortBindings(inputPorts)
if err != nil {
return err
}
}
restoreOptions.PublishPorts = inputPorts

argLen := len(args)
if restoreOptions.Import != "" {
Expand Down
143 changes: 76 additions & 67 deletions pkg/api/handlers/libpod/containers.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package libpod

import (
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"

"github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/libpod/define"
Expand Down Expand Up @@ -206,7 +208,9 @@ func ShowMountedContainers(w http.ResponseWriter, r *http.Request) {
}

func Checkpoint(w http.ResponseWriter, r *http.Request) {
var targetFile string
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
containerEngine := abi.ContainerEngine{Libpod: runtime}

decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
query := struct {
Keep bool `schema:"keep"`
Expand All @@ -224,66 +228,68 @@ func Checkpoint(w http.ResponseWriter, r *http.Request) {
errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
return
}

name := utils.GetName(r)
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
ctr, err := runtime.LookupContainer(name)
if err != nil {
if _, err := runtime.LookupContainer(name); err != nil {
utils.ContainerNotFound(w, name, err)
return
}
names := []string{name}

options := entities.CheckpointOptions{
Keep: query.Keep,
LeaveRunning: query.LeaveRunning,
TCPEstablished: query.TCPEstablished,
IgnoreRootFS: query.IgnoreRootFS,
PrintStats: query.PrintStats,
}

if query.Export {
tmpFile, err := ioutil.TempFile("", "checkpoint")
f, err := ioutil.TempFile("", "checkpoint")
if err != nil {
utils.InternalServerError(w, err)
return
}
defer os.Remove(tmpFile.Name())
if err := tmpFile.Close(); err != nil {
defer os.Remove(f.Name())
if err := f.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,
PrintStats: query.PrintStats,
}
if query.Export {
options.TargetFile = targetFile
options.Export = f.Name()
}
criuStatistics, runtimeCheckpointDuration, err := ctr.Checkpoint(r.Context(), options)

reports, err := containerEngine.ContainerCheckpoint(r.Context(), names, options)
if err != nil {
utils.InternalServerError(w, err)
return
}
if query.Export {
f, err := os.Open(targetFile)
if err != nil {
utils.InternalServerError(w, err)

if !query.Export {
if len(reports) != 1 {
utils.InternalServerError(w, fmt.Errorf("expected 1 restore report but got %d", len(reports)))
return
}
defer f.Close()
utils.WriteResponse(w, http.StatusOK, f)
if reports[0].Err != nil {
utils.InternalServerError(w, reports[0].Err)
return
}
utils.WriteResponse(w, http.StatusOK, reports[0])
return
}

f, err := os.Open(options.Export)
if err != nil {
utils.InternalServerError(w, err)
return
}
utils.WriteResponse(
w,
http.StatusOK,
entities.CheckpointReport{
Id: ctr.ID(),
RuntimeDuration: runtimeCheckpointDuration,
CRIUStatistics: criuStatistics,
},
)
defer f.Close()
utils.WriteResponse(w, http.StatusOK, f)
}

func Restore(w http.ResponseWriter, r *http.Request) {
var (
targetFile string
)
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
containerEngine := abi.ContainerEngine{Libpod: runtime}

decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
query := struct {
Keep bool `schema:"keep"`
Expand All @@ -295,6 +301,7 @@ func Restore(w http.ResponseWriter, r *http.Request) {
IgnoreStaticIP bool `schema:"ignoreStaticIP"`
IgnoreStaticMAC bool `schema:"ignoreStaticMAC"`
PrintStats bool `schema:"printStats"`
PublishPorts string `schema:"publishPorts"`
}{
// override any golang type defaults
}
Expand All @@ -303,53 +310,55 @@ func Restore(w http.ResponseWriter, r *http.Request) {
errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
return
}
name := utils.GetName(r)
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
ctr, err := runtime.LookupContainer(name)
if err != nil {
utils.ContainerNotFound(w, name, err)
return

options := entities.RestoreOptions{
Name: query.Name,
Keep: query.Keep,
TCPEstablished: query.TCPEstablished,
IgnoreRootFS: query.IgnoreRootFS,
IgnoreVolumes: query.IgnoreVolumes,
IgnoreStaticIP: query.IgnoreStaticIP,
IgnoreStaticMAC: query.IgnoreStaticMAC,
PrintStats: query.PrintStats,
PublishPorts: strings.Fields(query.PublishPorts),
}

var names []string
if query.Import {
t, err := ioutil.TempFile("", "restore")
if err != nil {
utils.InternalServerError(w, err)
return
}
defer t.Close()
defer os.Remove(t.Name())
if err := compat.SaveFromBody(t, r); err != nil {
utils.InternalServerError(w, err)
return
}
targetFile = t.Name()
options.Import = t.Name()
} else {
name := utils.GetName(r)
if _, err := runtime.LookupContainer(name); err != nil {
utils.ContainerNotFound(w, name, err)
return
}
names = []string{name}
}

options := libpod.ContainerCheckpointOptions{
Keep: query.Keep,
TCPEstablished: query.TCPEstablished,
IgnoreRootfs: query.IgnoreRootFS,
IgnoreStaticIP: query.IgnoreStaticIP,
IgnoreStaticMAC: query.IgnoreStaticMAC,
PrintStats: query.PrintStats,
}
if query.Import {
options.TargetFile = targetFile
options.Name = query.Name
}
criuStatistics, runtimeRestoreDuration, err := ctr.Restore(r.Context(), options)
reports, err := containerEngine.ContainerRestore(r.Context(), names, options)
if err != nil {
utils.InternalServerError(w, err)
return
}
utils.WriteResponse(
w,
http.StatusOK,
entities.RestoreReport{
Id: ctr.ID(),
RuntimeDuration: runtimeRestoreDuration,
CRIUStatistics: criuStatistics,
},
)
if len(reports) != 1 {
utils.InternalServerError(w, fmt.Errorf("expected 1 restore report but got %d", len(reports)))
return
}
if reports[0].Err != nil {
utils.InternalServerError(w, reports[0].Err)
return
}
utils.WriteResponse(w, http.StatusOK, reports[0])
}

func InitContainer(w http.ResponseWriter, r *http.Request) {
Expand Down
49 changes: 43 additions & 6 deletions pkg/bindings/containers/checkpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package containers

import (
"context"
"io"
"net/http"
"os"

"github.com/containers/podman/v3/pkg/bindings"
"github.com/containers/podman/v3/pkg/domain/entities"
Expand All @@ -23,13 +25,34 @@ func Checkpoint(ctx context.Context, nameOrID string, options *CheckpointOptions
if err != nil {
return nil, err
}

// "export" is a bool for the server so override it in the parameters
// if set.
export := false
if options.Export != nil && *options.Export != "" {
export = true
params.Set("export", "true")
}
response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/checkpoint", params, nil, nameOrID)
if err != nil {
return nil, err
}
defer response.Body.Close()

return &report, response.Process(&report)
if !export {
return &report, response.Process(&report)
}

f, err := os.OpenFile(*options.Export, os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
return nil, err
}
defer f.Close()
if _, err := io.Copy(f, response.Body); err != nil {
return nil, err
}

return &entities.CheckpointReport{}, nil
}

// Restore restores a checkpointed container to running. The container is identified by the nameOrID option. All
Expand All @@ -47,12 +70,26 @@ func Restore(ctx context.Context, nameOrID string, options *RestoreOptions) (*en
if err != nil {
return nil, err
}
// The import key is a reserved golang term
params.Del("ImportArchive")
if i := options.GetImportAchive(); options.Changed("ImportArchive") {
params.Set("import", i)

for _, p := range options.PublishPorts {
params.Add("publishPorts", p)
}

params.Del("ImportArchive") // The import key is a reserved golang term

// Open the to-be-imported archive if needed.
var r io.Reader
if i := options.GetImportAchive(); i != "" {
params.Set("import", "true")
r, err = os.Open(i)
if err != nil {
return nil, err
}
// Hard-code the name since it will be ignored in any case.
nameOrID = "import"
}
response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/restore", params, nil, nameOrID)

response, err := conn.DoRequest(ctx, r, http.MethodPost, "/containers/%s/restore", params, nil, nameOrID)
if err != nil {
return nil, err
}
Expand Down
7 changes: 6 additions & 1 deletion pkg/bindings/containers/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,23 @@ type CheckpointOptions struct {
Keep *bool
LeaveRunning *bool
TCPEstablished *bool
PrintStats *bool
}

//go:generate go run ../generator/generator.go RestoreOptions
// RestoreOptions are optional options for restoring containers
type RestoreOptions struct {
IgnoreRootfs *bool
IgnoreVolumes *bool
IgnoreStaticIP *bool
IgnoreStaticMAC *bool
ImportAchive *string
Keep *bool
Name *string
TCPEstablished *bool
Pod *string
PrintStats *bool
PublishPorts []string
}

//go:generate go run ../generator/generator.go CreateOptions
Expand All @@ -86,7 +90,8 @@ type ExecInspectOptions struct{}
//go:generate go run ../generator/generator.go ExecStartOptions
// ExecStartOptions are optional options for starting
// exec sessions
type ExecStartOptions struct{}
type ExecStartOptions struct {
}

//go:generate go run ../generator/generator.go HealthCheckOptions
// HealthCheckOptions are optional options for checking
Expand Down
15 changes: 15 additions & 0 deletions pkg/bindings/containers/types_checkpoint_options.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading