Skip to content

Commit

Permalink
Merge pull request containers#12281 from vrothberg/fix-12007
Browse files Browse the repository at this point in the history
fix remote checkpoint/restore
  • Loading branch information
openshift-merge-robot authored Nov 17, 2021
2 parents bd6fbb1 + 33ec8c6 commit 85733e0
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 133 deletions.
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

0 comments on commit 85733e0

Please sign in to comment.