diff mbox

Client: Add cgroup testing v2

Message ID 1314747158-14101-1-git-send-email-lmr@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Lucas Meneghel Rodrigues Aug. 30, 2011, 11:32 p.m. UTC
From: root <root@dhcp-26-193.brq.redhat.com>

This patchset adds a cgroup client test module,
plus support libraries for doing cgroup testing:

cgroup.py:
* Structure for different cgroup subtests
* Contains basic "cgroup-memory" test

cgroup_common.py:
* Library for cgroup handling (intended to be
  used from kvm test in the future)
* Universal smoke_test for every module

cgroup_client.py:
* Application which is executed and controled
  using cgroups
* Contains smoke, memory, cpu and devices tests
  which were manually tested to break cgroup
  rules and will be used in the cgroup.py
  subtests

Changes from v1:
 * Coding style fixes
 * Use of new style classes
 * Drop the use of eval(), replacing with getattr()
 * Replace os.system with utils.system
 * More detailed logging of cgroup module init

Signed-off-by: Lukas Doktor <ldoktor@redhat.com>
---
 client/tests/cgroup/cgroup.py        |  421 ++++++++++++++++++++++++++++++++++
 client/tests/cgroup/cgroup_client.py |  132 +++++++++++
 client/tests/cgroup/cgroup_common.py |  379 ++++++++++++++++++++++++++++++
 client/tests/cgroup/control          |   12 +
 4 files changed, 944 insertions(+), 0 deletions(-)
 create mode 100755 client/tests/cgroup/cgroup.py
 create mode 100755 client/tests/cgroup/cgroup_client.py
 create mode 100755 client/tests/cgroup/cgroup_common.py
 create mode 100644 client/tests/cgroup/control
diff mbox

Patch

diff --git a/client/tests/cgroup/cgroup.py b/client/tests/cgroup/cgroup.py
new file mode 100755
index 0000000..2fd0b23
--- /dev/null
+++ b/client/tests/cgroup/cgroup.py
@@ -0,0 +1,421 @@ 
+import os, logging
+import time
+from tempfile import NamedTemporaryFile
+
+from autotest_lib.client.bin import test, utils
+from autotest_lib.client.common_lib import error
+from cgroup_common import Cgroup as CG
+from cgroup_common import CgroupModules
+
+class cgroup(test.test):
+    """
+    Tests the cgroup functionalities. It works by creating a process (which is
+    also a python application) that will try to use CPU and memory. We will
+    then verify whether the cgroups rules are obeyed.
+    """
+    version = 1
+    _client = ""
+    modules = CgroupModules()
+
+    def run_once(self):
+        """
+	    Try to access different resources which are restricted by cgroup.
+        """
+        logging.info('Starting cgroup testing')
+
+        err = ""
+        # Run available tests
+        for i in ['memory', 'cpuset']:
+            logging.info("---< 'test_%s' START >---", i)
+            try:
+                if not self.modules.get_pwd(i):
+                    raise error.TestFail("module not available/mounted")
+                t_function = getattr(self, "test_%s" % i)
+                t_function()
+                logging.info("---< 'test_%s' PASSED >---", i)
+            except AttributeError:
+                err += "%s, " % i
+                logging.error("test_%s: Test doesn't exist", i)
+                logging.info("---< 'test_%s' FAILED >---", i)
+            except Exception, inst:
+                err += "%s, " % i
+                logging.error("test_%s: %s", i, inst)
+                logging.info("---< 'test_%s' FAILED >---", i)
+
+        if err:
+            logging.error('Some subtests failed (%s)' % err[:-2])
+            raise error.TestFail('Some subtests failed (%s)' % err[:-2])
+
+
+    def setup(self):
+        """
+        Setup
+        """
+        logging.debug('Setting up cgroups modules')
+
+        self._client = os.path.join(self.bindir, "cgroup_client.py")
+
+        _modules = ['cpuset', 'ns', 'cpu', 'cpuacct', 'memory', 'devices',
+                    'freezer', 'net_cls', 'blkio']
+        if (self.modules.init(_modules) <= 0):
+            raise error.TestFail('Can\'t mount any cgroup modules')
+
+
+    def cleanup(self):
+        """
+        Unmount all cgroups and remove directories
+        """
+        logging.info('Cleanup')
+        self.modules.cleanup()
+
+
+    #############################
+    # TESTS
+    #############################
+    def test_memory(self):
+        """
+        Memory test
+        """
+        def cleanup(supress=False):
+            # cleanup
+            logging.debug("test_memory: Cleanup")
+            err = ""
+            if item.rm_cgroup(pwd):
+                err += "\nCan't remove cgroup directory"
+
+            utils.system("swapon -a")
+
+            if err:
+                if supress:
+                    logging.warn("Some parts of cleanup failed%s" % err)
+                else:
+                    raise error.TestFail("Some parts of cleanup failed%s" % err)
+
+        # Preparation
+        item = CG('memory', self._client)
+        if item.initialize(self.modules):
+            raise error.TestFail("cgroup init failed")
+
+        if item.smoke_test():
+            raise error.TestFail("smoke_test failed")
+
+        pwd = item.mk_cgroup()
+        if pwd == None:
+            raise error.TestFail("Can't create cgroup")
+
+        logging.debug("test_memory: Memory filling test")
+
+        f = open('/proc/meminfo','r')
+        mem = f.readline()
+        while not mem.startswith("MemFree"):
+            mem = f.readline()
+        # Use only 1G or max of the free memory
+        mem = min(int(mem.split()[1])/1024, 1024)
+        mem = max(mem, 100) # at least 100M
+        memsw_limit_bytes = item.get_property("memory.memsw.limit_in_bytes",
+                                              supress=True)
+        if memsw_limit_bytes is not None:
+            memsw = True
+            # Clear swap
+            utils.system("swapoff -a")
+            utils.system("swapon -a")
+            f.seek(0)
+            swap = f.readline()
+            while not swap.startswith("SwapTotal"):
+                swap = f.readline()
+            swap = int(swap.split()[1])/1024
+            if swap < mem / 2:
+                logging.error("Not enough swap memory to test 'memsw'")
+                memsw = False
+        else:
+            # Doesn't support swap + memory limitation, disable swap
+            logging.info("System does not support 'memsw'")
+            utils.system("swapoff -a")
+            memsw = False
+        outf = NamedTemporaryFile('w+', prefix="cgroup_client-",
+                                  dir="/tmp")
+        logging.debug("test_memory: Initializition passed")
+
+        ################################################
+        # Fill the memory without cgroup limitation
+        # Should pass
+        ################################################
+        logging.debug("test_memory: Memfill WO cgroup")
+        ps = item.test("memfill %d %s" % (mem, outf.name))
+        ps.stdin.write('\n')
+        i = 0
+        while ps.poll() == None:
+            if i > 60:
+                break
+            i += 1
+            time.sleep(1)
+        if i > 60:
+            ps.terminate()
+            raise error.TestFail("Memory filling failed (WO cgroup)")
+        outf.seek(0)
+        outf.flush()
+        out = outf.readlines()
+        if (len(out) < 2) or (ps.poll() != 0):
+            raise error.TestFail("Process failed (WO cgroup); output:\n%s"
+                                 "\nReturn: %d" % (out, ps.poll()))
+        if not out[-1].startswith("PASS"):
+            raise error.TestFail("Unsuccessful memory filling "
+                                 "(WO cgroup)")
+        logging.debug("test_memory: Memfill WO cgroup passed")
+
+        ################################################
+        # Fill the memory with 1/2 memory limit
+        # memsw: should swap out part of the process and pass
+        # WO memsw: should fail (SIGKILL)
+        ################################################
+        logging.debug("test_memory: Memfill mem only limit")
+        ps = item.test("memfill %d %s" % (mem, outf.name))
+        if item.set_cgroup(ps.pid, pwd):
+            raise error.TestFail("Could not set cgroup")
+        if item.set_prop("memory.limit_in_bytes", ("%dM" % (mem/2)), pwd):
+            raise error.TestFail("Could not set mem limit (mem)")
+        ps.stdin.write('\n')
+        i = 0
+        while ps.poll() == None:
+            if i > 120:
+                break
+            i += 1
+            time.sleep(1)
+        if i > 120:
+            ps.terminate()
+            raise error.TestFail("Memory filling failed (mem)")
+        outf.seek(0)
+        outf.flush()
+        out = outf.readlines()
+        if (len(out) < 2):
+            raise error.TestFail("Process failed (mem); output:\n%s"
+                          "\nReturn: %d" % (out, ps.poll()))
+        if memsw:
+            if not out[-1].startswith("PASS"):
+                logging.error("test_memory: cgroup_client.py returned %d; "
+                              "output:\n%s", ps.poll(), out)
+                raise error.TestFail("Unsuccessful memory filling (mem)")
+        else:
+            if out[-1].startswith("PASS"):
+                raise error.TestFail("Unexpected memory filling (mem)")
+            else:
+                filled = int(out[-2].split()[1][:-1])
+                if mem/2 > 1.5 * filled:
+                    logging.error("test_memory: Limit = %dM, Filled = %dM (+ "
+                                  "python overhead upto 1/3 (mem))", mem/2,
+                                  filled)
+                else:
+                    logging.debug("test_memory: Limit = %dM, Filled = %dM (+ "
+                                  "python overhead upto 1/3 (mem))", mem/2,
+                                  filled)
+        logging.debug("test_memory: Memfill mem only cgroup passed")
+
+        ################################################
+        # Fill the memory with 1/2 memory+swap limit
+        # Should fail
+        # (memory.limit_in_bytes have to be set prior to this test)
+        ################################################
+        if memsw:
+            logging.debug("test_memory: Memfill mem + swap limit")
+            ps = item.test("memfill %d %s" % (mem, outf.name))
+            if item.set_cgroup(ps.pid, pwd):
+                raise error.TestFail("Could not set cgroup (memsw)")
+            if item.set_prop("memory.memsw.limit_in_bytes", "%dM"%(mem/2), pwd):
+                raise error.TestFail("Could not set mem limit (memsw)")
+            ps.stdin.write('\n')
+            i = 0
+            while ps.poll() == None:
+                if i > 120:
+                    break
+                i += 1
+                time.sleep(1)
+            if i > 120:
+                ps.terminate()
+                raise error.TestFail("Memory filling failed (mem)")
+            outf.seek(0)
+            outf.flush()
+            out = outf.readlines()
+            if (len(out) < 2):
+                raise error.TestFail("Process failed (memsw); output:\n%s"
+                                     "\nReturn: %d" % (out, ps.poll()))
+            if out[-1].startswith("PASS"):
+                raise error.TestFail("Unexpected memory filling (memsw)",
+                              mem)
+            else:
+                filled = int(out[-2].split()[1][:-1])
+                if mem / 2 > 1.5 * filled:
+                    logging.error("test_memory: Limit = %dM, Filled = %dM (+ "
+                                  "python overhead upto 1/3 (memsw))", mem/2,
+                                  filled)
+                else:
+                    logging.debug("test_memory: Limit = %dM, Filled = %dM (+ "
+                                  "python overhead upto 1/3 (memsw))", mem/2,
+                                  filled)
+            logging.debug("test_memory: Memfill mem + swap cgroup passed")
+
+        ################################################
+        # CLEANUP
+        ################################################
+        cleanup()
+
+
+
+    def test_cpuset(self):
+        """
+        Cpuset test
+        1) Initiate CPU load on CPU0, than spread into CPU* - CPU0
+        """
+        class per_cpu_load:
+            """
+            Handles the per_cpu_load stats
+            self.values [cpus, cpu0, cpu1, ...]
+            """
+            def __init__(self):
+                """
+                Init
+                """
+                self.values = []
+                self.f = open('/proc/stat', 'r')
+                line = self.f.readline()
+                while line:
+                    if line.startswith('cpu'):
+                        self.values.append(int(line.split()[1]))
+                    else:
+                        break
+                    line = self.f.readline()
+
+            def reload(self):
+                """
+                Reload current values
+                """
+                self.values = self.get()
+
+            def get(self):
+                """
+                Get the current values
+                @return vals: array of current values [cpus, cpu0, cpu1..]
+                """
+                self.f.seek(0)
+                self.f.flush()
+                vals = []
+                for i in range(len(self.values)):
+                    vals.append(int(self.f.readline().split()[1]))
+                return vals
+
+            def tick(self):
+                """
+                Reload values and returns the load between the last tick/reload
+                @return vals: array of load between ticks/reloads
+                              values [cpus, cpu0, cpu1..]
+                """
+                vals = self.get()
+                ret = []
+                for i in range(len(self.values)):
+                    ret.append(vals[i] - self.values[i])
+                self.values = vals
+                return ret
+
+        def cleanup(supress=False):
+            # cleanup
+            logging.debug("test_cpuset: Cleanup")
+            err = ""
+            try:
+                for task in tasks:
+                    for i in range(10):
+                        task.terminate()
+                        if task.poll() != None:
+                            break
+                        time.sleep(1)
+                    if i >= 9:
+                        logging.error("test_cpuset: Subprocess didn't finish")
+            except Exception, inst:
+                err += "\nCan't terminate tasks: %s" % inst
+            if item.rm_cgroup(pwd):
+                err += "\nCan't remove cgroup direcotry"
+            if err:
+                if supress:
+                    logging.warn("Some parts of cleanup failed%s" % err)
+                else:
+                    raise error.TestFail("Some parts of cleanup failed%s" % err)
+
+        # Preparation
+        item = CG('cpuset', self._client)
+        if item.initialize(self.modules):
+            raise error.TestFail("cgroup init failed")
+
+        # FIXME: new cpuset cgroup doesn't have any mems and cpus assigned
+        # thus smoke_test won't work
+        #if item.smoke_test():
+        #    raise error.TestFail("smoke_test failed")
+
+        try:
+            # Available cpus: cpuset.cpus = "0-$CPUS\n"
+            no_cpus = int(item.get_prop("cpuset.cpus").split('-')[1]) + 1
+        except:
+            raise error.TestFail("Failed to get no_cpus or no_cpus = 1")
+
+        pwd = item.mk_cgroup()
+        if pwd == None:
+            raise error.TestFail("Can't create cgroup")
+        # FIXME: new cpuset cgroup doesn't have any mems and cpus assigned
+        try:
+            tmp = item.get_prop("cpuset.cpus")
+            item.set_property("cpuset.cpus", tmp, pwd)
+            tmp = item.get_prop("cpuset.mems")
+            item.set_property("cpuset.mems", tmp, pwd)
+        except:
+            cleanup(True)
+            raise error.TestFail("Failed to set cpus and mems of"
+                                 "a new cgroup")
+
+        ################################################
+        # Cpu allocation test
+        # Use cpu0 and verify, than all cpu* - cpu0 and verify
+        ################################################
+        logging.debug("test_cpuset: Cpu allocation test")
+
+        tasks = []
+        # Run no_cpus + 1 jobs
+        for i in range(no_cpus + 1):
+            tasks.append(item.test("cpu"))
+            if item.set_cgroup(tasks[i].pid, pwd):
+                cleanup(True)
+                raise error.TestFail("Failed to set cgroup")
+            tasks[i].stdin.write('\n')
+        stats = per_cpu_load()
+        # Use only the first CPU
+        item.set_property("cpuset.cpus", 0, pwd)
+        stats.reload()
+        time.sleep(10)
+        # [0] = all cpus
+        s1 = stats.tick()[1:]
+        s2 = s1[1:]
+        s1 = s1[0]
+        for _s in s2:
+            if s1 < _s:
+                cleanup(True)
+                raise error.TestFail("Unused processor had higher utilization\n"
+                                     "used cpu: %s, remaining cpus: %s"
+                                     % (s1, s2))
+
+        if no_cpus == 2:
+            item.set_property("cpuset.cpus", "1", pwd)
+        else:
+            item.set_property("cpuset.cpus", "1-%d"%(no_cpus-1), pwd)
+        stats.reload()
+        time.sleep(10)
+        s1 = stats.tick()[1:]
+        s2 = s1[0]
+        s1 = s1[1:]
+        for _s in s1:
+            if s2 > _s:
+                cleanup(True)
+                raise error.TestFail("Unused processor had higher utilization\n"
+                                     "used cpus: %s, remaining cpu: %s"
+                                     % (s1, s2))
+        logging.debug("test_cpuset: Cpu allocation test passed")
+
+        ################################################
+        # CLEANUP
+        ################################################
+        cleanup()
diff --git a/client/tests/cgroup/cgroup_client.py b/client/tests/cgroup/cgroup_client.py
new file mode 100755
index 0000000..63127f4
--- /dev/null
+++ b/client/tests/cgroup/cgroup_client.py
@@ -0,0 +1,132 @@ 
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+"""
+Interactive python script for testing cgroups. It will try to use system
+resources such as cpu, memory and device IO. The other cgroups test
+instrumentation will inspect whether the linux box behaved as it should.
+
+@copyright: 2011 Red Hat Inc.
+@author: Lukas Doktor <ldoktor@redhat.com>
+"""
+import array, sys, time, math, os
+from tempfile import mktemp
+
+def test_smoke(args):
+    """
+    SIGSTOP the process and after SIGCONT exits.
+    """
+    print "TEST: smoke"
+    print "TEST: wait for input"
+    raw_input()
+    print "PASS: smoke"
+
+
+def test_memfill(args):
+    """
+    SIGSTOP and after SIGCONT fills the memory up to size size.
+    """
+    size = 1024
+    f = sys.stdout
+    if args:
+        size = int(args[0])
+        if len(args) > 1:
+            f = open(args[1], 'w', 0)
+    print "TEST: memfill (%dM)" % size
+    print "Redirecting to: %s" % f.name
+    f.write("TEST: memfill (%dM)\n" % size)
+    f.write("TEST: wait for input\n")
+    raw_input()
+    mem = array.array('B')
+    buf = ""
+    for i in range(1024 * 1024):
+        buf += '\x00'
+    for i in range(size):
+        mem.fromstring(buf)
+        f.write("TEST: %dM\n" % i)
+        try:
+            f.flush()
+            os.fsync(f)
+        except:
+            pass
+    f.write("PASS: memfill (%dM)\n" % size)
+
+
+def test_cpu(args):
+    """
+    Stress the CPU.
+    """
+    print "TEST: cpu"
+    print "TEST: wait for input"
+    raw_input()
+    while True:
+        for i in range (1000, 10000):
+            math.factorial(i)
+
+
+def test_devices(args):
+    if args:
+        if args[0] == "write":
+            test_devices_write()
+        else:
+            test_devices_read()
+    else:
+        test_devices_read()
+
+
+def test_devices_read():
+    """
+    Inf read from /dev/zero
+    """
+    print "TEST: devices read"
+    print "TEST: wait for input"
+    raw_input()
+
+    dev = open("/dev/zero", 'r')
+    while True:
+        print "TEST: tick"
+        dev.flush()
+        dev.read(1024*1024)
+        time.sleep(1)
+
+
+def test_devices_write():
+    """
+    Inf write into /dev/null device
+    """
+    print "TEST: devices write"
+    print "TEST: wait for input"
+    raw_input()
+
+    dev = open("/dev/null", 'w')
+    buf = ""
+    for _ in range(1024*1024):
+        buf += '\x00'
+    while True:
+        print "TEST: tick"
+        dev.write(buf)
+        dev.flush()
+        time.sleep(1)
+
+
+def main():
+    """
+    Main (infinite) loop.
+    """
+    if len(sys.argv) < 2:
+        print "FAIL: Incorrect usage (%s)" % sys.argv
+        return -1
+    args = sys.argv[2:]
+    if sys.argv[1] == "smoke":
+        test_smoke(args)
+    elif sys.argv[1] == "memfill":
+        test_memfill(args)
+    elif sys.argv[1] == "cpu":
+        test_cpu(args)
+    elif sys.argv[1] == "devices":
+        test_devices(args)
+    else:
+        print "FAIL: No test specified (%s)" % sys.argv
+
+if __name__ == "__main__":
+    main()
+
diff --git a/client/tests/cgroup/cgroup_common.py b/client/tests/cgroup/cgroup_common.py
new file mode 100755
index 0000000..51e93f7
--- /dev/null
+++ b/client/tests/cgroup/cgroup_common.py
@@ -0,0 +1,379 @@ 
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+"""
+Helpers for cgroup testing.
+
+@copyright: 2011 Red Hat Inc.
+@author: Lukas Doktor <ldoktor@redhat.com>
+"""
+import os, logging, subprocess, time, shutil
+from tempfile import mkdtemp
+from autotest_lib.client.bin import utils
+from autotest_lib.client.common_lib import error
+
+
+class Cgroup(object):
+    """
+    Cgroup handling class.
+    """
+    def __init__(self, module, _client):
+        """
+        Constructor
+        @param module: Name of the cgroup module
+        @param _client: Test script pwd + name
+        """
+        self.module = module
+        self._client = _client
+        self.root = None
+
+
+    def initialize(self, modules):
+        """
+        Initializes object for use.
+
+        @param modules: Array of all available cgroup modules.
+        @return: 0 when PASSED.
+        """
+        self.root = modules.get_pwd(self.module)
+        if self.root:
+            return 0
+        else:
+            logging.error("cg.initialize(): Module %s not found", self.module)
+            return -1
+        return 0
+
+
+    def mk_cgroup(self, root=None):
+        """
+        Creates new temporary cgroup
+        @param root: where to create this cgroup (default: self.root)
+        @return: 0 when PASSED
+        """
+        try:
+            if root:
+                pwd = mkdtemp(prefix='cgroup-', dir=root) + '/'
+            else:
+                pwd = mkdtemp(prefix='cgroup-', dir=self.root) + '/'
+        except Exception, inst:
+            logging.error("cg.mk_cgroup(): %s" , inst)
+            return None
+        return pwd
+
+
+    def rm_cgroup(self, pwd, supress=False):
+        """
+        Removes cgroup.
+
+        @param pwd: cgroup directory.
+        @param supress: supress output.
+        @return: 0 when PASSED
+        """
+        try:
+            os.rmdir(pwd)
+        except Exception, inst:
+            if not supress:
+                logging.error("cg.rm_cgroup(): %s" , inst)
+            return -1
+        return 0
+
+
+    def test(self, cmd):
+        """
+        Executes cgroup_client.py with cmd parameter.
+
+        @param cmd: command to be executed
+        @return: subprocess.Popen() process
+        """
+        logging.debug("cg.test(): executing paralel process '%s'", cmd)
+        cmd = self._client + ' ' + cmd
+        process = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
+                                   stdout=subprocess.PIPE,
+                                   stderr=subprocess.PIPE, close_fds=True)
+        return process
+
+
+    def is_cgroup(self, pid, pwd):
+        """
+        Checks if the 'pid' process is in 'pwd' cgroup
+        @param pid: pid of the process
+        @param pwd: cgroup directory
+        @return: 0 when is 'pwd' member
+        """
+        if open(pwd + '/tasks').readlines().count("%d\n" % pid) > 0:
+            return 0
+        else:
+            return -1
+
+
+    def is_root_cgroup(self, pid):
+        """
+        Checks if the 'pid' process is in root cgroup (WO cgroup)
+        @param pid: pid of the process
+        @return: 0 when is 'root' member
+        """
+        return self.is_cgroup(pid, self.root)
+
+
+    def set_cgroup(self, pid, pwd):
+        """
+        Sets cgroup membership
+        @param pid: pid of the process
+        @param pwd: cgroup directory
+        @return: 0 when PASSED
+        """
+        try:
+            open(pwd+'/tasks', 'w').write(str(pid))
+        except Exception, inst:
+            logging.error("cg.set_cgroup(): %s" , inst)
+            return -1
+        if self.is_cgroup(pid, pwd):
+            logging.error("cg.set_cgroup(): Setting %d pid into %s cgroup "
+                          "failed", pid, pwd)
+            return -1
+        else:
+            return 0
+
+    def set_root_cgroup(self, pid):
+        """
+        Resets the cgroup membership (sets to root)
+        @param pid: pid of the process
+        @return: 0 when PASSED
+        """
+        return self.set_cgroup(pid, self.root)
+
+
+    def get_prop(self, prop, pwd=None, supress=False):
+        """
+        Gets one line of the property value
+        @param prop: property name (file)
+        @param pwd: cgroup directory
+        @param supress: supress the output
+        @return: String value or None when FAILED
+        """
+        tmp = self.get_property(prop, pwd, supress)
+        if tmp:
+            if tmp[0][-1] == '\n':
+                tmp[0] = tmp[0][:-1]
+            return tmp[0]
+        else:
+            return None
+
+
+    def get_property(self, prop, pwd=None, supress=False):
+        """
+        Gets the property value
+        @param prop: property name (file)
+        @param pwd: cgroup directory
+        @param supress: supress the output
+        @return: [] values or None when FAILED
+        """
+        if pwd == None:
+            pwd = self.root
+        try:
+            ret = open(pwd+prop, 'r').readlines()
+        except Exception, inst:
+            ret = None
+            if not supress:
+                logging.error("cg.get_property(): %s" , inst)
+        return ret
+
+
+    def set_prop(self, prop, value, pwd=None, check=True):
+        """
+        Sets the one-line property value concerning the K,M,G postfix
+        @param prop: property name (file)
+        @param value: desired value
+        @param pwd: cgroup directory
+        @param check: check the value after setup
+        @return: 0 when PASSED
+        """
+        _value = value
+        try:
+            value = str(value)
+            if value[-1] == '\n':
+                value = value[:-1]
+            if value[-1] == 'K':
+                value = int(value[:-1]) * 1024
+            elif value[-1] == 'M':
+                value = int(value[:-1]) * 1048576
+            elif value[-1] == 'G':
+                value = int(value[:-1]) * 1073741824
+        except:
+            logging.error("cg.set_prop() fallback into cg.set_property.")
+            value = _value
+        return self.set_property(prop, value, pwd, check)
+
+
+    def set_property(self, prop, value, pwd=None, check=True):
+        """
+        Sets the property value
+        @param prop: property name (file)
+        @param value: desired value
+        @param pwd: cgroup directory
+        @param check: check the value after setup
+        @return: 0 when PASSED
+        """
+        value = str(value)
+        if pwd == None:
+            pwd = self.root
+        try:
+            open(pwd+prop, 'w').write(value)
+        except Exception, inst:
+            logging.error("cg.set_property(): %s" , inst)
+            return -1
+        if check:
+            # Get the first line - '\n'
+            _value = self.get_property(prop, pwd)[0][:-1]
+            if value != _value:
+                logging.error("cg.set_property(): Setting failed: desired = %s,"
+                              " real value = %s", value, _value)
+                return -1
+        return 0
+
+
+    def smoke_test(self):
+        """
+        Smoke test
+        Module independent basic tests
+        """
+        part = 0
+        pwd = self.mk_cgroup()
+        if pwd == None:
+            logging.error("cg.smoke_test[%d]: Can't create cgroup", part)
+            return -1
+
+        part += 1
+        ps = self.test("smoke")
+        if ps == None:
+            logging.error("cg.smoke_test[%d]: Couldn't create process", part)
+            return -1
+
+        part += 1
+        if (ps.poll() != None):
+            logging.error("cg.smoke_test[%d]: Process died unexpectidly", part)
+            return -1
+
+        # New process should be a root member
+        part += 1
+        if self.is_root_cgroup(ps.pid):
+            logging.error("cg.smoke_test[%d]: Process is not a root member",
+                          part)
+            return -1
+
+        # Change the cgroup
+        part += 1
+        if self.set_cgroup(ps.pid, pwd):
+            logging.error("cg.smoke_test[%d]: Could not set cgroup", part)
+            return -1
+
+        # Try to remove used cgroup
+        part += 1
+        if self.rm_cgroup(pwd, supress=True) == 0:
+            logging.error("cg.smoke_test[%d]: Unexpected successful deletion of"
+                          " the used cgroup", part)
+            return -1
+
+        # Return the process into the root cgroup
+        part += 1
+        if self.set_root_cgroup(ps.pid):
+            logging.error("cg.smoke_test[%d]: Could not return the root cgroup "
+                          "membership", part)
+            return -1
+
+        # It should be safe to remove the cgroup now
+        part += 1
+        if self.rm_cgroup(pwd):
+            logging.error("cg.smoke_test[%d]: Can't remove cgroup directory",
+                          part)
+            return -1
+
+        # Finish the process
+        part += 1
+        ps.stdin.write('\n')
+        time.sleep(2)
+        if (ps.poll() == None):
+            logging.error("cg.smoke_test[%d]: Process is not finished", part)
+            return -1
+
+        return 0
+
+
+class CgroupModules(object):
+    """
+    Handles the list of different cgroup filesystems.
+    """
+    def __init__(self):
+        self.modules = []
+        self.modules.append([])
+        self.modules.append([])
+        self.modules.append([])
+        self.mountdir = mkdtemp(prefix='cgroup-') + '/'
+
+
+    def init(self, _modules):
+        """
+        Checks the mounted modules and if necessary mounts them into tmp
+            mountdir.
+        @param _modules: Desired modules.
+        @return: Number of initialized modules.
+        """
+        logging.debug("Desired cgroup modules: %s", _modules)
+        mounts = []
+        fp = open('/proc/mounts', 'r')
+        line = fp.readline().split()
+        while line:
+            if line[2] == 'cgroup':
+                mounts.append(line)
+            line = fp.readline().split()
+        fp.close()
+
+        for module in _modules:
+            # Is it already mounted?
+            i = False
+            for mount in mounts:
+                if mount[3].find(module) != -1:
+                    self.modules[0].append(module)
+                    self.modules[1].append(mount[1] + '/')
+                    self.modules[2].append(False)
+                    i = True
+                    break
+            if not i:
+                # Not yet mounted
+                os.mkdir(self.mountdir + module)
+                cmd = ('mount -t cgroup -o %s %s %s' %
+                       (module, module, self.mountdir + module))
+                try:
+                    utils.run(cmd)
+                    self.modules[0].append(module)
+                    self.modules[1].append(self.mountdir + module)
+                    self.modules[2].append(True)
+                except error.CmdError:
+                    logging.info("Cgroup module '%s' not available", module)
+
+        logging.debug("Initialized cgroup modules: %s", self.modules[0])
+        return len(self.modules[0])
+
+
+    def cleanup(self):
+        """
+        Unmount all cgroups and remove the mountdir.
+        """
+        for i in range(len(self.modules[0])):
+            if self.modules[2][i]:
+                utils.system('umount %s -l' % self.modules[1][i],
+                             ignore_status=True)
+        shutil.rmtree(self.mountdir)
+
+
+    def get_pwd(self, module):
+        """
+        Returns the mount directory of 'module'
+        @param module: desired module (memory, ...)
+        @return: mount directory of 'module' or None
+        """
+        try:
+            i = self.modules[0].index(module)
+        except Exception, inst:
+            logging.error("module %s not found: %s", module, inst)
+            return None
+        return self.modules[1][i]
diff --git a/client/tests/cgroup/control b/client/tests/cgroup/control
new file mode 100644
index 0000000..86aec06
--- /dev/null
+++ b/client/tests/cgroup/control
@@ -0,0 +1,12 @@ 
+AUTHOR = "Lukas Doktor <ldoktor@redhat.com>"
+NAME = "Cgroup"
+TIME = "SHORT"
+TEST_CATEGORY = "Functional"
+TEST_CLASS = "General"
+TEST_TYPE = "client"
+
+DOC = """
+This test checks basic functionality of cgroups
+"""
+
+job.run_test('cgroup')