"""
Library to perform pre/post test setup for virt test.
"""
import os
import logging
import time
import re
import random
import math
import shutil
from virttest.staging import service
from autotest.client.shared import error, utils
import data_dir
import utils_misc
import versionable_class
import openvswitch
try:
from virttest.staging import utils_memory
except ImportError:
# pylint: disable=E0611
from autotest.client import utils_memory
[docs]class THPError(Exception):
"""
Base exception for Transparent Hugepage setup.
"""
pass
[docs]class THPNotSupportedError(THPError):
"""
Thrown when host does not support transparent hugepages.
"""
pass
[docs]class THPWriteConfigError(THPError):
"""
Thrown when host does not support transparent hugepages.
"""
pass
[docs]class THPKhugepagedError(THPError):
"""
Thrown when khugepaged is not behaving as expected.
"""
pass
[docs]class HPNotSupportedError(Exception):
"""
Thrown when host does not support hugepages.
"""
pass
[docs]class PolkitConfigError(Exception):
"""
Base exception for Polkit Config setup.
"""
pass
[docs]class PolkitRulesSetupError(PolkitConfigError):
"""
Thrown when setup polkit rules is not behaving as expected.
"""
pass
[docs]class PolkitWriteLibvirtdConfigError(PolkitConfigError):
"""
Thrown when setup libvirtd config file is not behaving as expected.
"""
pass
[docs]class PolkitConfigCleanupError(PolkitConfigError):
"""
Thrown when polkit config cleanup is not behaving as expected.
"""
pass
[docs]class TransparentHugePageConfig(object):
def __init__(self, test, params):
"""
Find paths for transparent hugepages and kugepaged configuration. Also,
back up original host configuration so it can be restored during
cleanup.
"""
self.params = params
RH_THP_PATH = "/sys/kernel/mm/redhat_transparent_hugepage"
UPSTREAM_THP_PATH = "/sys/kernel/mm/transparent_hugepage"
if os.path.isdir(RH_THP_PATH):
self.thp_path = RH_THP_PATH
elif os.path.isdir(UPSTREAM_THP_PATH):
self.thp_path = UPSTREAM_THP_PATH
else:
raise THPNotSupportedError("System doesn't support transparent "
"hugepages")
tmp_list = []
test_cfg = {}
test_config = self.params.get("test_config", None)
if test_config is not None:
tmp_list = re.split(';', test_config)
while len(tmp_list) > 0:
tmp_cfg = tmp_list.pop()
test_cfg[re.split(":", tmp_cfg)[0]] = re.split(":", tmp_cfg)[1]
# Save host current config, so we can restore it during cleanup
# We will only save the writeable part of the config files
original_config = {}
# List of files that contain string config values
self.file_list_str = []
# List of files that contain integer config values
self.file_list_num = []
logging.info("Scanning THP base path and recording base values")
for f in os.walk(self.thp_path):
base_dir = f[0]
if f[2]:
for name in f[2]:
f_dir = os.path.join(base_dir, name)
parameter = file(f_dir, 'r').read()
logging.debug("Reading path %s: %s", f_dir,
parameter.strip())
try:
# Verify if the path in question is writable
f = open(f_dir, 'w')
f.close()
if re.findall("\[(.*)\]", parameter):
original_config[f_dir] = re.findall("\[(.*)\]",
parameter)[0]
self.file_list_str.append(f_dir)
else:
original_config[f_dir] = int(parameter)
self.file_list_num.append(f_dir)
except IOError:
pass
self.test_config = test_cfg
self.original_config = original_config
[docs] def set_env(self):
"""
Applies test configuration on the host.
"""
if self.test_config:
logging.info("Applying custom THP test configuration")
for path in self.test_config.keys():
logging.info("Writing path %s: %s", path,
self.test_config[path])
file(path, 'w').write(self.test_config[path])
[docs] def value_listed(self, value):
"""
Get a parameters list from a string
"""
value_list = []
for i in re.split("\[|\]|\n+|\s+", value):
if i:
value_list.append(i)
return value_list
[docs] def khugepaged_test(self):
"""
Start, stop and frequency change test for khugepaged.
"""
def check_status_with_value(action_list, file_name):
"""
Check the status of khugepaged when set value to specify file.
"""
for (act, ret) in action_list:
logging.info("Writing path %s: %s, expected khugepage rc: %s ",
file_name, act, ret)
try:
file_object = open(file_name, "w")
file_object.write(act)
file_object.close()
except IOError, error_detail:
logging.info("IO Operation on path %s failed: %s",
file_name, error_detail)
timeout = time.time() + 50
while time.time() < timeout:
try:
utils.run('pgrep khugepaged', verbose=False)
if ret != 0:
time.sleep(1)
continue
except error.CmdError:
if ret == 0:
time.sleep(1)
continue
break
else:
if ret != 0:
raise THPKhugepagedError("Khugepaged still alive when"
"transparent huge page is "
"disabled")
else:
raise THPKhugepagedError("Khugepaged could not be set to"
"status %s" % act)
logging.info("Testing khugepaged")
for file_path in self.file_list_str:
action_list = []
if re.findall("enabled", file_path):
# Start and stop test for khugepaged
value_list = self.value_listed(open(file_path, "r").read())
for i in value_list:
if re.match("n", i, re.I):
action_stop = (i, 256)
for i in value_list:
if re.match("[^n]", i, re.I):
action = (i, 0)
action_list += [action_stop, action, action_stop]
action_list += [action]
check_status_with_value(action_list, file_path)
else:
value_list = self.value_listed(open(file_path, "r").read())
for i in value_list:
action = (i, 0)
action_list.append(action)
check_status_with_value(action_list, file_path)
for file_path in self.file_list_num:
action_list = []
file_object = open(file_path, "r")
value = file_object.read()
value = int(value)
file_object.close()
if value != 0 and value != 1:
new_value = random.random()
action_list.append((str(int(value * new_value)), 0))
action_list.append((str(int(value * (new_value + 1))), 0))
else:
action_list.append(("0", 0))
action_list.append(("1", 0))
check_status_with_value(action_list, file_path)
[docs] def setup(self):
"""
Configure host for testing. Also, check that khugepaged is working as
expected.
"""
self.set_env()
self.khugepaged_test()
[docs] def cleanup(self):
""":
Restore the host's original configuration after test
"""
logging.info("Restoring host's original THP configuration")
for path in self.original_config:
logging.info("Writing path %s: %s", path,
self.original_config[path])
try:
p_file = open(path, 'w')
p_file.write(str(self.original_config[path]))
p_file.close()
except IOError, error_detail:
logging.info("IO operation failed on file %s: %s", path,
error_detail)
[docs]class HugePageConfig(object):
def __init__(self, params):
"""
Gets environment variable values and calculates the target number
of huge memory pages.
:param params: Dict like object containing parameters for the test.
"""
self.vms = len(params.objects("vms"))
self.mem = int(params.get("mem"))
self.max_vms = int(params.get("max_vms", 0))
self.qemu_overhead = int(params.get("hugepages_qemu_overhead", 128))
self.deallocate = params.get("hugepages_deallocate", "yes") == "yes"
self.hugepage_path = '/mnt/kvm_hugepage'
if os.path.exists('/proc/sys/vm/nr_hugepages'):
self.kernel_hp_file = '/proc/sys/vm/nr_hugepages'
else:
raise HPNotSupportedError("System doesn't support hugepages")
self.pool_path = "/sys/kernel/mm/hugepages"
self.sys_node_path = "/sys/devices/system/node"
self.hugepage_size = self.get_hugepage_size()
self.hugepage_force_allocate = params.get("hugepage_force_allocate",
"no")
self.suggest_mem = None
self.lowest_mem_per_vm = int(params.get("lowest_mem", "256"))
target_hugepages = params.get("target_hugepages")
if target_hugepages is None:
target_hugepages = self.get_target_hugepages()
else:
target_hugepages = int(target_hugepages)
self.target_hugepages = target_hugepages
[docs] def get_hugepage_size(self):
"""
Get the current system setting for huge memory page size.
"""
meminfo = open('/proc/meminfo', 'r').readlines()
huge_line_list = [h for h in meminfo if h.startswith("Hugepagesize")]
try:
return int(huge_line_list[0].split()[1])
except ValueError, e:
raise ValueError("Could not get huge page size setting from "
"/proc/meminfo: %s" % e)
[docs] def get_target_hugepages(self):
"""
Calculate the target number of hugepages for testing purposes.
"""
if self.vms < self.max_vms:
self.vms = self.max_vms
# memory of all VMs plus qemu overhead of 128MB per guest
# (this value can be overridden in your cartesian config)
vmsm = self.vms * (self.mem + self.qemu_overhead)
target_hugepages = int(vmsm * 1024 / self.hugepage_size)
# FIXME Now the buddyinfo can not get chunk info which is bigger
# than 4M. So this will only fit for 2M size hugepages. Can not work
# when hugepage size is 1G.
# And sometimes huge page can not get all pages so decrease the page
# for about 10 huge page to make sure the allocate can success
decreased_pages = 10
if self.hugepage_size > 2048:
self.hugepage_force_allocate = "yes"
if self.hugepage_force_allocate == "no":
hugepage_allocated = open(self.kernel_hp_file, "r")
available_hugepages = int(hugepage_allocated.read().strip())
hugepage_allocated.close()
chunk_bottom = int(math.log(self.hugepage_size / 4, 2))
chunk_info = utils_memory.get_buddy_info(">=%s" % chunk_bottom,
zones="DMA32 Normal")
for size in chunk_info:
available_hugepages += int(chunk_info[size] * math.pow(2,
int(int(size) - chunk_bottom)))
available_hugepages = available_hugepages - decreased_pages
if target_hugepages > available_hugepages:
logging.warn("This test requires more huge pages than we"
" currently have, we'll try to allocate the"
" biggest number the system can support.")
target_hugepages = available_hugepages
available_mem = available_hugepages * self.hugepage_size
self.suggest_mem = int(available_mem / self.vms / 1024 -
self.qemu_overhead)
if self.suggest_mem < self.lowest_mem_per_vm:
raise MemoryError("This host doesn't have enough free "
"large memory pages for this test to "
"run (only %s MB memory available for "
"each guest)" % self.suggest_mem)
return target_hugepages
[docs] def get_multi_supported_hugepage_size(self):
"""
As '/proc/meminfo' only show default huge page size, this function is
for get huge page size of multiple huge page pools.
For each huge page size supported by the running kernel, a
subdirectory will exist, of the form:
hugepages-${size}kB
under /sys/kernel/mm/hugepages, get the support size and return a list.
:return: supported size list in kB unit
"""
hugepage_size = []
if os.path.isdir(self.pool_path):
for path_name in os.listdir(self.pool_path):
logging.debug("path name is %s" % path_name)
if os.path.isdir("%s/%s" % (self.pool_path, path_name)):
hugepage_size.append(path_name.split('-')[1][:-2])
logging.debug(path_name.split('-')[1][:-2])
return hugepage_size
else:
raise ValueError("Root hugepage control sysfs directory %s did not"
" exist" % self.pool_path)
[docs] def get_node_num_huge_pages(self, node, pagesize):
"""
Get number of pages of certain page size under given numa node.
:param node: string or int, node number
:param pagesize: string or int, page size in kB
:return: int, node huge pages number of given page size
"""
node_page_path = "%s/node%s" % (self.sys_node_path, node)
node_page_path += "/hugepages/hugepages-%skB/nr_hugepages" % pagesize
if not os.path.isfile(node_page_path):
raise ValueError("%s page size nr_hugepages file of node %s did "
"not exist" % (pagesize, node))
out = utils.system_output("cat %s" % node_page_path)
return int(out)
[docs] def set_node_num_huge_pages(self, num, node, pagesize):
"""
Set number of pages of certain page size under given numa node.
:param num: string or int, number of pages
:param node: string or int, node number
:param pagesize: string or int, page size in kB
"""
node_page_path = "%s/node%s" % (self.sys_node_path, node)
node_page_path += "/hugepages/hugepages-%skB/nr_hugepages" % pagesize
if not os.path.isfile(node_page_path):
raise ValueError("%s page size nr_hugepages file of node %s did "
"not exist" % (pagesize, node))
utils.system("echo %s > %s" % (num, node_page_path))
@error.context_aware
def set_hugepages(self):
"""
Sets the hugepage limit to the target hugepage value calculated.
"""
error.context("setting hugepages limit to %s" % self.target_hugepages)
hugepage_cfg = open(self.kernel_hp_file, "r+")
hp = hugepage_cfg.readline()
while int(hp) < self.target_hugepages:
loop_hp = hp
hugepage_cfg.write(str(self.target_hugepages))
hugepage_cfg.flush()
hugepage_cfg.seek(0)
hp = int(hugepage_cfg.readline())
if loop_hp == hp:
raise ValueError("Cannot set the kernel hugepage setting "
"to the target value of %d hugepages." %
self.target_hugepages)
hugepage_cfg.close()
logging.debug("Successfully set %s large memory pages on host ",
self.target_hugepages)
@error.context_aware
def mount_hugepage_fs(self):
"""
Verify if there's a hugetlbfs mount set. If there's none, will set up
a hugetlbfs mount using the class attribute that defines the mount
point.
"""
error.context("mounting hugepages path")
if not os.path.ismount(self.hugepage_path):
if not os.path.isdir(self.hugepage_path):
os.makedirs(self.hugepage_path)
cmd = "mount -t hugetlbfs -o pagesize=%sK " % self.hugepage_size
cmd += "none %s" % self.hugepage_path
utils.system(cmd)
[docs] def setup(self):
logging.debug("Number of VMs this test will use: %d", self.vms)
logging.debug("Amount of memory used by each vm: %s", self.mem)
logging.debug("System setting for large memory page size: %s",
self.hugepage_size)
logging.debug("Number of large memory pages needed for this test: %s",
self.target_hugepages)
self.set_hugepages()
self.mount_hugepage_fs()
return self.suggest_mem
@error.context_aware
def cleanup(self):
if self.deallocate:
error.context("trying to deallocate hugepage memory")
try:
utils.system("umount %s" % self.hugepage_path)
except error.CmdError:
return
utils.system("echo 0 > %s" % self.kernel_hp_file)
logging.debug("Hugepage memory successfully deallocated")
[docs]class KSMConfig(object):
def __init__(self, params, env):
"""
:param params: Dict like object containing parameters for the test.
"""
self.pages_to_scan = params.get("ksm_pages_to_scan")
self.sleep_ms = params.get("ksm_sleep_ms")
self.run = params.get("ksm_run", "1")
self.ksm_module = params.get("ksm_module")
if self.run == "yes":
self.run = "1"
elif self.run == "no":
self.run == "0"
# Get KSM module status if there is one
self.ksmctler = utils_misc.KSMController()
self.ksm_module_loaded = self.ksmctler.is_module_loaded()
# load the ksm module for further information check
if self.ksm_module and not self.ksm_module_loaded:
self.ksmctler.load_ksm_module()
# For ksmctl both pages_to_scan and sleep_ms should have value
# So give some default value when it is not set up in params
if self.pages_to_scan is None:
self.pages_to_scan = "5000"
if self.sleep_ms is None:
self.sleep_ms = "50"
# Check if ksmtuned is running before the test
self.ksmtuned_process = self.ksmctler.get_ksmtuned_pid()
# As ksmtuned may update KSM config most of the time we should disable
# it when we test KSM
self.disable_ksmtuned = params.get("disable_ksmtuned", "yes") == "yes"
self.default_status = []
self.default_status.append(self.ksmctler.get_ksm_feature("run"))
self.default_status.append(self.ksmctler.get_ksm_feature(
"pages_to_scan"))
self.default_status.append(self.ksmctler.get_ksm_feature(
"sleep_millisecs"))
self.default_status.append(int(self.ksmtuned_process))
self.default_status.append(self.ksm_module_loaded)
[docs] def setup(self, env):
if self.disable_ksmtuned:
self.ksmctler.stop_ksmtuned()
env.data["KSM_default_config"] = self.default_status
self.ksmctler.set_ksm_feature({"run": self.run,
"pages_to_scan": self.pages_to_scan,
"sleep_millisecs": self.sleep_ms})
[docs] def cleanup(self, env):
default_status = env.data.get("KSM_default_config")
# Get original ksm loaded status
default_ksm_loaded = default_status.pop()
# Remove pid of ksmtuned
if default_status.pop() != 0:
# ksmtuned used to run in host. Start the process
# and don't need set up the configures.
self.ksmctler.start_ksmtuned()
return
if default_status == self.default_status:
# Nothing changed
return
self.ksmctler.set_ksm_feature({"run": default_status[0],
"pages_to_scan": default_status[1],
"sleep_millisecs": default_status[2]})
if self.ksm_module and not default_ksm_loaded:
self.ksmctler.unload_ksm_module()
[docs]class PrivateBridgeError(Exception):
def __init__(self, brname):
self.brname = brname
def __str__(self):
return "Bridge %s not available after setup" % self.brname
[docs]class PrivateBridgeConfig(object):
__shared_state = {}
def __init__(self, params=None):
self.__dict__ = self.__shared_state
if params is not None:
self.brname = params.get("priv_brname", 'atbr0')
self.subnet = params.get("priv_subnet", '192.168.58')
self.ip_version = params.get("bridge_ip_version", "ipv4")
self.dhcp_server_pid = None
ports = params.get("priv_bridge_ports", '53 67').split()
s_port = params.get("guest_port_remote_shell", "10022")
if s_port not in ports:
ports.append(s_port)
ft_port = params.get("guest_port_file_transfer", "10023")
if ft_port not in ports:
ports.append(ft_port)
u_port = params.get("guest_port_unattended_install", "13323")
if u_port not in ports:
ports.append(u_port)
self.iptables_rules = self._assemble_iptables_rules(ports)
self.physical_nic = params.get("physical_nic")
self.force_create = False
if params.get("bridge_force_create", "no") == "yes":
self.force_create = True
def _assemble_iptables_rules(self, port_list):
rules = []
index = 0
for port in port_list:
index += 1
rules.append("INPUT %s -i %s -p tcp --dport %s -j ACCEPT" %
(index, self.brname, port))
index += 1
rules.append("INPUT %s -i %s -p udp --dport %s -j ACCEPT" %
(index, self.brname, port))
rules.append("FORWARD 1 -m physdev --physdev-is-bridged -j ACCEPT")
rules.append("FORWARD 2 -d %s.0/24 -o %s -m state "
"--state RELATED,ESTABLISHED -j ACCEPT" %
(self.subnet, self.brname))
rules.append("FORWARD 3 -s %s.0/24 -i %s -j ACCEPT" %
(self.subnet, self.brname))
rules.append("FORWARD 4 -i %s -o %s -j ACCEPT" %
(self.brname, self.brname))
return rules
def _add_bridge(self):
utils.system("brctl addbr %s" % self.brname)
ip_fwd_path = "/proc/sys/net/%s/ip_forward" % self.ip_version
ip_fwd = open(ip_fwd_path, "w")
ip_fwd.write("1\n")
utils.system("brctl stp %s on" % self.brname)
utils.system("brctl setfd %s 4" % self.brname)
if self.physical_nic:
utils.system("brctl addif %s %s" % (self.brname,
self.physical_nic))
def _bring_bridge_up(self):
utils.system("ifconfig %s %s.1 up" % (self.brname, self.subnet))
def _iptables_add(self, cmd):
return utils.system("iptables -I %s" % cmd)
def _iptables_del(self, cmd):
return utils.system("iptables -D %s" % cmd)
def _enable_nat(self):
for rule in self.iptables_rules:
self._iptables_add(rule)
def _start_dhcp_server(self):
utils.system("service dnsmasq stop")
utils.system("dnsmasq --strict-order --bind-interfaces "
"--listen-address %s.1 --dhcp-range %s.2,%s.254 "
"--dhcp-lease-max=253 "
"--dhcp-no-override "
"--pid-file=/tmp/dnsmasq.pid "
"--log-facility=/tmp/dnsmasq.log" %
(self.subnet, self.subnet, self.subnet))
self.dhcp_server_pid = None
try:
self.dhcp_server_pid = int(open('/tmp/dnsmasq.pid', 'r').read())
except ValueError:
raise PrivateBridgeError(self.brname)
logging.debug("Started internal DHCP server with PID %s",
self.dhcp_server_pid)
def _verify_bridge(self):
brctl_output = utils.system_output("brctl show")
if self.brname not in brctl_output:
raise PrivateBridgeError(self.brname)
def _get_bridge_info(self):
return utils.system_output("brctl show")
def _br_exist(self):
return self.brname in self._get_bridge_info()
def _br_in_use(self):
output = self._get_bridge_info()
for line in output.split("\n"):
if line.startswith(self.brname):
# len == 4 means there is a TAP using the bridge
# so don't try to clean it up
if len(line.split()) < 4:
return False
return True
[docs] def setup(self):
if self._br_exist() and self.force_create:
self._bring_bridge_down()
self._remove_bridge()
if not self._br_exist():
logging.info("Configuring KVM test private bridge %s", self.brname)
try:
self._add_bridge()
except:
self._remove_bridge()
raise
try:
self._bring_bridge_up()
except:
self._bring_bridge_down()
self._remove_bridge()
raise
try:
self._enable_nat()
except:
self._disable_nat()
self._bring_bridge_down()
self._remove_bridge()
raise
try:
self._start_dhcp_server()
except:
self._stop_dhcp_server()
self._disable_nat()
self._bring_bridge_down()
self._remove_bridge()
raise
# Fix me the physical_nic always down after setup
# Need manually up.
if self.physical_nic:
time.sleep(5)
utils.system("ifconfig %s up" % self.physical_nic)
self._verify_bridge()
def _stop_dhcp_server(self):
if self.dhcp_server_pid is not None:
try:
os.kill(self.dhcp_server_pid, 15)
except OSError:
pass
else:
try:
dhcp_server_pid = int(open('/tmp/dnsmasq.pid', 'r').read())
except ValueError:
return
try:
os.kill(dhcp_server_pid, 15)
except OSError:
pass
def _bring_bridge_down(self):
utils.system("ifconfig %s down" % self.brname, ignore_status=True)
def _disable_nat(self):
for rule in self.iptables_rules:
split_list = rule.split(' ')
# We need to remove numbering here
split_list.pop(1)
rule = " ".join(split_list)
self._iptables_del(rule)
def _remove_bridge(self):
utils.system("brctl delbr %s" % self.brname, ignore_status=True)
[docs] def cleanup(self):
if not self._br_in_use():
logging.debug(
"Cleaning up KVM test private bridge %s", self.brname)
self._stop_dhcp_server()
self._disable_nat()
self._bring_bridge_down()
self._remove_bridge()
[docs]class PrivateOvsBridgeConfig(PrivateBridgeConfig):
def __init__(self, params=None):
super(PrivateOvsBridgeConfig, self).__init__(params)
ovs = versionable_class.factory(openvswitch.OpenVSwitchSystem)()
ovs.init_system()
self.ovs = ovs
def _get_bridge_info(self):
return self.ovs.status()
def _br_exist(self):
return "Bridge \"%s\"" % self.brname in self._get_bridge_info()
def _br_in_use(self):
output = self._get_bridge_info()
for br_info in output.split("Bridge"):
br_info = br_info.strip()
if (br_info and re.match(self.brname, br_info) and
len(re.findall("Port\s+", br_info)) == 1):
return False
return True
def _verify_bridge(self):
self.ovs.check()
def _add_bridge(self):
self.ovs.add_br(self.brname)
def _remove_bridge(self):
self.ovs.del_br(self.brname)
[docs]class PciAssignable(object):
"""
Request PCI assignable devices on host. It will check whether to request
PF (physical Functions) or VF (Virtual Functions).
"""
def __init__(self, driver=None, driver_option=None, host_set_flag=None,
kvm_params=None, vf_filter_re=None, pf_filter_re=None,
device_driver=None, nic_name_re=None):
"""
Initialize parameter 'type' which could be:
vf: Virtual Functions
pf: Physical Function (actual hardware)
mixed: Both includes VFs and PFs
If pass through Physical NIC cards, we need to specify which devices
to be assigned, e.g. 'eth1 eth2'.
If pass through Virtual Functions, we need to specify max vfs in driver
e.g. max_vfs = 7 in config file.
:param type: PCI device type.
:type type: string
:param driver: Kernel module for the PCI assignable device.
:type driver: string
:param driver_option: Module option to specify the maximum number of
VFs (eg 'max_vfs=7')
:type driver_option: string
:param host_set_flag: Flag for if the test should setup host env:
0: do nothing
1: do setup env
2: do cleanup env
3: setup and cleanup env
:type host_set_flag: string
:param kvm_params: a dict for kvm module parameters default value
:type kvm_params: dict
:param vf_filter_re: Regex used to filter vf from lspci.
:type vf_filter_re: string
:param pf_filter_re: Regex used to filter pf from lspci.
:type pf_filter_re: string
"""
self.devices = []
self.driver = driver
self.driver_option = driver_option
self.name_list = []
self.devices_requested = 0
self.pf_vf_info = []
self.dev_unbind_drivers = {}
self.dev_drivers = {}
self.vf_filter_re = vf_filter_re
self.pf_filter_re = pf_filter_re
if nic_name_re:
self.nic_name_re = nic_name_re
else:
self.nic_name_re = "\w+(?=: flags)|eth[0-9](?=\s*Link)"
if device_driver:
if device_driver == "pci-assign":
self.device_driver = "pci-stub"
else:
self.device_driver = device_driver
else:
self.device_driver = "pci-stub"
if host_set_flag is not None:
self.setup = int(host_set_flag) & 1 == 1
self.cleanup = int(host_set_flag) & 2 == 2
else:
self.setup = False
self.cleanup = False
self.kvm_params = kvm_params
self.auai_path = None
if self.kvm_params is not None:
for i in self.kvm_params:
if "allow_unsafe_assigned_interrupts" in i:
self.auai_path = i
if self.setup:
self.sr_iov_setup()
[docs] def add_device(self, device_type="vf", name=None, mac=None):
"""
Add device type and name to class.
:param device_type: vf/pf device is added.
:type device_type: string
:param name: Physical device interface name. eth1 or others
:type name: string
:param mac: set mac address for vf.
:type mac: string
"""
device = {}
device['type'] = device_type
if name is not None:
device['name'] = name
if mac:
device['mac'] = mac
self.devices.append(device)
self.devices_requested += 1
def _get_pf_pci_id(self, name=None):
"""
Get the PF PCI ID according to name.
It returns the first free pf, if no name matched.
:param name: Name of the PCI device.
:type name: string
:return: pci id of the PF device.
:rtype: string
"""
pf_id = None
if self.pf_vf_info:
for pf in self.pf_vf_info:
if name and "ethname" in pf and name == pf["ethname"]:
pf["occupied"] = True
pf_id = pf["pf_id"]
break
if pf_id is None:
for pf in self.pf_vf_info:
if not pf["occupied"]:
pf["occupied"] = True
pf_id = pf["pf_id"]
break
return pf_id
def _get_vf_pci_id(self, name=None):
"""
Get the VF PCI ID according to name.
It returns the first free vf, if no name matched.
:param name: Name of the PCI device.
:type name: string
:return: pci id of the VF device.
:rtype: string
"""
vf_id = None
if self.pf_vf_info:
for pf in self.pf_vf_info:
if name and "ethname" in pf and name == pf["ethname"]:
for vf in pf["vf_ids"]:
vf_id = vf["vf_id"]
if (not vf["occupied"] and
not self.is_binded_to_stub(vf_id)):
vf["occupied"] = True
return vf_id
if vf_id is None:
for pf in self.pf_vf_info:
if pf["occupied"]:
continue
for vf in pf["vf_ids"]:
vf_id = vf["vf_id"]
if (not vf["occupied"] and
not self.is_binded_to_stub(vf_id)):
vf["occupied"] = True
return vf_id
@error.context_aware
def _release_dev(self, pci_id):
"""
Release a single PCI device.
:param pci_id: PCI ID of a given PCI device.
:type pci_id: string
:return: True if successfully release the device. else false.
:rtype: bool
"""
base_dir = "/sys/bus/pci"
short_id = pci_id[5:]
vendor_id = utils_misc.get_vendor_from_pci_id(short_id)
drv_path = os.path.join(base_dir, "devices/%s/driver" % pci_id)
if self.device_driver in os.readlink(drv_path):
error.context("Release device %s to host" % pci_id, logging.info)
stub_path = os.path.join(base_dir,
"drivers/%s" % self.device_driver)
cmd = "echo '%s' > %s/unbind" % (pci_id, stub_path)
logging.info("Run command in host: %s" % cmd)
try:
output = None
output = utils.system_output(cmd, timeout=60)
except Exception:
msg = "Command %s fail with output %s" % (cmd, output)
logging.error(msg)
return False
drivers_probe = os.path.join(base_dir, "drivers_probe")
cmd = "echo '%s' > %s" % (pci_id, drivers_probe)
logging.info("Run command in host: %s" % cmd)
try:
output = None
output = utils.system_output(cmd, timeout=60)
except Exception:
msg = "Command %s fail with output %s" % (cmd, output)
logging.error(msg)
return False
if self.is_binded_to_stub(pci_id):
return False
return True
[docs] def get_vf_status(self, vf_id):
"""
Check whether one vf is assigned to VM.
:param vf_id: vf id to check.
:type vf_id: string
:return: Return True if vf has already assigned to VM. Else
return false.
:rtype: bool
"""
base_dir = "/sys/bus/pci"
tub_path = os.path.join(base_dir, "drivers/pci-stub")
vf_res_path = os.path.join(tub_path, "%s/resource*" % vf_id)
cmd = "lsof %s" % vf_res_path
output = utils.system_output(cmd, timeout=60, ignore_status=True)
if 'qemu' in output:
return True
else:
return False
[docs] def get_vf_num_by_id(self, vf_id):
"""
Return corresponding pf eth name and vf num according to vf id.
:param vf_id: vf id to check.
:type vf_id: string
:return: PF device name and vf num.
:rtype: string
"""
for pf_info in self.pf_vf_info:
for vf_info in pf_info.get('vf_ids'):
if vf_id == vf_info["vf_id"]:
return pf_info['ethname'], pf_info["vf_ids"].index(vf_info)
raise ValueError("Could not find vf id '%s' in '%s'" % (vf_id,
self.pf_vf_info))
[docs] def get_pf_vf_info(self):
"""
Get pf and vf related information in this host that match ``self.pf_filter_re``.
for every pf it will create following information:
pf_id:
The id of the pf device.
occupied:
Whether the pf device assigned or not
vf_ids:
Id list of related vf in this pf.
ethname:
eth device name in host for this pf.
:return: return a list contains pf vf information.
:rtype: list of dict
"""
base_dir = "/sys/bus/pci/devices"
cmd = "lspci | awk '/%s/ {print $1}'" % self.pf_filter_re
pf_ids = [i for i in utils.system_output(cmd).splitlines()]
pf_vf_dict = []
for pf_id in pf_ids:
pf_info = {}
vf_ids = []
full_id = utils_misc.get_full_pci_id(pf_id)
pf_info["pf_id"] = full_id
pf_info["occupied"] = False
d_link = os.path.join("/sys/bus/pci/devices", full_id)
txt = utils.system_output("ls %s" % d_link)
re_vfn = "(virtfn[0-9])"
paths = re.findall(re_vfn, txt)
for path in paths:
f_path = os.path.join(d_link, path)
vf_id = os.path.basename(os.path.realpath(f_path))
vf_info = {}
vf_info["vf_id"] = vf_id
vf_info["occupied"] = False
vf_ids.append(vf_info)
pf_info["vf_ids"] = vf_ids
pf_vf_dict.append(pf_info)
if_out = utils.system_output("ifconfig -a")
ethnames = re.findall(self.nic_name_re, if_out)
for eth in ethnames:
cmd = "ethtool -i %s | awk '/bus-info/ {print $2}'" % eth
pci_id = utils.system_output(cmd)
if not pci_id:
continue
for pf in pf_vf_dict:
if pci_id in pf["pf_id"]:
pf["ethname"] = eth
return pf_vf_dict
[docs] def get_vf_devs(self, devices=None):
"""
Get all unused VFs PCI IDs.
:param devices: List of device dict that contain PF VF information.
:type devices: List of dict
:return: List of all available PCI IDs for Virtual Functions.
:rtype: List of string
"""
vf_ids = []
if not devices:
devices = self.devices
logging.info("devices = %s", devices)
for device in devices:
if device['type'] == 'vf':
name = device.get('name', None)
vf_id = self._get_vf_pci_id(name)
logging.info("vf_id = %s", vf_id)
if not vf_id:
continue
vf_ids.append(vf_id)
return vf_ids
[docs] def get_pf_devs(self, devices=None):
"""
Get PFs PCI IDs requested by self.devices.
It will try to get PF by device name.
It will still return it, if device name you set already occupied.
Please set unoccupied device name. If not sure, please just do not
set device name. It will return unused PF list.
:param devices: List of device dict that contain PF VF information.
:type devices: List of dict
:return: List with all PCI IDs for the physical hardware requested
:rtype: List of string
"""
pf_ids = []
if not devices:
devices = self.devices
for device in devices:
if device['type'] == 'pf':
name = device.get('name', None)
pf_id = self._get_pf_pci_id(name)
if not pf_id:
continue
pf_ids.append(pf_id)
return pf_ids
[docs] def get_devs(self, devices=None):
"""
Get devices' PCI IDs according to parameters set in self.devices.
:param devices: List of device dict that contain PF VF information.
:type devices: List of dict
:return: List of all available devices' PCI IDs
:rtype: List of string
"""
base_dir = "/sys/bus/pci"
if not devices:
devices = self.devices
if isinstance(devices, dict):
devices = [devices]
pf_ids = self.get_pf_devs(devices)
logging.info("pf_ids = %s", pf_ids)
vf_ids = self.get_vf_devs(devices)
logging.info("vf_ids = %s", vf_ids)
vf_ids.sort()
dev_ids = []
for device in devices:
d_type = device.get("type", "vf")
if d_type == "vf":
dev_id = vf_ids.pop(0)
(ethname, vf_num) = self.get_vf_num_by_id(dev_id)
set_mac_cmd = "ip link set dev %s vf %s mac %s " % (ethname,
vf_num,
device["mac"])
utils.run(set_mac_cmd)
elif d_type == "pf":
dev_id = pf_ids.pop(0)
dev_ids.append(dev_id)
unbind_driver = os.path.realpath(os.path.join(base_dir,
"devices/%s/driver" % dev_id))
self.dev_unbind_drivers[dev_id] = unbind_driver
if len(dev_ids) != len(devices):
logging.error("Did not get enough PCI Device")
return dev_ids
[docs] def get_vfs_count(self):
"""
Get VFs count number according to lspci.
"""
# FIXME: Need to think out a method of identify which
# 'virtual function' belongs to which physical card considering
# that if the host has more than one 82576 card. PCI_ID?
cmd = "lspci | grep '%s' | wc -l" % self.vf_filter_re
vf_num = int(utils.system_output(cmd, verbose=False))
logging.info("Found %s vf in host", vf_num)
return vf_num
[docs] def get_same_group_devs(self, pci_id):
"""
Get the device that in same iommu group.
:param pci_id: Device's pci_id
:type pci_id: string
:return: Return the device's pci id that in same group with pci_id.
:rtype: List of string.
"""
pci_ids = []
base_dir = "/sys/bus/pci/devices"
devices_link = os.path.join(base_dir,
"%s/iommu_group/devices/" % pci_id)
out = utils.system_output("ls %s" % devices_link)
if out:
pci_ids = out.split()
return pci_ids
[docs] def check_vfs_count(self):
"""
Check VFs count number according to the parameter driver_options.
"""
# Network card 82576 has two network interfaces and each can be
# virtualized up to 7 virtual functions, therefore we multiply
# two for the value of driver_option 'max_vfs'.
expected_count = int((re.findall("(\d)", self.driver_option)[0])) * 2
return (self.get_vfs_count() == expected_count)
[docs] def is_binded_to_stub(self, full_id):
"""
Verify whether the device with full_id is already binded to driver.
:param full_id: Full ID for the given PCI device
:type full_id: String
"""
base_dir = "/sys/bus/pci"
stub_path = os.path.join(base_dir, "drivers/%s" % self.device_driver)
return os.path.exists(os.path.join(stub_path, full_id))
@error.context_aware
def sr_iov_setup(self):
"""
Ensure the PCI device is working in sr_iov mode.
Check if the PCI hardware device drive is loaded with the appropriate,
parameters (number of VFs), and if it's not, perform setup.
:return: True, if the setup was completed successfully, False otherwise.
:rtype: bool
"""
# Check if the host support interrupt remapping
error.context("Set up host env for PCI assign test", logging.info)
kvm_re_probe = True
o = utils.system_output("dmesg")
ecap = re.findall("ecap\s+(.\w+)", o)
if not ecap:
logging.error("Fail to check host interrupt remapping support.")
else:
if int(ecap[0], 16) & 8 == 8:
# host support interrupt remapping.
# No need enable allow_unsafe_assigned_interrupts.
kvm_re_probe = False
if self.kvm_params is not None:
if self.auai_path and self.kvm_params[self.auai_path] == "Y":
kvm_re_probe = False
# Try to re probe kvm module with interrupt remapping support
if kvm_re_probe and self.auai_path:
cmd = "echo Y > %s" % self.auai_path
error.context("enable PCI passthrough with '%s'" % cmd,
logging.info)
try:
utils.system(cmd)
except Exception:
logging.debug("Can not enable the interrupt remapping support")
lnk = "/sys/module/vfio_iommu_type1/parameters/allow_unsafe_interrupts"
if self.device_driver == "vfio-pci":
status = utils.system('lsmod | grep vfio', ignore_status=True)
if status:
logging.info("Load vfio-pci module.")
cmd = "modprobe vfio-pci"
utils.run(cmd)
time.sleep(3)
if not ecap or (int(ecap[0], 16) & 8 != 8):
cmd = "echo Y > %s" % lnk
error.context("enable PCI passthrough with '%s'" % cmd,
logging.info)
utils.run(cmd)
re_probe = False
status = utils.system("lsmod | grep %s" % self.driver,
ignore_status=True)
if status:
re_probe = True
elif not self.check_vfs_count():
if self.driver:
utils.system_output("modprobe -r %s" % self.driver, timeout=60)
re_probe = True
else:
self.setup = None
return True
# Re-probe driver with proper number of VFs
if re_probe:
cmd = None
status = 0
if self.driver:
cmd = "modprobe %s " % self.driver
if self.driver_option:
cmd += " %s" % self.driver_option
if cmd:
error.context("Loading the driver '%s' with command '%s'" %
(self.driver, cmd), logging.info)
status = utils.system(cmd, ignore_status=True)
dmesg = utils.system_output("dmesg", timeout=60, ignore_status=True)
file_name = "host_dmesg_after_load_%s.txt" % self.driver
logging.info("Log dmesg after loading '%s' to '%s'.", self.driver,
file_name)
utils_misc.log_line(file_name, dmesg)
if status:
return False
self.setup = None
return True
[docs] def sr_iov_cleanup(self):
"""
Clean up the sriov setup
Check if the PCI hardware device drive is loaded with the appropriate,
parameters (none of VFs), and if it's not, perform cleanup.
:return: True, if the setup was completed successfully, False otherwise.
:rtype: bool
"""
# Check if the host support interrupt remapping
error.context("Clean up host env after PCI assign test", logging.info)
kvm_re_probe = False
if self.kvm_params is not None:
for kvm_param, value in self.kvm_params.items():
if open(kvm_param, "r").read().strip() != value:
cmd = "echo %s > %s" % (value, kvm_param)
logging.info("Write '%s' to '%s'", value, kvm_param)
try:
utils.system(cmd)
except Exception:
logging.error("Failed to write '%s' to '%s'", value,
kvm_param)
re_probe = False
status = utils.system('lsmod | grep %s' % self.driver,
ignore_status=True)
if status:
if self.driver:
cmd = "modprobe -r %s" % self.driver
logging.info("Running host command: %s" % cmd)
utils.system_output(cmd, timeout=60)
re_probe = True
else:
return True
# Re-probe driver with proper number of VFs
if re_probe:
if self.driver:
cmd = "modprobe %s" % self.driver
msg = "Loading the driver '%s' without option" % self.driver
error.context(msg, logging.info)
status = utils.system(cmd, ignore_status=True)
if status:
return False
return True
[docs] def request_devs(self, devices=None):
"""
Implement setup process: unbind the PCI device and then bind it
to the device driver.
:param devices: List of device dict
:type devices: List of dict
:return: List of successfully requested devices' PCI IDs.
:rtype: List of string
"""
if not self.pf_vf_info:
self.pf_vf_info = self.get_pf_vf_info()
base_dir = "/sys/bus/pci"
stub_path = os.path.join(base_dir, "drivers/%s" % self.device_driver)
self.pci_ids = self.get_devs(devices)
logging.info("The following pci_ids were found: %s", self.pci_ids)
requested_pci_ids = []
# Setup all devices specified for assignment to guest
for p_id in self.pci_ids:
if self.device_driver == "vfio-pci":
pci_ids = self.get_same_group_devs(p_id)
logging.info("Following devices are in same group: %s", pci_ids)
else:
pci_ids = [p_id]
for pci_id in pci_ids:
short_id = pci_id[5:]
drv_path = os.path.join(base_dir, "devices/%s/driver" % pci_id)
dev_prev_driver = os.path.realpath(os.path.join(drv_path,
os.readlink(drv_path)))
self.dev_drivers[pci_id] = dev_prev_driver
# Judge whether the device driver has been binded to stub
if not self.is_binded_to_stub(pci_id):
error.context("Bind device %s to stub" % pci_id,
logging.info)
vendor_id = utils_misc.get_vendor_from_pci_id(short_id)
stub_new_id = os.path.join(stub_path, 'new_id')
unbind_dev = os.path.join(drv_path, 'unbind')
stub_bind = os.path.join(stub_path, 'bind')
stub_remove_id = os.path.join(stub_path, 'remove_id')
info_write_to_files = [(vendor_id, stub_new_id),
(pci_id, unbind_dev),
(pci_id, stub_bind),
(vendor_id, stub_remove_id)]
for content, f_name in info_write_to_files:
try:
logging.info("Write '%s' to file '%s'", content,
f_name)
utils.open_write_close(f_name, content)
except IOError:
logging.debug("Failed to write %s to file %s",
content, f_name)
continue
if not self.is_binded_to_stub(pci_id):
logging.error("Binding device %s to stub failed", pci_id)
continue
else:
logging.debug("Device %s already binded to stub", pci_id)
requested_pci_ids.append(p_id)
return requested_pci_ids
@error.context_aware
def release_devs(self):
"""
Release all PCI devices currently assigned to VMs back to the
virtualization host.
"""
try:
for pci_id in self.dev_drivers:
if not self._release_dev(pci_id):
logging.error(
"Failed to release device %s to host", pci_id)
else:
logging.info("Released device %s successfully", pci_id)
if self.cleanup:
self.sr_iov_cleanup()
self.devices = []
self.devices_requested = 0
self.dev_unbind_drivers = {}
except Exception:
return
[docs]class LibvirtPolkitConfig(object):
"""
Enable polkit access driver for libvirtd and set polkit rules.
For setting JavaScript polkit rule, using template of rule to satisfy
libvirt ACL API testing need, just replace keys in template.
Create a non-privileged user 'testacl' for test if given
'unprivileged_user' contains 'EXAMPLE', and delete the user at cleanup.
Multiple rules could be add into one config file while action_id string
is offered space separated.
e.g.
action_id = "org.libvirt.api.domain.start org.libvirt.api.domain.write"
then 2 actions "org.libvirt.api.domain.start" and
"org.libvirt.api.domain.write" specified, which could be used to generate
2 rules in one config file.
"""
def __init__(self, params):
"""
:param params: Dict like object containing parameters for the test.
"""
self.libvirtd_path = "/etc/libvirt/libvirtd.conf"
self.libvirtd_backup_path = "/etc/libvirt/libvirtd.conf.virttest.backup"
self.polkit_rules_path = "/etc/polkit-1/rules.d/"
self.polkit_rules_path += "500-libvirt-acl-virttest.rules"
self.polkitd = service.Factory.create_service("polkit")
if params.get("action_id"):
self.action_id = params.get("action_id").split()
else:
self.action_id = []
self.user = params.get("unprivileged_user")
if params.get("action_lookup"):
# The action_lookup string should be separated by space and
# each separated string should have ':' which represent key:value
# for later use.
self.attr = params.get("action_lookup").split()
else:
self.attr = []
[docs] def file_replace_append(self, fpath, pat, repl):
"""
Replace pattern in file with replacement str if pattern found in file,
else append the replacement str to file.
:param fpath: string, the file path
:param pat: string, the pattern string
:param repl: string, the string to replace
"""
try:
lines = open(fpath).readlines()
if not any(re.search(pat, line) for line in lines):
f = open(fpath, 'a')
f.write(repl + '\n')
f.close()
return
else:
out_fpath = fpath + ".tmp"
out = open(out_fpath, "w")
for line in lines:
if re.search(pat, line):
out.write(repl + '\n')
else:
out.write(line)
out.close()
os.rename(out_fpath, fpath)
except Exception:
raise PolkitWriteLibvirtdConfigError("Failed to update file '%s'."
% fpath)
def _setup_libvirtd(self):
"""
Config libvirtd
"""
# Backup libvirtd.conf
shutil.copy(self.libvirtd_path, self.libvirtd_backup_path)
# Set the API access control scheme
access_str = "access_drivers = [ \"polkit\" ]"
access_pat = "^ *access_drivers"
self.file_replace_append(self.libvirtd_path, access_pat, access_str)
# Set UNIX socket access controls
sock_rw_str = "unix_sock_rw_perms = \"0777\""
sock_rw_pat = "^ *unix_sock_rw_perms"
self.file_replace_append(self.libvirtd_path, sock_rw_pat, sock_rw_str)
# Set authentication mechanism
auth_unix_str = "auth_unix_rw = \"none\""
auth_unix_pat = "^ *auth_unix_rw"
self.file_replace_append(self.libvirtd_path, auth_unix_pat,
auth_unix_str)
def _set_polkit_conf(self):
"""
Set polkit libvirt ACL rule config file
"""
# polkit template string
template = "polkit.addRule(function(action, subject) {\n"
template += "RULE"
template += "});"
# polkit rule template string
rule = " if (action.id == 'ACTION_ID'"
rule += " && subject.user == 'USERNAME') {\n"
rule += "HANDLE"
rule += " }\n"
handle = " if (ACTION_LOOKUP) {\n"
handle += " return polkit.Result.YES;\n"
handle += " } else {\n"
handle += " return polkit.Result.NO;\n"
handle += " }\n"
action_str = "action.lookup('ATTR') == 'VAL'"
try:
# replace keys except 'ACTION_ID', these keys will remain same
# as in different rules
rule_tmp = rule.replace('USERNAME', self.user)
# replace HANDLE part in rule
action_opt = []
if self.attr:
for i in range(len(self.attr)):
attr_tmp = self.attr[i].split(':')
action_tmp = action_str.replace('ATTR', attr_tmp[0])
action_tmp = action_tmp.replace('VAL', attr_tmp[1])
action_opt.append(action_tmp)
if i > 0:
action_opt[i] = " && " + action_opt[i]
action_tmp = ""
for i in range(len(action_opt)):
action_tmp += action_opt[i]
# replace ACTION_LOOKUP with string from self.attr
handle_tmp = handle.replace('ACTION_LOOKUP', action_tmp)
rule_tmp = rule_tmp.replace('HANDLE', handle_tmp)
else:
rule_tmp = rule_tmp.replace('HANDLE', " ")
# replace 'ACTION_ID' in loop and generate rules
rules = ""
for i in range(len(self.action_id)):
rules += rule_tmp.replace('ACTION_ID', self.action_id[i])
# replace 'RULE' with rules in polkit template string
self.template = template.replace('RULE', rules)
logging.debug("The polkit config rule is:\n%s" % self.template)
# write the config file
utils.open_write_close(self.polkit_rules_path, self.template)
except Exception:
raise PolkitRulesSetupError("Set polkit rules file failed")
[docs] def setup(self):
"""
Enable polkit libvirt access driver and setup polkit ACL rules.
"""
self._setup_libvirtd()
# Use 'testacl' if unprivileged_user in cfg contains string 'EXAMPLE',
# and if user 'testacl' is not exist on host, create it for test.
if self.user.count('EXAMPLE'):
cmd = "id testacl"
if utils.system(cmd, ignore_status=True):
logging.debug("Create new user 'testacl' on host.")
cmd = "useradd testacl"
utils.system(cmd, ignore_status=True)
self.user = 'testacl'
self._set_polkit_conf()
# Polkit rule will take about 1 second to take effect after change.
# Restart polkit daemon will force it immediately.
self.polkitd.restart()
[docs] def cleanup(self):
"""
Cleanup polkit config
"""
try:
if os.path.exists(self.polkit_rules_path):
os.unlink(self.polkit_rules_path)
if os.path.exists(self.libvirtd_backup_path):
os.rename(self.libvirtd_backup_path, self.libvirtd_path)
if self.user.count('EXAMPLE'):
logging.debug("Delete the created user 'testacl'.")
cmd = "userdel -r testacl"
utils.system(cmd, ignore_status=True)
except Exception:
raise PolkitConfigCleanupError("Failed to cleanup polkit config.")
[docs]class EGDConfigError(Exception):
"""
Raise when setup local egd.pl server failed.
"""
pass
[docs]class EGDConfig(object):
"""
Setup egd.pl server on localhost, support startup with socket unix or tcp.
"""
def __init__(self, params, env):
self.params = params
self.env = env
def __get_tarball(self):
tarball = "egd-0.9.tar.gz"
tarball = self.params.get("egd_source_tarball", tarball)
return utils_misc.get_path(data_dir.DEPS_DIR, tarball)
def __extra_tarball(self):
tmp_dir = data_dir.get_tmp_dir()
tarball = self.__get_tarball()
extra_cmd = "tar -xzvf %s -C %s" % (tarball, tmp_dir)
utils.system(extra_cmd, ignore_status=True)
output = utils.system_output("tar -tzf %s" % tarball)
return os.path.join(tmp_dir, output.splitlines()[0])
[docs] def startup(self, socket):
"""
Start egd.pl server with tcp or unix socket.
"""
if utils.system("which egd.pl", ignore_status=True) != 0:
self.install()
prog = utils.system_output("which egd.pl")
pid = self.get_pid(socket)
try:
if not pid:
cmd = "%s %s" % (prog, socket)
utils.BgJob(cmd)
except Exception, details:
msg = "Unable to start egd.pl on localhost '%s'" % details
raise EGDConfigError(msg)
pid = self.get_pid(socket)
logging.info("egd.pl started as pid: %s" % pid)
return pid
[docs] def install(self):
"""
Install egd.pl from source code
"""
pwd = os.getcwd()
try:
make_cmd = "perl Makefile.PL && make && make install"
make_cmd = self.params.get("build_egd_cmd", make_cmd)
src_root = self.__extra_tarball()
os.chdir(src_root)
utils.make(make=make_cmd, timeout=120)
except Exception, details:
raise EGDConfigError("Install egd.pl error '%s'" % details)
finally:
os.chdir(pwd)
[docs] def get_pid(self, socket):
"""
Check egd.pl start at socket on localhost.
"""
cmd = "lsof %s" % socket
if socket.startswith("localhost:"):
cmd = "lsof -i '@%s'" % socket
def system_output_wrapper():
return utils.system_output(cmd, ignore_status=True)
output = utils.wait_for(system_output_wrapper, timeout=5)
if not output:
return 0
pid = int(re.findall(r".*egd.pl\s+(\d+)\s+\w+", output, re.M)[-1])
return pid
[docs] def setup(self):
backend = self.params["chardev_backend"]
backend_type = self.params["%s_type" % backend]
path = "path_%s" % backend_type
port = "port_%s" % backend_type
path, port = map(self.params.get, [path, port])
sockets = port and ["localhost:%s" % port] or []
if path:
sockets.append(path)
pids = filter(None, map(self.startup, sockets))
self.env.data["egd_pids"] = pids
[docs] def cleanup(self):
try:
for pid in self.env.data["egd_pids"]:
logging.info("Stop egd.pl(%s)" % pid)
utils.signal_pid(pid, 15)
def _all_killed():
for pid in self.env.data["egd_pids"]:
if utils.pid_is_alive(pid):
return False
return True
# wait port released by egd.pl
utils.wait_for(_all_killed, timeout=60)
except OSError:
logging.warn("egd.pl is running")