Skip to content

Commit

Permalink
fix remote checkpoint/restore
Browse files Browse the repository at this point in the history
Nothing was working before, and it's too much to summarize.  To make
sure we're not regressing in the future again, enable the remote e2e
tests.

Fixes: containers#12007
Signed-off-by: Valentin Rothberg <[email protected]>
  • Loading branch information
vrothberg committed Nov 16, 2021
1 parent be681ab commit 33ec8c6
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 33ec8c6

Please sign in to comment.