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

Use CRIU VERSION RPC if available #1535

Merged
merged 5 commits into from
Aug 5, 2017
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
138 changes: 109 additions & 29 deletions libcontainer/container_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,8 @@ func (c *linuxContainer) checkCriuFeatures(criuOpts *CriuOpts, rpcOpts *criurpc.
var t criurpc.CriuReqType
t = criurpc.CriuReqType_FEATURE_CHECK

if err := c.checkCriuVersion("1.8"); err != nil {
// criu 1.8 => 10800
if err := c.checkCriuVersion(10800); err != nil {
// Feature checking was introduced with CRIU 1.8.
// Ignore the feature check if an older CRIU version is used
// and just act as before.
Expand Down Expand Up @@ -589,22 +590,12 @@ func (c *linuxContainer) checkCriuFeatures(criuOpts *CriuOpts, rpcOpts *criurpc.
return nil
}

// checkCriuVersion checks Criu version greater than or equal to minVersion
func (c *linuxContainer) checkCriuVersion(minVersion string) error {
var x, y, z, versionReq int

_, err := fmt.Sscanf(minVersion, "%d.%d.%d\n", &x, &y, &z) // 1.5.2
if err != nil {
_, err = fmt.Sscanf(minVersion, "Version: %d.%d\n", &x, &y) // 1.6
if err != nil {
return fmt.Errorf("Unable to parse the CRIU min version: %s", minVersion)
}
}
versionReq = x*10000 + y*100 + z
func parseCriuVersion(path string) (int, error) {
var x, y, z int

out, err := exec.Command(c.criuPath, "-V").Output()
out, err := exec.Command(path, "-V").Output()
if err != nil {
return fmt.Errorf("Unable to execute CRIU command: %s", c.criuPath)
return 0, fmt.Errorf("Unable to execute CRIU command: %s", path)
}

x = 0
Expand All @@ -616,7 +607,7 @@ func (c *linuxContainer) checkCriuVersion(minVersion string) error {
if sp := strings.Index(string(out), "GitID"); sp > 0 {
version = string(out)[sp:ep]
} else {
return fmt.Errorf("Unable to parse the CRIU version: %s", c.criuPath)
return 0, fmt.Errorf("Unable to parse the CRIU version: %s", path)
}

n, err := fmt.Sscanf(string(version), "GitID: v%d.%d.%d", &x, &y, &z) // 1.5.2
Expand All @@ -627,7 +618,7 @@ func (c *linuxContainer) checkCriuVersion(minVersion string) error {
z++
}
if n < 2 || err != nil {
return fmt.Errorf("Unable to parse the CRIU version: %s %d %s", version, n, err)
return 0, fmt.Errorf("Unable to parse the CRIU version: %s %d %s", version, n, err)
}
} else {
// criu release version format
Expand All @@ -636,19 +627,81 @@ func (c *linuxContainer) checkCriuVersion(minVersion string) error {
n, err = fmt.Sscanf(string(out), "Version: %d.%d\n", &x, &y) // 1.6
}
if n < 2 || err != nil {
return fmt.Errorf("Unable to parse the CRIU version: %s %d %s", out, n, err)
return 0, fmt.Errorf("Unable to parse the CRIU version: %s %d %s", out, n, err)
}
}

c.criuVersion = x*10000 + y*100 + z
return x*10000 + y*100 + z, nil
}

if c.criuVersion < versionReq {
return fmt.Errorf("CRIU version %d must be %d or higher", c.criuVersion, versionReq)
func compareCriuVersion(criuVersion int, minVersion int) error {
// simple function to perform the actual version compare
if criuVersion < minVersion {
return fmt.Errorf("CRIU version %d must be %d or higher", criuVersion, minVersion)
}

return nil
}

// This is used to store the result of criu version RPC
var criuVersionRPC *criurpc.CriuVersion

// checkCriuVersion checks Criu version greater than or equal to minVersion
func (c *linuxContainer) checkCriuVersion(minVersion int) error {

// If the version of criu has already been determined there is no need
// to ask criu for the version again. Use the value from c.criuVersion.
if c.criuVersion != 0 {
return compareCriuVersion(c.criuVersion, minVersion)
}

// First try if this version of CRIU support the version RPC.
// The CRIU version RPC was introduced with CRIU 3.0.

// First, reset the variable for the RPC answer to nil
criuVersionRPC = nil

var t criurpc.CriuReqType
t = criurpc.CriuReqType_VERSION
req := &criurpc.CriuReq{
Type: &t,
}

err := c.criuSwrk(nil, req, nil, false)
if err != nil {
return fmt.Errorf("CRIU version check failed: %s", err)
}

if criuVersionRPC != nil {
logrus.Debugf("CRIU version: %s", criuVersionRPC)
// major and minor are always set
c.criuVersion = int(*criuVersionRPC.Major) * 10000
c.criuVersion += int(*criuVersionRPC.Minor) * 100
if criuVersionRPC.Sublevel != nil {
c.criuVersion += int(*criuVersionRPC.Sublevel)
}
if criuVersionRPC.Gitid != nil {
// runc's convention is that a CRIU git release is
// always the same as increasing the minor by 1
c.criuVersion -= (c.criuVersion % 100)
c.criuVersion += 100
}
return compareCriuVersion(c.criuVersion, minVersion)
}

// This is CRIU without the version RPC and therefore
// older than 3.0. Parsing the output is required.

// This can be remove once runc does not work with criu older than 3.0

c.criuVersion, err = parseCriuVersion(c.criuPath)
if err != nil {
return err
}

return compareCriuVersion(c.criuVersion, minVersion)
}

const descriptorsFilename = "descriptors.json"

func (c *linuxContainer) addCriuDumpMount(req *criurpc.CriuReq, m *configs.Mount) {
Expand Down Expand Up @@ -698,7 +751,8 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
return fmt.Errorf("cannot checkpoint a rootless container")
}

if err := c.checkCriuVersion("1.5.2"); err != nil {
// criu 1.5.2 => 10502
if err := c.checkCriuVersion(10502); err != nil {
return err
}

Expand Down Expand Up @@ -771,7 +825,8 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {

// append optional manage cgroups mode
if criuOpts.ManageCgroupsMode != 0 {
if err := c.checkCriuVersion("1.7"); err != nil {
// criu 1.7 => 10700
if err := c.checkCriuVersion(10700); err != nil {
return err
}
mode := criurpc.CriuCgMode(criuOpts.ManageCgroupsMode)
Expand Down Expand Up @@ -885,7 +940,8 @@ func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
return fmt.Errorf("cannot restore a rootless container")
}

if err := c.checkCriuVersion("1.5.2"); err != nil {
// criu 1.5.2 => 10502
if err := c.checkCriuVersion(10502); err != nil {
return err
}
if criuOpts.WorkDirectory == "" {
Expand Down Expand Up @@ -981,7 +1037,8 @@ func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {

// append optional manage cgroups mode
if criuOpts.ManageCgroupsMode != 0 {
if err := c.checkCriuVersion("1.7"); err != nil {
// criu 1.7 => 10700
if err := c.checkCriuVersion(10700); err != nil {
return err
}
mode := criurpc.CriuCgMode(criuOpts.ManageCgroupsMode)
Expand Down Expand Up @@ -1043,7 +1100,14 @@ func (c *linuxContainer) criuSwrk(process *Process, req *criurpc.CriuReq, opts *
return err
}

logPath := filepath.Join(opts.WorkDirectory, req.GetOpts().GetLogFile())
var logPath string
if opts != nil {
logPath = filepath.Join(opts.WorkDirectory, req.GetOpts().GetLogFile())
} else {
// For the VERSION RPC 'opts' is set to 'nil' and therefore
// opts.WorkDirectory does not exist. Set logPath to "".
logPath = ""
}
criuClient := os.NewFile(uintptr(fds[0]), "criu-transport-client")
criuClientFileCon, err := net.FileConn(criuClient)
criuClient.Close()
Expand All @@ -1058,7 +1122,11 @@ func (c *linuxContainer) criuSwrk(process *Process, req *criurpc.CriuReq, opts *
defer criuServer.Close()

args := []string{"swrk", "3"}
logrus.Debugf("Using CRIU %d at: %s", c.criuVersion, c.criuPath)
if c.criuVersion != 0 {
// If the CRIU Version is still '0' then this is probably
// the initial CRIU run to detect the version. Skip it.
logrus.Debugf("Using CRIU %d at: %s", c.criuVersion, c.criuPath)
}
logrus.Debugf("Using CRIU with following args: %s", args)
cmd := exec.Command(c.criuPath, args...)
if process != nil {
Expand Down Expand Up @@ -1099,8 +1167,11 @@ func (c *linuxContainer) criuSwrk(process *Process, req *criurpc.CriuReq, opts *
logrus.Debugf("Using CRIU in %s mode", req.GetType().String())
// In the case of criurpc.CriuReqType_FEATURE_CHECK req.GetOpts()
// should be empty. For older CRIU versions it still will be
// available but empty.
if req.GetType() != criurpc.CriuReqType_FEATURE_CHECK {
// available but empty. criurpc.CriuReqType_VERSION actually
// has no req.GetOpts().
if !(req.GetType() == criurpc.CriuReqType_FEATURE_CHECK ||
req.GetType() == criurpc.CriuReqType_VERSION) {

val := reflect.ValueOf(req.GetOpts())
v := reflect.Indirect(val)
for i := 0; i < v.NumField(); i++ {
Expand Down Expand Up @@ -1143,11 +1214,20 @@ func (c *linuxContainer) criuSwrk(process *Process, req *criurpc.CriuReq, opts *
}
if !resp.GetSuccess() {
typeString := req.GetType().String()
if typeString == "VERSION" {
// If the VERSION RPC fails this probably means that the CRIU
// version is too old for this RPC. Just return 'nil'.
return nil
}
return fmt.Errorf("criu failed: type %s errno %d\nlog file: %s", typeString, resp.GetCrErrno(), logPath)
}

t := resp.GetType()
switch {
case t == criurpc.CriuReqType_VERSION:
logrus.Debugf("CRIU version: %s", resp)
criuVersionRPC = resp.GetVersion()
break
case t == criurpc.CriuReqType_FEATURE_CHECK:
logrus.Debugf("Feature check says: %s", resp)
criuFeatures = resp.GetFeatures()
Expand Down
Loading