Commit 3f994bcf authored by Pavel Emelyanov's avatar Pavel Emelyanov Committed by Andrei Vagin

lib: Add simple Go wrappers for swrk mode

We'll need some docs :) bu the API is

criu := MakeCriu()

criu.Dump(opts, notify)
criu.Restore(opts, notify)
criu.PreDump(opts, notify)
criu.StartPageServer(opts)

where opts is the object from rpc.proto, Go has almost native support
for those, so caller should

- compile .proto file
- export it and golang/protobuf/proto
- create and initialize the CriuOpts struct

and notify is an interface with callbacks that correspond to criu
notification messages.

A stupid dump/restore tool in src/test/main.go demonstrates the above.

Changes since v1:

* Added keep_open mode for pre-dumps. Do use it one needs
  to call criu.Prepare() right after creation and criu.Cleanup()
  right after .Dump()

* Report resp.cr_errmsg string on request error.

Further TODO:

- docs
- code comments

travis-ci: success for libphaul (rev2)
Signed-off-by: 's avatarPavel Emelyanov <xemul@virtuozzo.com>
Signed-off-by: 's avatarAndrei Vagin <avagin@virtuozzo.com>
parent e51960ea
src/rpc/rpc.pb.go
all: test
test: rpc
GOPATH=$(shell pwd):/usr/share/gocode go build -o test test
rpc:
mkdir -p src/rpc/
protoc --go_out=src/rpc/ --proto_path=../../images/ ../../images/rpc.proto
package criu
import (
"errors"
"fmt"
"github.com/golang/protobuf/proto"
"os"
"os/exec"
"rpc"
"strconv"
"syscall"
)
type Criu struct {
swrk_cmd *exec.Cmd
swrk_sk *os.File
}
func MakeCriu() *Criu {
return &Criu{}
}
func (c *Criu) Prepare() error {
fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_SEQPACKET, 0)
if err != nil {
return err
}
cln := os.NewFile(uintptr(fds[0]), "criu-xprt-cln")
syscall.CloseOnExec(fds[0])
srv := os.NewFile(uintptr(fds[1]), "criu-xprt-srv")
defer srv.Close()
args := []string{"swrk", strconv.Itoa(fds[1])}
cmd := exec.Command("criu", args...)
err = cmd.Start()
if err != nil {
cln.Close()
return err
}
c.swrk_cmd = cmd
c.swrk_sk = cln
return nil
}
func (c *Criu) Cleanup() {
if c.swrk_cmd != nil {
c.swrk_sk.Close()
c.swrk_sk = nil
c.swrk_cmd.Wait()
c.swrk_cmd = nil
}
}
func (c *Criu) sendAndRecv(req_b []byte) ([]byte, int, error) {
cln := c.swrk_sk
_, err := cln.Write(req_b)
if err != nil {
return nil, 0, err
}
resp_b := make([]byte, 2*4096)
n, err := cln.Read(resp_b)
if err != nil {
return nil, 0, err
}
return resp_b, n, nil
}
func (c *Criu) doSwrk(req_type rpc.CriuReqType, opts *rpc.CriuOpts, nfy CriuNotify) error {
req := rpc.CriuReq{
Type: &req_type,
Opts: opts,
}
if nfy != nil {
opts.NotifyScripts = proto.Bool(true)
}
if c.swrk_cmd == nil {
err := c.Prepare()
if err != nil {
return err
}
defer c.Cleanup()
}
for {
req_b, err := proto.Marshal(&req)
if err != nil {
return err
}
resp_b, resp_s, err := c.sendAndRecv(req_b)
if err != nil {
return err
}
resp := &rpc.CriuResp{}
err = proto.Unmarshal(resp_b[:resp_s], resp)
if err != nil {
return err
}
if !resp.GetSuccess() {
return fmt.Errorf("operation failed (msg:%s err:%d)",
resp.GetCrErrmsg(), resp.GetCrErrno())
}
resp_type := resp.GetType()
if resp_type == req_type {
break
}
if resp_type != rpc.CriuReqType_NOTIFY {
return errors.New("unexpected responce")
}
if nfy == nil {
return errors.New("unexpected notify")
}
notify := resp.GetNotify()
switch notify.GetScript() {
case "pre-dump":
err = nfy.PreDump()
case "post-dump":
err = nfy.PostDump()
case "pre-restore":
err = nfy.PreRestore()
case "post-restore":
err = nfy.PostRestore(notify.GetPid())
case "network-lock":
err = nfy.NetworkLock()
case "network-unlock":
err = nfy.NetworkUnlock()
case "setup-namespaces":
err = nfy.SetupNamespaces(notify.GetPid())
case "post-setup-namespaces":
err = nfy.PostSetupNamespaces()
case "post-resume":
err = nfy.PostResume()
default:
err = nil
}
if err != nil {
return err
}
req = rpc.CriuReq{
Type: &resp_type,
NotifySuccess: proto.Bool(true),
}
}
return nil
}
func (c *Criu) Dump(opts rpc.CriuOpts, nfy CriuNotify) error {
return c.doSwrk(rpc.CriuReqType_DUMP, &opts, nfy)
}
func (c *Criu) Restore(opts rpc.CriuOpts, nfy CriuNotify) error {
return c.doSwrk(rpc.CriuReqType_RESTORE, &opts, nfy)
}
func (c *Criu) PreDump(opts rpc.CriuOpts, nfy CriuNotify) error {
return c.doSwrk(rpc.CriuReqType_PRE_DUMP, &opts, nfy)
}
func (c *Criu) StartPageServer(opts rpc.CriuOpts) error {
return c.doSwrk(rpc.CriuReqType_PAGE_SERVER, &opts, nil)
}
package criu
type CriuNotify interface {
PreDump() error
PostDump() error
PreRestore() error
PostRestore(pid int32) error
NetworkLock() error
NetworkUnlock() error
SetupNamespaces(pid int32) error
PostSetupNamespaces() error
PostResume() error
}
type CriuNoNotify struct {
}
func (c CriuNoNotify) PreDump() error {
return nil
}
func (c CriuNoNotify) PostDump() error {
return nil
}
func (c CriuNoNotify) PreRestore() error {
return nil
}
func (c CriuNoNotify) PostRestore(pid int32) error {
return nil
}
func (c CriuNoNotify) NetworkLock() error {
return nil
}
func (c CriuNoNotify) NetworkUnlock() error {
return nil
}
func (c CriuNoNotify) SetupNamespaces(pid int32) error {
return nil
}
func (c CriuNoNotify) PostSetupNamespaces() error {
return nil
}
func (c CriuNoNotify) PostResume() error {
return nil
}
package main
import (
"criu"
"fmt"
"github.com/golang/protobuf/proto"
"os"
"rpc"
"strconv"
)
type TestNfy struct {
criu.CriuNoNotify
}
func (c TestNfy) PreDump() error {
fmt.Printf("TEST PRE DUMP\n")
return nil
}
func doDump(c *criu.Criu, pid_s string, img_dir string, pre bool, prev_img string) error {
fmt.Printf("Dumping\n")
pid, _ := strconv.Atoi(pid_s)
img, err := os.Open(img_dir)
if err != nil {
return fmt.Errorf("can't open image dir (%s)", err)
}
defer img.Close()
opts := rpc.CriuOpts{
Pid: proto.Int32(int32(pid)),
ImagesDirFd: proto.Int32(int32(img.Fd())),
LogLevel: proto.Int32(4),
LogFile: proto.String("dump.log"),
}
if prev_img != "" {
opts.ParentImg = proto.String(prev_img)
opts.TrackMem = proto.Bool(true)
}
if pre {
err = c.PreDump(opts, TestNfy{})
} else {
err = c.Dump(opts, TestNfy{})
}
if err != nil {
return fmt.Errorf("dump fail (%s)", err)
}
return nil
}
// Usage: test $act $pid $images_dir
func main() {
c := criu.MakeCriu()
act := os.Args[1]
switch act {
case "dump":
err := doDump(c, os.Args[2], os.Args[3], false, "")
if err != nil {
fmt.Print(err)
os.Exit(1)
}
case "dump2":
err := c.Prepare()
if err != nil {
fmt.Print(err)
os.Exit(1)
}
err = doDump(c, os.Args[2], os.Args[3]+"/pre", true, "")
if err != nil {
fmt.Printf("pre-dump failed")
fmt.Print(err)
os.Exit(1)
}
err = doDump(c, os.Args[2], os.Args[3], false, "./pre")
if err != nil {
fmt.Printf("dump failed")
fmt.Print(err)
os.Exit(1)
}
c.Cleanup()
case "restore":
fmt.Printf("Restoring\n")
img, err := os.Open(os.Args[2])
if err != nil {
fmt.Printf("can't open image dir")
os.Exit(1)
}
defer img.Close()
opts := rpc.CriuOpts{
ImagesDirFd: proto.Int32(int32(img.Fd())),
LogLevel: proto.Int32(4),
LogFile: proto.String("restore.log"),
}
err = c.Restore(opts, nil)
if err != nil {
fmt.Printf("Error:")
fmt.Print(err)
fmt.Printf("\n")
os.Exit(1)
}
default:
fmt.Printf("unknown action\n")
os.Exit(1)
}
fmt.Printf("Success\n")
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment