virt: Add more flexible way to specify comm ports host -> guest
[autotest-zwu.git] / client / virt / virt_test_setup.py
blob1539cac7ebb12400223ca19f70ab0f38c76bc6df
1 """
2 Library to perform pre/post test setup for KVM autotest.
3 """
4 import os, logging, time, re, sre, random
5 from autotest_lib.client.common_lib import error
6 from autotest_lib.client.bin import utils
9 class THPError(Exception):
10 """
11 Base exception for Transparent Hugepage setup.
12 """
13 pass
16 class THPNotSupportedError(THPError):
17 """
18 Thrown when host does not support tansparent hugepages.
19 """
20 pass
23 class THPWriteConfigError(THPError):
24 """
25 Thrown when host does not support tansparent hugepages.
26 """
27 pass
30 class THPKhugepagedError(THPError):
31 """
32 Thrown when khugepaged is not behaving as expected.
33 """
34 pass
37 class TransparentHugePageConfig(object):
38 def __init__(self, test, params):
39 """
40 Find paths for transparent hugepages and kugepaged configuration. Also,
41 back up original host configuration so it can be restored during
42 cleanup.
43 """
44 self.params = params
46 RH_THP_PATH = "/sys/kernel/mm/redhat_transparent_hugepage"
47 UPSTREAM_THP_PATH = "/sys/kernel/mm/transparent_hugepage"
48 if os.path.isdir(RH_THP_PATH):
49 self.thp_path = RH_THP_PATH
50 elif os.path.isdir(UPSTREAM_THP_PATH):
51 self.thp_path = UPSTREAM_THP_PATH
52 else:
53 raise THPNotSupportedError("System doesn't support transparent "
54 "hugepages")
56 tmp_list = []
57 test_cfg = {}
58 test_config = self.params.get("test_config", None)
59 if test_config is not None:
60 tmp_list = re.split(';', test_config)
61 while len(tmp_list) > 0:
62 tmp_cfg = tmp_list.pop()
63 test_cfg[re.split(":", tmp_cfg)[0]] = sre.split(":", tmp_cfg)[1]
64 # Save host current config, so we can restore it during cleanup
65 # We will only save the writeable part of the config files
66 original_config = {}
67 # List of files that contain string config values
68 self.file_list_str = []
69 # List of files that contain integer config values
70 self.file_list_num = []
71 for f in os.walk(self.thp_path):
72 base_dir = f[0]
73 if f[2]:
74 for name in f[2]:
75 f_dir = os.path.join(base_dir, name)
76 parameter = file(f_dir, 'r').read()
77 try:
78 # Verify if the path in question is writable
79 f = open(f_dir, 'w')
80 f.close()
81 if re.findall("\[(.*)\]", parameter):
82 original_config[f_dir] = re.findall("\[(.*)\]",
83 parameter)[0]
84 self.file_list_str.append(f_dir)
85 else:
86 original_config[f_dir] = int(parameter)
87 self.file_list_num.append(f_dir)
88 except IOError:
89 pass
91 self.test_config = test_cfg
92 self.original_config = original_config
95 def set_env(self):
96 """
97 Applies test configuration on the host.
98 """
99 if self.test_config:
100 for path in self.test_config.keys():
101 file(path, 'w').write(self.test_config[path])
104 def value_listed(self, value):
106 Get a parameters list from a string
108 value_list = []
109 for i in re.split("\[|\]|\n+|\s+", value):
110 if i:
111 value_list.append(i)
112 return value_list
115 def khugepaged_test(self):
117 Start, stop and frequency change test for khugepaged.
119 def check_status_with_value(action_list, file_name):
121 Check the status of khugepaged when set value to specify file.
123 for (a, r) in action_list:
124 open(file_name, "w").write(a)
125 time.sleep(5)
126 try:
127 utils.run('pgrep khugepaged')
128 if r != 0:
129 raise THPKhugepagedError("Khugepaged still alive when"
130 "transparent huge page is "
131 "disabled")
132 except error.CmdError:
133 if r == 0:
134 raise THPKhugepagedError("Khugepaged could not be set to"
135 "status %s" % a)
138 for file_path in self.file_list_str:
139 action_list = []
140 if re.findall("enabled", file_path):
141 # Start and stop test for khugepaged
142 value_list = self.value_listed(open(file_path,"r").read())
143 for i in value_list:
144 if re.match("n", i, re.I):
145 action_stop = (i, 256)
146 for i in value_list:
147 if re.match("[^n]", i, re.I):
148 action = (i, 0)
149 action_list += [action_stop, action, action_stop]
150 action_list += [action]
152 check_status_with_value(action_list, file_path)
153 else:
154 value_list = self.value_listed(open(file_path,"r").read())
155 for i in value_list:
156 action = (i, 0)
157 action_list.append(action)
158 check_status_with_value(action_list, file_path)
160 for file_path in self.file_list_num:
161 action_list = []
162 value = int(open(file_path, "r").read())
163 if value != 0 and value != 1:
164 new_value = random.random()
165 action_list.append((str(int(value * new_value)),0))
166 action_list.append((str(int(value * ( new_value + 1))),0))
167 else:
168 action_list.append(("0", 0))
169 action_list.append(("1", 0))
171 check_status_with_value(action_list, file_path)
174 def setup(self):
176 Configure host for testing. Also, check that khugepaged is working as
177 expected.
179 self.set_env()
180 self.khugepaged_test()
183 def cleanup(self):
184 """:
185 Restore the host's original configuration after test
187 for path in self.original_config:
188 p_file = open(path, 'w')
189 p_file.write(str(self.original_config[path]))
190 p_file.close()
193 class HugePageConfig(object):
194 def __init__(self, params):
196 Gets environment variable values and calculates the target number
197 of huge memory pages.
199 @param params: Dict like object containing parameters for the test.
201 self.vms = len(params.objects("vms"))
202 self.mem = int(params.get("mem"))
203 self.max_vms = int(params.get("max_vms", 0))
204 self.hugepage_path = '/mnt/kvm_hugepage'
205 self.hugepage_size = self.get_hugepage_size()
206 self.target_hugepages = self.get_target_hugepages()
207 self.kernel_hp_file = '/proc/sys/vm/nr_hugepages'
210 def get_hugepage_size(self):
212 Get the current system setting for huge memory page size.
214 meminfo = open('/proc/meminfo', 'r').readlines()
215 huge_line_list = [h for h in meminfo if h.startswith("Hugepagesize")]
216 try:
217 return int(huge_line_list[0].split()[1])
218 except ValueError, e:
219 raise ValueError("Could not get huge page size setting from "
220 "/proc/meminfo: %s" % e)
223 def get_target_hugepages(self):
225 Calculate the target number of hugepages for testing purposes.
227 if self.vms < self.max_vms:
228 self.vms = self.max_vms
229 # memory of all VMs plus qemu overhead of 64MB per guest
230 vmsm = (self.vms * self.mem) + (self.vms * 64)
231 return int(vmsm * 1024 / self.hugepage_size)
234 @error.context_aware
235 def set_hugepages(self):
237 Sets the hugepage limit to the target hugepage value calculated.
239 error.context("setting hugepages limit to %s" % self.target_hugepages)
240 hugepage_cfg = open(self.kernel_hp_file, "r+")
241 hp = hugepage_cfg.readline()
242 while int(hp) < self.target_hugepages:
243 loop_hp = hp
244 hugepage_cfg.write(str(self.target_hugepages))
245 hugepage_cfg.flush()
246 hugepage_cfg.seek(0)
247 hp = int(hugepage_cfg.readline())
248 if loop_hp == hp:
249 raise ValueError("Cannot set the kernel hugepage setting "
250 "to the target value of %d hugepages." %
251 self.target_hugepages)
252 hugepage_cfg.close()
253 logging.debug("Successfuly set %s large memory pages on host ",
254 self.target_hugepages)
257 @error.context_aware
258 def mount_hugepage_fs(self):
260 Verify if there's a hugetlbfs mount set. If there's none, will set up
261 a hugetlbfs mount using the class attribute that defines the mount
262 point.
264 error.context("mounting hugepages path")
265 if not os.path.ismount(self.hugepage_path):
266 if not os.path.isdir(self.hugepage_path):
267 os.makedirs(self.hugepage_path)
268 cmd = "mount -t hugetlbfs none %s" % self.hugepage_path
269 utils.system(cmd)
272 def setup(self):
273 logging.debug("Number of VMs this test will use: %d", self.vms)
274 logging.debug("Amount of memory used by each vm: %s", self.mem)
275 logging.debug("System setting for large memory page size: %s",
276 self.hugepage_size)
277 logging.debug("Number of large memory pages needed for this test: %s",
278 self.target_hugepages)
279 self.set_hugepages()
280 self.mount_hugepage_fs()
283 @error.context_aware
284 def cleanup(self):
285 error.context("trying to dealocate hugepage memory")
286 try:
287 utils.system("umount %s" % self.hugepage_path)
288 except error.CmdError:
289 return
290 utils.system("echo 0 > %s" % self.kernel_hp_file)
291 logging.debug("Hugepage memory successfuly dealocated")
294 class PrivateBridgeError(Exception):
295 def __init__(self, brname):
296 self.brname = brname
298 def __str__(self):
299 return "Bridge %s not available after setup" % self.brname
302 class PrivateBridgeConfig(object):
303 __shared_state = {}
304 def __init__(self, params=None):
305 self.__dict__ = self.__shared_state
306 if params is not None:
307 self.brname = params.get("priv_brname", 'atbr0')
308 self.subnet = params.get("priv_subnet", '192.168.58')
309 self.ip_version = params.get("bridge_ip_version", "ipv4")
310 self.dhcp_server_pid = None
311 ports = params.get("priv_bridge_ports", '53 67').split()
312 s_port = params.get("guest_port_remote_shell", "10022")
313 if s_port not in ports:
314 ports.append(s_port)
315 ft_port = params.get("guest_port_file_transfer", "10023")
316 if ft_port not in ports:
317 ports.append(ft_port)
318 u_port = params.get("guest_port_unattended_install", "13323")
319 if u_port not in ports:
320 ports.append(u_port)
321 self.iptables_rules = self._assemble_iptables_rules(ports)
324 def _assemble_iptables_rules(self, port_list):
325 rules = []
326 index = 0
327 for port in port_list:
328 index += 1
329 rules.append("INPUT %s -i %s -p tcp -m tcp --dport %s -j ACCEPT" %
330 (index, self.brname, port))
331 index += 1
332 rules.append("INPUT %s -i %s -p udp -m udp --dport %s -j ACCEPT" %
333 (index, self.brname, port))
334 rules.append("FORWARD 1 -m physdev --physdev-is-bridged -j ACCEPT")
335 rules.append("FORWARD 2 -d %s.0/24 -o %s -m state "
336 "--state RELATED,ESTABLISHED -j ACCEPT" %
337 (self.subnet, self.brname))
338 rules.append("FORWARD 3 -s %s.0/24 -i %s -j ACCEPT" %
339 (self.subnet, self.brname))
340 rules.append("FORWARD 4 -i %s -o %s -j ACCEPT" %
341 (self.brname, self.brname))
342 return rules
345 def _add_bridge(self):
346 utils.system("brctl addbr %s" % self.brname)
347 ip_fwd_path = "/proc/sys/net/%s/ip_forward" % self.ip_version
348 ip_fwd = open(ip_fwd_path, "w")
349 ip_fwd.write("1\n")
350 utils.system("brctl stp %s on" % self.brname)
351 utils.system("brctl setfd %s 0" % self.brname)
354 def _bring_bridge_up(self):
355 utils.system("ifconfig %s %s.1 up" % (self.brname, self.subnet))
358 def _iptables_add(self, cmd):
359 return utils.system("iptables -I %s" % cmd)
362 def _iptables_del(self, cmd):
363 return utils.system("iptables -D %s" % cmd)
366 def _enable_nat(self):
367 for rule in self.iptables_rules:
368 self._iptables_add(rule)
371 def _start_dhcp_server(self):
372 utils.run("service dnsmasq stop")
373 utils.run("dnsmasq --strict-order --bind-interfaces "
374 "--listen-address %s.1 --dhcp-range %s.2,%s.254 "
375 "--dhcp-lease-max=253 "
376 "--dhcp-no-override "
377 "--pid-file=/tmp/dnsmasq.pid "
378 "--log-facility=/tmp/dnsmasq.log" %
379 (self.subnet, self.subnet, self.subnet))
380 self.dhcp_server_pid = None
381 try:
382 self.dhcp_server_pid = int(open('/tmp/dnsmasq.pid', 'r').read())
383 except ValueError:
384 raise PrivateBridgeError(self.brname)
385 logging.debug("Started internal DHCP server with PID %s",
386 self.dhcp_server_pid)
389 def _verify_bridge(self):
390 brctl_output = utils.system_output("brctl show")
391 if self.brname not in brctl_output:
392 raise PrivateBridgeError(self.brname)
395 def setup(self):
396 brctl_output = utils.system_output("brctl show")
397 if self.brname not in brctl_output:
398 logging.debug("Configuring KVM test private bridge %s", self.brname)
399 try:
400 self._add_bridge()
401 except:
402 self._remove_bridge()
403 raise
404 try:
405 self._bring_bridge_up()
406 except:
407 self._bring_bridge_down()
408 self._remove_bridge()
409 raise
410 try:
411 self._enable_nat()
412 except:
413 self._disable_nat()
414 self._bring_bridge_down()
415 self._remove_bridge()
416 raise
417 try:
418 self._start_dhcp_server()
419 except:
420 self._stop_dhcp_server()
421 self._disable_nat()
422 self._bring_bridge_down()
423 self._remove_bridge()
424 raise
425 self._verify_bridge()
428 def _stop_dhcp_server(self):
429 if self.dhcp_server_pid is not None:
430 try:
431 os.kill(self.dhcp_server_pid, 15)
432 except OSError:
433 pass
434 else:
435 try:
436 dhcp_server_pid = int(open('/tmp/dnsmasq.pid', 'r').read())
437 except ValueError:
438 return
439 try:
440 os.kill(dhcp_server_pid, 15)
441 except OSError:
442 pass
445 def _bring_bridge_down(self):
446 utils.system("ifconfig %s down" % self.brname, ignore_status=True)
449 def _disable_nat(self):
450 for rule in self.iptables_rules:
451 split_list = rule.split(' ')
452 # We need to remove numbering here
453 split_list.pop(1)
454 rule = " ".join(split_list)
455 self._iptables_del(rule)
458 def _remove_bridge(self):
459 utils.system("brctl delbr %s" % self.brname, ignore_status=True)
462 def cleanup(self):
463 brctl_output = utils.system_output("brctl show")
464 cleanup = False
465 for line in brctl_output.split("\n"):
466 if line.startswith(self.brname):
467 # len == 4 means there is a TAP using the bridge
468 # so don't try to clean it up
469 if len(line.split()) < 4:
470 cleanup = True
471 break
472 if cleanup:
473 logging.debug("Cleaning up KVM test private bridge %s", self.brname)
474 self._stop_dhcp_server()
475 self._disable_nat()
476 self._bring_bridge_down()
477 self._remove_bridge()