From 273b7853c8e0a95ee1980b61260419f28aa08824 Mon Sep 17 00:00:00 2001 From: Adrian Reber Date: Tue, 14 Mar 2017 20:21:58 +0000 Subject: [PATCH] checkpoint: check if system supports pre-dumping Instead of relying on version numbers it is possible to check if CRIU actually supports certain features. This introduces an initial implementation to check if CRIU and the underlying kernel actually support dirty memory tracking for memory pre-dumping. Upstream CRIU also supports the lazy-page migration feature check and additional feature checks can be included in CRIU to reduce the version number parsing. There are also certain CRIU features which depend on one side on the CRIU version but also require certain kernel versions to actually work. CRIU knows if it can do certain things on the kernel it is running on and using the feature check RPC interface makes it easier for runc to decide if the criu+kernel combination will support that feature. Feature checking was introduced with CRIU 1.8. Running with older CRIU versions will ignore the feature check functionality and behave just like it used to. v2: - Do not use reflection to compare requested and responded features. Checking which feature is available is now hardcoded and needs to be adapted for every new feature check. The code is now much more readable and simpler. v3: - Move the variable criuFeat out of the linuxContainer struct, as it is not container specific. Now it is a global variable. Signed-off-by: Adrian Reber --- libcontainer/container_linux.go | 85 +++++++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 9 deletions(-) diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go index faecc468..150dc564 100644 --- a/libcontainer/container_linux.go +++ b/libcontainer/container_linux.go @@ -535,6 +535,56 @@ func (c *linuxContainer) NotifyMemoryPressure(level PressureLevel) (<-chan struc return notifyMemoryPressure(c.cgroupManager.GetPaths(), level) } +var criuFeatures *criurpc.CriuFeatures + +func (c *linuxContainer) checkCriuFeatures(criuOpts *CriuOpts, rpcOpts *criurpc.CriuOpts, criuFeat *criurpc.CriuFeatures) error { + + var t criurpc.CriuReqType + t = criurpc.CriuReqType_FEATURE_CHECK + + if err := c.checkCriuVersion("1.8"); 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. + // As all automated PR testing is done using CRIU 1.7 this + // code will not be tested by automated PR testing. + return nil + } + + // make sure the features we are looking for are really not from + // some previous check + criuFeatures = nil + + req := &criurpc.CriuReq{ + Type: &t, + // Theoretically this should not be necessary but CRIU + // segfaults if Opts is empty. + // Fixed in CRIU 2.12 + Opts: rpcOpts, + Features: criuFeat, + } + + err := c.criuSwrk(nil, req, criuOpts, false) + if err != nil { + logrus.Debugf("%s", err) + return fmt.Errorf("CRIU feature check failed") + } + + logrus.Debugf("Feature check says: %s", criuFeatures) + missingFeatures := false + + if *criuFeat.MemTrack && !*criuFeatures.MemTrack { + missingFeatures = true + logrus.Debugf("CRIU does not support MemTrack") + } + + if missingFeatures { + return fmt.Errorf("CRIU is missing features") + } + + 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 @@ -717,6 +767,14 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error { var t criurpc.CriuReqType if criuOpts.PreDump { + feat := criurpc.CriuFeatures{ + MemTrack: proto.Bool(true), + } + + if err := c.checkCriuFeatures(criuOpts, &rpcOpts, &feat); err != nil { + return err + } + t = criurpc.CriuReqType_PRE_DUMP } else { t = criurpc.CriuReqType_DUMP @@ -1018,16 +1076,21 @@ func (c *linuxContainer) criuSwrk(process *Process, req *criurpc.CriuReq, opts * } logrus.Debugf("Using CRIU in %s mode", req.GetType().String()) - val := reflect.ValueOf(req.GetOpts()) - v := reflect.Indirect(val) - for i := 0; i < v.NumField(); i++ { - st := v.Type() - name := st.Field(i).Name - if strings.HasPrefix(name, "XXX_") { - continue + // 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 { + val := reflect.ValueOf(req.GetOpts()) + v := reflect.Indirect(val) + for i := 0; i < v.NumField(); i++ { + st := v.Type() + name := st.Field(i).Name + if strings.HasPrefix(name, "XXX_") { + continue + } + value := val.MethodByName("Get" + name).Call([]reflect.Value{}) + logrus.Debugf("CRIU option %s with value %v", name, value[0]) } - value := val.MethodByName("Get" + name).Call([]reflect.Value{}) - logrus.Debugf("CRIU option %s with value %v", name, value[0]) } data, err := proto.Marshal(req) if err != nil { @@ -1063,6 +1126,10 @@ func (c *linuxContainer) criuSwrk(process *Process, req *criurpc.CriuReq, opts * t := resp.GetType() switch { + case t == criurpc.CriuReqType_FEATURE_CHECK: + logrus.Debugf("Feature check says: %s", resp) + criuFeatures = resp.GetFeatures() + break case t == criurpc.CriuReqType_NOTIFY: if err := c.criuNotifications(resp, process, opts, extFds); err != nil { return err