#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Helpers for cgroup testing.
:copyright: 2011 Red Hat Inc.
:author: Lukas Doktor <ldoktor@redhat.com>
"""
import logging
import os
import shutil
import subprocess
import time
import re
import random
import commands
from tempfile import mkdtemp
from autotest.client import utils
from autotest.client.shared import error
from virttest import utils_misc
import service
[docs]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
self.cgroups = []
def __del__(self):
"""
Destructor
"""
self.cgroups.sort(reverse=True)
for pwd in self.cgroups[:]:
for task in self.get_property("tasks", pwd):
if task:
self.set_root_cgroup(int(task))
self.rm_cgroup(pwd)
[docs] def initialize(self, modules):
"""
Initializes object for use.
:param modules: Array of all available cgroup modules.
"""
self.root = modules.get_pwd(self.module)
if not self.root:
raise error.TestError("cg.initialize(): Module %s not found"
% self.module)
def __get_cgroup_pwd(self, cgroup):
"""
Get cgroup's full path
:param cgroup: cgroup name
:return: cgroup's full path
"""
if not isinstance(cgroup, str):
raise error.TestError("cgroup type isn't string!")
return os.path.join(self.root, cgroup) + '/'
[docs] def get_cgroup_name(self, pwd=None):
"""
Get cgroup's name
:param pwd: cgroup name
:return: cgroup's name
"""
if pwd is None:
# root cgroup
return None
if isinstance(pwd, int):
pwd = self.cgroups[pwd]
# self.root is "/cgroup/blkio," not "/cgroup/blkio/"
# cgroup is "/cgroup/blkio/test" or "/cgroup/blkio/test/test"
# expected cgroup name is test or test/test
if pwd.startswith(self.root + '/'):
return pwd[len(self.root) + 1: -1]
return None
[docs] def get_cgroup_index(self, cgroup):
"""
Get cgroup's index in cgroups
:param cgroup: cgroup name
:return: index of cgroup
"""
try:
if self.__get_cgroup_pwd(cgroup) not in self.cgroups:
raise error.TestFail("%s not exists!" % cgroup)
cgroup_pwd = self.__get_cgroup_pwd(cgroup)
return self.cgroups.index(cgroup_pwd)
except error.CmdError:
raise error.TestFail("Find index failed!")
[docs] def mk_cgroup_cgcreate(self, pwd=None, cgroup=None):
"""
Make a cgroup by executing the cgcreate command
:params: cgroup: name of the cgroup to be created
:return: last cgroup index
"""
try:
parent_cgroup = self.get_cgroup_name(pwd)
if cgroup is None:
range = "abcdefghijklmnopqrstuvwxyz0123456789"
sub_cgroup = "cgroup-" + "".join(random.sample(range +
range.upper(), 6))
else:
sub_cgroup = cgroup
if parent_cgroup is None:
cgroup = sub_cgroup
else:
# Parent cgroup:test. Created cgroup:test1.
# Whole cgroup name is "test/test1"
cgroup = os.path.join(parent_cgroup, sub_cgroup)
if self.__get_cgroup_pwd(cgroup) in self.cgroups:
raise error.TestFail("%s exists!" % cgroup)
cgcreate_cmd = "cgcreate -g %s:%s" % (self.module, cgroup)
utils.run(cgcreate_cmd, ignore_status=False)
pwd = self.__get_cgroup_pwd(cgroup)
self.cgroups.append(pwd)
return len(self.cgroups) - 1
except error.CmdError:
raise error.TestFail("Make cgroup by cgcreate failed!")
[docs] def mk_cgroup(self, pwd=None, cgroup=None):
"""
Creates new temporary cgroup
:param pwd: where to create this cgroup (default: self.root)
:param cgroup: desired cgroup name
:return: last cgroup index
"""
if pwd is None:
pwd = self.root
if isinstance(pwd, int):
pwd = self.cgroups[pwd]
try:
if cgroup and self.__get_cgroup_pwd(cgroup) in self.cgroups:
raise error.TestFail("%s exists!" % cgroup)
if not cgroup:
pwd = mkdtemp(prefix='cgroup-', dir=pwd) + '/'
else:
pwd = os.path.join(pwd, cgroup) + '/'
if not os.path.exists(pwd):
os.mkdir(pwd)
except Exception, inst:
raise error.TestError("cg.mk_cgroup(): %s" % inst)
self.cgroups.append(pwd)
return len(self.cgroups) - 1
[docs] def cgexec(self, cgroup, cmd, args=""):
"""
Execute command in desired cgroup
:param cgroup: Desired cgroup
:param cmd: Executed command
:param args: Executed command's parameters
"""
try:
cgexec_cmd = ("cgexec -g %s:%s %s %s" %
(self.module, cgroup, cmd, args))
status, output = commands.getstatusoutput(cgexec_cmd)
return status, output
except error.CmdError, detail:
raise error.TestFail("Execute %s in cgroup failed!\n%s" %
(cmd, detail))
[docs] def rm_cgroup(self, pwd):
"""
Removes cgroup.
:param pwd: cgroup directory.
"""
if isinstance(pwd, int):
pwd = self.cgroups[pwd]
try:
os.rmdir(pwd)
self.cgroups.remove(pwd)
except ValueError:
logging.warn("cg.rm_cgroup(): Removed cgroup which wasn't created"
"using this Cgroup")
except Exception, inst:
raise error.TestError("cg.rm_cgroup(): %s" % inst)
[docs] def get_all_cgroups(self):
"""
Get all sub cgroups in this controller
"""
lscgroup_cmd = "lscgroup %s:/" % self.module
result = utils.run(lscgroup_cmd, ignore_status=True)
if result.exit_status:
raise error.TestFail(result.stderr.strip())
cgroup_list = result.stdout.strip().splitlines()
# Remove root cgroup
cgroup_list = cgroup_list[1:]
self.root = get_cgroup_mountpoint(self.module)
sub_cgroup_list = []
for item in cgroup_list:
sub_cg = item.split(":/")[-1]
sub_cg_path = os.path.join(self.root, sub_cg) + '/'
sub_cgroup_list.append(sub_cg_path)
self.cgroups = sub_cgroup_list
return self.cgroups
[docs] def cgdelete_all_cgroups(self):
"""
Delete all cgroups in the module
"""
try:
for cgroup_pwd in self.cgroups:
# Ignore sub cgroup
cgroup = self.get_cgroup_name(cgroup_pwd)
if cgroup.count("/") > 0:
continue
self.cgdelete_cgroup(cgroup, True)
except error.CmdError:
raise error.TestFail("cgdelete all cgroups in %s failed!"
% self.module)
[docs] def cgdelete_cgroup(self, cgroup, recursive=False):
"""
Delete desired cgroup.
:params cgroup: desired cgroup
:params force: If true, sub cgroup can be deleted with parent cgroup
"""
try:
cgroup_pwd = self.__get_cgroup_pwd(cgroup)
if cgroup_pwd not in self.cgroups:
raise error.TestError("%s doesn't exist!" % cgroup)
cmd = "cgdelete %s:%s" % (self.module, cgroup)
if recursive:
cmd += " -r"
utils.run(cmd, ignore_status=False)
self.cgroups.remove(cgroup_pwd)
except error.CmdError, detail:
raise error.TestFail("cgdelete %s failed!\n%s" %
(cgroup, detail))
[docs] def cgclassify_cgroup(self, pid, cgroup):
"""
Classify pid into cgroup
:param pid: pid of the process
:param cgroup: cgroup name
"""
try:
cgroup_pwd = self.__get_cgroup_pwd(cgroup)
if cgroup_pwd not in self.cgroups:
raise error.TestError("%s doesn't exist!" % cgroup)
cgclassify_cmd = ("cgclassify -g %s:%s %d" %
(self.module, cgroup, pid))
utils.run(cgclassify_cmd, ignore_status=False)
except error.CmdError, detail:
raise error.TestFail("Classify process to tasks file failed!:%s" %
detail)
[docs] def get_pids(self, pwd=None):
"""
Get all pids in cgroup
:params: pwd: cgroup directory
:return: all pids(list)
"""
if pwd is None:
pwd = self.root
if isinstance(pwd, int):
pwd = self.cgroups[pwd]
try:
return [_.strip() for _ in open(os.path.join(pwd, 'tasks'), 'r')]
except Exception, inst:
raise error.TestError("cg.get_pids(): %s" % inst)
[docs] 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 parallel 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
[docs] 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 isinstance(pwd, int):
pwd = self.cgroups[pwd]
if open(os.path.join(pwd, 'tasks')).readlines().count("%d\n" % pid) > 0:
return 0
else:
return -1
[docs] 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)
[docs] def set_cgroup(self, pid, pwd=None):
"""
Sets cgroup membership
:param pid: pid of the process
:param pwd: cgroup directory
"""
if pwd is None:
pwd = self.root
if isinstance(pwd, int):
pwd = self.cgroups[pwd]
try:
open(os.path.join(pwd, 'tasks'), 'w').write(str(pid))
except Exception, inst:
raise error.TestError("cg.set_cgroup(): %s" % inst)
if self.is_cgroup(pid, pwd):
raise error.TestError("cg.set_cgroup(): Setting %d pid into %s "
"cgroup failed" % (pid, pwd))
[docs] 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)
[docs] def get_property(self, prop, pwd=None):
"""
Gets the property value
:param prop: property name (file)
:param pwd: cgroup directory
:return: [] values or None when FAILED
"""
if pwd is None:
pwd = self.root
if isinstance(pwd, int):
pwd = self.cgroups[pwd]
try:
# Remove tailing '\n' from each line
file_link = os.path.join(pwd, prop)
ret = [_[:-1].replace("\t", " ") for _ in open(file_link, 'r')]
if ret:
return ret
else:
return [""]
except Exception, inst:
raise error.TestError("cg.get_property(): %s" % inst)
[docs] def set_property_h(self, prop, value, pwd=None, check=True, checkprop=None):
"""
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 / override checking value
:param checkprop: override prop when checking the value
"""
_value = value
try:
value = str(value)
human = {'B': 1,
'K': 1024,
'M': 1048576,
'G': 1073741824,
'T': 1099511627776
}
if human.has_key(value[-1]):
value = int(value[:-1]) * human[value[-1]]
except Exception:
logging.warn("cg.set_prop() fallback into cg.set_property.")
value = _value
self.set_property(prop, value, pwd, check, checkprop)
[docs] def set_property(self, prop, value, pwd=None, check=True, checkprop=None):
"""
Sets the property value
:param prop: property name (file)
:param value: desired value
:param pwd: cgroup directory
:param check: check the value after setup / override checking value
:param checkprop: override prop when checking the value
"""
value = str(value)
if pwd is None:
pwd = self.root
if isinstance(pwd, int):
pwd = self.cgroups[pwd]
try:
open(os.path.join(pwd, prop), 'w').write(value)
except Exception, inst:
raise error.TestError("cg.set_property(): %s" % inst)
if check is not False:
if check is True:
check = value
if checkprop is None:
checkprop = prop
_values = self.get_property(checkprop, pwd)
# Sanitize non printable characters before check
check = " ".join(check.split())
if check not in _values:
raise error.TestError("cg.set_property(): Setting failed: "
"desired = %s, real values = %s"
% (repr(check), repr(_values)))
[docs] def cgset_property(self, prop, value, pwd=None, check=True, checkprop=None):
"""
Sets the property value by cgset command
:param prop: property name (file)
:param value: desired value
:param pwd: cgroup directory
:param check: check the value after setup / override checking value
:param checkprop: override prop when checking the value
"""
if pwd is None:
pwd = self.root
if isinstance(pwd, int):
pwd = self.cgroups[pwd]
try:
cgroup = self.get_cgroup_name(pwd)
cgset_cmd = "cgset -r %s='%s' %s" % (prop, value, cgroup)
utils.run(cgset_cmd, ignore_status=False)
except error.CmdError, detail:
raise error.TestFail("Modify %s failed!:\n%s" % (prop, detail))
if check is not False:
if check is True:
check = value
if checkprop is None:
checkprop = prop
_values = self.get_property(checkprop,
self.get_cgroup_index(cgroup))
# Sanitize non printable characters before check
check = " ".join(check.split())
if check not in _values:
raise error.TestError("cg.set_property(): Setting failed: "
"desired = %s, real values = %s"
% (repr(check), repr(_values)))
[docs] def smoke_test(self):
"""
Smoke test
Module independent basic tests
"""
pwd = self.mk_cgroup()
ps = self.test("smoke")
if ps is None:
raise error.TestError("cg.smoke_test: Couldn't create process")
if (ps.poll() is not None):
raise error.TestError("cg.smoke_test: Process died unexpectidly")
# New process should be a root member
if self.is_root_cgroup(ps.pid):
raise error.TestError(
"cg.smoke_test: Process is not a root member")
# Change the cgroup
self.set_cgroup(ps.pid, pwd)
# Try to remove used cgroup
try:
self.rm_cgroup(pwd)
except error.TestError:
pass
else:
raise error.TestError("cg.smoke_test: Unexpected successful"
" deletion of the used cgroup")
# Return the process into the root cgroup
self.set_root_cgroup(ps.pid)
# It should be safe to remove the cgroup now
self.rm_cgroup(pwd)
# Finish the process
ps.stdin.write('\n')
time.sleep(2)
if (ps.poll() is None):
raise error.TestError("cg.smoke_test: Process is not finished")
[docs]class CgroupModules(object):
"""
Handles the list of different cgroup filesystems.
"""
def __init__(self, mountdir=None):
self.modules = []
self.modules.append([])
self.modules.append([])
self.modules.append([])
if mountdir is None:
self.mountdir = mkdtemp(prefix='cgroup-') + '/'
self.rm_mountdir = True
else:
self.mountdir = mountdir
self.rm_mountdir = False
def __del__(self):
"""
Unmount all cgroups and remove the mountdir
"""
for i in range(len(self.modules[0])):
if self.modules[2][i]:
try:
utils.system('umount %s -l' % self.modules[1][i])
except Exception, failure_detail:
logging.warn("CGM: Couldn't unmount %s directory: %s",
self.modules[1][i], failure_detail)
try:
if self.rm_mountdir:
# If delete /cgroup/, this action will break cgroup service.
shutil.rmtree(self.mountdir)
except Exception:
logging.warn(
"CGM: Couldn't remove the %s directory", self.mountdir)
[docs] def init(self, _modules):
"""
Checks the mounted modules and if necessary mounts them into tmp
mountdir.
:param _modules: Desired modules.'memory','cpu,cpuset'...
:return: Number of initialized modules.
"""
logging.debug("Desired cgroup modules: %s", _modules)
mounts = []
proc_mounts = open('/proc/mounts', 'r')
line = proc_mounts.readline().split()
while line:
if line[2] == 'cgroup':
mounts.append(line)
line = proc_mounts.readline().split()
proc_mounts.close()
for module in _modules:
# Is it already mounted?
i = False
_module = set(module.split(','))
for mount in mounts:
# 'memory' or 'memory,cpuset'
if _module.issubset(mount[3].split(',')):
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
module_path = os.path.join(self.mountdir, module)
if not os.path.exists(module_path):
os.mkdir(module_path)
cmd = ('mount -t cgroup -o %s %s %s' %
(module, module, module_path))
try:
utils.run(cmd)
self.modules[0].append(module)
self.modules[1].append(module_path)
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])
[docs] 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]
[docs]def get_load_per_cpu(_stats=None):
"""
Gather load per cpu from /proc/stat
:param _stats: previous values
:return: list of diff/absolute values of CPU times [SUM, CPU1, CPU2, ...]
"""
stats = []
f_stat = open('/proc/stat', 'r')
if _stats:
for i in range(len(_stats)):
stats.append(int(f_stat.readline().split()[1]) - _stats[i])
else:
line = f_stat.readline()
while line:
if line.startswith('cpu'):
stats.append(int(line.split()[1]))
else:
break
line = f_stat.readline()
return stats
[docs]def get_cgroup_mountpoint(controller, mount_file="/proc/mounts"):
"""
Get desired controller's mountpoint
:param controller: Desired controller
:param mount_file: Name of file contains mounting information, in most
cases this are not need to be set.
:return: controller's mountpoint
:raise: TestError when contoller doesn't exist in mount table
"""
f_cgcon = open(mount_file, "rU")
cgconf_txt = f_cgcon.read()
f_cgcon.close()
mntpt = re.findall(r"\s(\S*cgroup/\S*%s(?=[,\ ])\S*)" % controller, cgconf_txt)
if len(mntpt) == 0:
# Controller is not supported if not found in mount table.
raise error.TestError("Doesn't support controller <%s>" % controller)
return mntpt[0]
[docs]def get_all_controllers():
"""
Get all controllers used in system
:return: all used controllers(controller_list)
"""
try:
result = utils.run("lssubsys", ignore_status=False)
controllers_str = result.stdout.strip()
controller_list = []
for controller in controllers_str.splitlines():
controller_sub_list = controller.split(",")
controller_list += controller_sub_list
except error.CmdError:
controller_list = ['cpuacct', 'cpu', 'memory', 'cpuset',
'devices', 'freezer', 'blkio', 'netcls']
return controller_list
[docs]def resolve_task_cgroup_path(pid, controller):
"""
Resolving cgroup mount path of a particular task
:params: pid : process id of a task for which the cgroup path required
:params: controller: takes one of the controller names in controller list
:return: resolved path for cgroup controllers of a given pid
"""
root_path = get_cgroup_mountpoint(controller)
proc_cgroup = "/proc/%d/cgroup" % pid
if not os.path.isfile(proc_cgroup):
raise NameError('File %s does not exist\n Check whether cgroup \
installed in the system' % proc_cgroup)
try:
proc_file = open(proc_cgroup, 'r')
proc_cgroup_txt = proc_file.read()
finally:
proc_file.close()
mount_path = re.findall(r":\S*%s(?=[,:])\S*:(\S*)\n" % controller, proc_cgroup_txt)
return os.path.join(root_path, mount_path[0].strip("/"))
[docs]class CgconfigService(object):
"""
Cgconfig service class.
"""
def __init__(self):
# libcgroup lack libcgroup-tools dependency will introduces
# following error,
# Failed to issue method call:
# Unit cgconfig.service failed to load:
# No such file or directory
#
# Please refer to
# https://bugzilla.redhat.com/show_bug.cgi?format=multiple&id=882887
if not utils_misc.yum_install(['libcgroup-tools']):
error.TestError("Failed to install libcgroup-tools on host")
self._service_manager = service.Factory.create_service("cgconfig")
def _service_cgconfig_control(self, action):
"""
Cgconfig control by action.
If cmd executes successfully, return True, otherwise return False.
If the action is status, return True when it's running, otherwise return
False.
:param action: cgconfig service action
"""
if not hasattr(self._service_manager, action):
raise error.TestError("Unknown action: %s" % action)
return getattr(self._service_manager, action)()
[docs] def cgconfig_start(self):
"""
Sart cgconfig service
"""
return self._service_cgconfig_control("start")
[docs] def cgconfig_stop(self):
"""
Sop cgconfig service
"""
return self._service_cgconfig_control("stop")
[docs] def cgconfig_restart(self):
"""
Restart cgconfig service
"""
return self._service_cgconfig_control("restart")
[docs] def cgconfig_condrestart(self):
"""
Condrestart cgconfig service
"""
return self._service_cgconfig_control("condrestart")
[docs] def cgconfig_is_running(self):
"""
Check cgconfig service status
"""
return self._service_cgconfig_control("status")
[docs]def all_cgroup_delete():
"""
Clear all cgroups in system
"""
try:
utils.run("cgclear", ignore_status=False)
except error.CmdError, detail:
raise error.TestFail("Clear all cgroup failed!:\n%s" % detail)