#!/usr/bin/python2 # -*- coding: utf-8 -*- """ Helpers for cgroup testing. @copyright: 2011 Red Hat Inc. @author: Lukas Doktor """ 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]