Skip to content
Closed
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
129 changes: 129 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package api

import (
"context"
"errors"
"io"
"os"
"time"

specs "github.com/opencontainers/runtime-spec/specs-go"
)

var (
ErrNotImplemented = errors.New("command not implemented")
ErrEmptyID = errors.New("container id cannot be empty")
)

type GlobalConfig struct {
Debug bool
Root string
CriuPath string
SystemdCgroup bool
}

type ContainerOperations interface {
Create(ctx context.Context, id string, opts CommandOpts) (*CommandResult, error)
Delete(ctx context.Context, id string, opts DeleteOpts) error
Kill(ctx context.Context, id string, sig os.Signal, opts KillOpts) error
List(ctx context.Context) ([]Container, error)
PS(ctx context.Context, id string) ([]int, error)
Pause(ctx context.Context, id string) error
Resume(ctx context.Context, id string) error
Run(ctx context.Context, id string, opts CommandOpts) (*CommandResult, error)
Start(ctx context.Context, id string) error
State(ctx context.Context, id string) (*Container, error)
Exec(ctx context.Context, id string, opts ExecOpts) (*CommandResult, error)
}

type CheckpointOperations interface {
Checkpoint(ctx context.Context, id string, opts CheckpointOpts) error
Restore(ctx context.Context, id string, opts RestoreOpts) (*CommandResult, error)
}

type ExecOpts struct {
PidFile string
Detach bool
ConsoleSocket string
Process *specs.Process
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
}

type RestoreOpts struct {
CommandOpts
CheckpointOpts
}

type DeleteOpts struct {
Force bool
}

type KillOpts struct {
All bool
}

type CriuPageServerInfo struct {
Address string // IP address of CRIU page server
Port int32 // port number of CRIU page server
}

type CheckpointOpts struct {
ImagesDirectory string // directory for storing image files
WorkDirectory string // directory to cd and write logs/pidfiles/stats to
ParentImage string // directory for storing parent image files in pre-dump and dump
LeaveRunning bool // leave container in running state after checkpoint
TcpEstablished bool // checkpoint/restore established TCP connections
ExternalUnixConnections bool // allow external unix connections
ShellJob bool // allow to dump and restore shell jobs
FileLocks bool // handle file locks, for safety
PreDump bool // call criu predump to perform iterative checkpoint
PageServer CriuPageServerInfo // allow to dump to criu page server
ManageCgroupsMode string // dump or restore cgroup mode
EmptyNs uint32 // don't c/r properties for namespace from this mask
AutoDedup bool // auto deduplication for incremental dumps
LazyPages bool // restore memory pages lazily using userfaultfd
StatusFd string // fd for feedback when lazy server is ready
}

type CommandOpts struct {
Spec *specs.Spec
PidFile string
ConsoleSocket string
NoPivot bool
NoNewKeyring bool
PreserveFDs int
Detach bool
NoSubreaper bool
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
}

type CommandResult struct {
Status int
}

// Container represents the platform agnostic pieces relating to a
// running container's status and state
type Container struct {
// Version is the OCI version for the container
Version string `json:"ociVersion"`
// ID is the container ID
ID string `json:"id"`
// InitProcessPid is the init process id in the parent namespace
InitProcessPid int `json:"pid"`
// Status is the current status of the container, running, paused, ...
Status string `json:"status"`
// Bundle is the path on the filesystem to the bundle
Bundle string `json:"bundle"`
// Rootfs is a path to a directory containing the container's root filesystem.
Rootfs string `json:"rootfs"`
// Created is the unix timestamp for the creation time of the container in UTC
Created time.Time `json:"created"`
// Annotations is the user defined annotations added to the config.
Annotations map[string]string `json:"annotations,omitempty"`
// The owner of the state directory (the owner of the container).
Owner string `json:"owner"`
}
150 changes: 150 additions & 0 deletions api/command/checkpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package command

import (
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/opencontainers/runc/api"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/urfave/cli"
"golang.org/x/sys/unix"
)

func NewCheckpointCommand(apiNew APINew) cli.Command {
return cli.Command{
Name: "checkpoint",
Usage: "checkpoint a running container",
ArgsUsage: `<container-id>

Where "<container-id>" is the name for the instance of the container to be
checkpointed.`,
Description: `The checkpoint command saves the state of the container instance.`,
Flags: []cli.Flag{
cli.StringFlag{Name: "image-path", Value: "", Usage: "path for saving criu image files"},
cli.StringFlag{Name: "work-path", Value: "", Usage: "path for saving work files and logs"},
cli.StringFlag{Name: "parent-path", Value: "", Usage: "path for previous criu image files in pre-dump"},
cli.BoolFlag{Name: "leave-running", Usage: "leave the process running after checkpointing"},
cli.BoolFlag{Name: "tcp-established", Usage: "allow open tcp connections"},
cli.BoolFlag{Name: "ext-unix-sk", Usage: "allow external unix sockets"},
cli.BoolFlag{Name: "shell-job", Usage: "allow shell jobs"},
cli.BoolFlag{Name: "lazy-pages", Usage: "use userfaultfd to lazily restore memory pages"},
cli.StringFlag{Name: "status-fd", Value: "", Usage: "criu writes \\0 to this FD once lazy-pages is ready"},
cli.StringFlag{Name: "page-server", Value: "", Usage: "ADDRESS:PORT of the page server"},
cli.BoolFlag{Name: "file-locks", Usage: "handle file locks, for safety"},
cli.BoolFlag{Name: "pre-dump", Usage: "dump container's memory information only, leave the container running after this"},
cli.StringFlag{Name: "manage-cgroups-mode", Value: "", Usage: "cgroups mode: 'soft' (default), 'full' and 'strict'"},
cli.StringSliceFlag{Name: "empty-ns", Usage: "create a namespace, but don't restore its properties"},
cli.BoolFlag{Name: "auto-dedup", Usage: "enable auto deduplication of memory images"},
},
Action: func(ctx *cli.Context) error {
if err := CheckArgs(ctx, 1, ExactArgs); err != nil {
return err
}
id, err := GetID(ctx)
if err != nil {
return err
}
a, err := apiNew(NewGlobalConfig(ctx))
if err != nil {
return err
}
cr, ok := a.(api.CheckpointOperations)
if !ok {
return api.ErrNotImplemented
}
opts, err := criuOptions(ctx)
if err != nil {
return err
}
// these are the mandatory criu options for a container
if err := setPageServer(ctx, opts); err != nil {
return err
}
if err := setEmptyNsMask(ctx, opts); err != nil {
return err
}
return cr.Checkpoint(context.Background(), id, *opts)
},
}

}

func getCheckpointImagePath(ctx *cli.Context) string {
imagePath := ctx.String("image-path")
if imagePath == "" {
imagePath = getDefaultImagePath(ctx)
}
return imagePath
}

func setPageServer(ctx *cli.Context, options *api.CheckpointOpts) error {
// xxx following criu opts are optional
// The dump image can be sent to a criu page server
if psOpt := ctx.String("page-server"); psOpt != "" {
addressPort := strings.Split(psOpt, ":")
if len(addressPort) != 2 {
return fmt.Errorf("use --page-server ADDRESS:PORT to specify page server")
}
portInt, err := strconv.Atoi(addressPort[1])
if err != nil {
return fmt.Errorf("invalid port number")
}
options.PageServer = api.CriuPageServerInfo{
Address: addressPort[0],
Port: int32(portInt),
}
}
return nil
}

var namespaceMapping = map[specs.LinuxNamespaceType]int{
specs.NetworkNamespace: unix.CLONE_NEWNET,
}

func setEmptyNsMask(ctx *cli.Context, options *api.CheckpointOpts) error {
var nsmask int

for _, ns := range ctx.StringSlice("empty-ns") {
f, exists := namespaceMapping[specs.LinuxNamespaceType(ns)]
if !exists {
return fmt.Errorf("namespace %q is not supported", ns)
}
nsmask |= f
}
options.EmptyNs = uint32(nsmask)
return nil
}

func getDefaultImagePath(ctx *cli.Context) string {
cwd, err := os.Getwd()
if err != nil {
panic(err)
}
return filepath.Join(cwd, "checkpoint")
}

func criuOptions(ctx *cli.Context) (*api.CheckpointOpts, error) {
imagePath := getCheckpointImagePath(ctx)
if err := os.MkdirAll(imagePath, 0655); err != nil {
return nil, err
}
return &api.CheckpointOpts{
ImagesDirectory: imagePath,
WorkDirectory: ctx.String("work-path"),
ParentImage: ctx.String("parent-path"),
LeaveRunning: ctx.Bool("leave-running"),
TcpEstablished: ctx.Bool("tcp-established"),
ExternalUnixConnections: ctx.Bool("ext-unix-sk"),
ShellJob: ctx.Bool("shell-job"),
FileLocks: ctx.Bool("file-locks"),
PreDump: ctx.Bool("pre-dump"),
AutoDedup: ctx.Bool("auto-dedup"),
LazyPages: ctx.Bool("lazy-pages"),
StatusFd: ctx.String("status-fd"),
ManageCgroupsMode: ctx.String("manage-cgroups-mode"),
}, nil
}
Loading