2 High-level KVM test utility functions.
4 This module is meant to reduce code size by performing common test procedures.
5 Generally, code here should look like test code.
7 - Functions in this module should raise exceptions if things go wrong
8 (unlike functions in kvm_utils.py and kvm_vm.py which report failure via
9 their returned values).
10 - Functions in this module may use logging.info(), in addition to
11 logging.debug() and logging.error(), to log messages the user may be
12 interested in (unlike kvm_utils.py and kvm_vm.py which use
13 logging.debug() for anything that isn't an error).
14 - Functions in this module typically use functions and classes from
15 lower-level modules (e.g. kvm_utils.py, kvm_vm.py, kvm_subprocess.py).
16 - Functions in this module should not be used by lower-level modules.
17 - Functions in this module should be used in the right context.
18 For example, a function should not be used where it may display
19 misleading or inaccurate info or debug messages.
21 @copyright: 2008-2009 Red Hat Inc.
24 import time
, os
, logging
, re
, signal
25 from autotest_lib
.client
.common_lib
import error
26 from autotest_lib
.client
.bin
import utils
27 from autotest_lib
.client
.tools
import scan_results
28 import aexpect
, virt_utils
, virt_vm
31 def get_living_vm(env
, vm_name
):
33 Get a VM object from the environment and make sure it's alive.
35 @param env: Dictionary with test environment.
36 @param vm_name: Name of the desired VM object.
39 vm
= env
.get_vm(vm_name
)
41 raise error
.TestError("VM '%s' not found in environment" % vm_name
)
43 raise error
.TestError("VM '%s' seems to be dead; test requires a "
44 "living VM" % vm_name
)
48 def wait_for_login(vm
, nic_index
=0, timeout
=240, start
=0, step
=2, serial
=None):
50 Try logging into a VM repeatedly. Stop on success or when timeout expires.
53 @param nic_index: Index of NIC to access in the VM.
54 @param timeout: Time to wait before giving up.
55 @param serial: Whether to use a serial connection instead of a remote
57 @return: A shell session object.
59 end_time
= time
.time() + timeout
63 logging
.info("Trying to log into guest %s using serial connection,"
64 " timeout %ds", vm
.name
, timeout
)
66 while time
.time() < end_time
:
68 session
= vm
.serial_login()
70 except virt_utils
.LoginError
, e
:
75 logging
.info("Trying to log into guest %s using remote connection,"
76 " timeout %ds", vm
.name
, timeout
)
78 while time
.time() < end_time
:
80 session
= vm
.login(nic_index
=nic_index
)
82 except (virt_utils
.LoginError
, virt_vm
.VMError
), e
:
86 raise error
.TestFail("Could not log into guest %s using %s connection" %
88 logging
.info("Logged into guest %s using %s connection", vm
.name
, type)
92 def reboot(vm
, session
, method
="shell", sleep_before_reset
=10, nic_index
=0,
95 Reboot the VM and wait for it to come back up by trying to log in until
99 @param session: A shell session object.
100 @param method: Reboot method. Can be "shell" (send a shell reboot
101 command) or "system_reset" (send a system_reset monitor command).
102 @param nic_index: Index of NIC to access in the VM, when logging in after
104 @param timeout: Time to wait before giving up (after rebooting).
105 @return: A new shell session object.
107 if method
== "shell":
108 # Send a reboot command to the guest's shell
109 session
.sendline(vm
.get_params().get("reboot_command"))
110 logging
.info("Reboot command sent. Waiting for guest to go down")
111 elif method
== "system_reset":
112 # Sleep for a while before sending the command
113 time
.sleep(sleep_before_reset
)
114 # Clear the event list of all QMP monitors
115 monitors
= [m
for m
in vm
.monitors
if m
.protocol
== "qmp"]
118 # Send a system_reset monitor command
119 vm
.monitor
.cmd("system_reset")
120 logging
.info("Monitor command system_reset sent. Waiting for guest to "
122 # Look for RESET QMP events
125 if not m
.get_event("RESET"):
126 raise error
.TestFail("RESET QMP event not received after "
127 "system_reset (monitor '%s')" % m
.name
)
129 logging
.info("RESET QMP event received")
131 logging
.error("Unknown reboot method: %s", method
)
133 # Wait for the session to become unresponsive and close it
134 if not virt_utils
.wait_for(lambda: not session
.is_responsive(timeout
=30),
136 raise error
.TestFail("Guest refuses to go down")
139 # Try logging into the guest until timeout expires
140 logging
.info("Guest is down. Waiting for it to go up again, timeout %ds",
142 session
= vm
.wait_for_login(nic_index
, timeout
=timeout
)
143 logging
.info("Guest is up again")
147 def migrate(vm
, env
=None, mig_timeout
=3600, mig_protocol
="tcp",
148 mig_cancel
=False, offline
=False, stable_check
=False,
149 clean
=False, save_path
=None, dest_host
='localhost', mig_port
=None):
151 Migrate a VM locally and re-register it in the environment.
153 @param vm: The VM to migrate.
154 @param env: The environment dictionary. If omitted, the migrated VM will
156 @param mig_timeout: timeout value for migration.
157 @param mig_protocol: migration protocol
158 @param mig_cancel: Test migrate_cancel or not when protocol is tcp.
159 @param dest_host: Destination host (defaults to 'localhost').
160 @param mig_port: Port that will be used for migration.
161 @return: The post-migration VM, in case of same host migration, True in
162 case of multi-host migration.
165 o
= vm
.monitor
.info("migrate")
166 if isinstance(o
, str):
167 return "status: active" not in o
169 return o
.get("status") != "active"
172 o
= vm
.monitor
.info("migrate")
173 if isinstance(o
, str):
174 return "status: completed" in o
176 return o
.get("status") == "completed"
179 o
= vm
.monitor
.info("migrate")
180 if isinstance(o
, str):
181 return "status: failed" in o
183 return o
.get("status") == "failed"
186 o
= vm
.monitor
.info("migrate")
187 if isinstance(o
, str):
188 return ("Migration status: cancelled" in o
or
189 "Migration status: canceled" in o
)
191 return (o
.get("status") == "cancelled" or
192 o
.get("status") == "canceled")
194 def wait_for_migration():
195 if not virt_utils
.wait_for(mig_finished
, mig_timeout
, 2, 2,
196 "Waiting for migration to finish"):
197 raise error
.TestFail("Timeout expired while waiting for migration "
200 if dest_host
== 'localhost':
203 if (dest_host
== 'localhost') and stable_check
:
204 # Pause the dest vm after creation
205 dest_vm
.params
['extra_params'] = (dest_vm
.params
.get('extra_params','')
208 if dest_host
== 'localhost':
209 dest_vm
.create(migration_mode
=mig_protocol
, mac_source
=vm
)
213 if mig_protocol
== "tcp":
214 if dest_host
== 'localhost':
215 uri
= "tcp:localhost:%d" % dest_vm
.migration_port
217 uri
= 'tcp:%s:%d' % (dest_host
, mig_port
)
218 elif mig_protocol
== "unix":
219 uri
= "unix:%s" % dest_vm
.migration_file
220 elif mig_protocol
== "exec":
221 uri
= '"exec:nc localhost %s"' % dest_vm
.migration_port
224 vm
.monitor
.cmd("stop")
225 vm
.monitor
.migrate(uri
)
229 vm
.monitor
.cmd("migrate_cancel")
230 if not virt_utils
.wait_for(mig_cancelled
, 60, 2, 2,
231 "Waiting for migration "
233 raise error
.TestFail("Failed to cancel migration")
235 vm
.monitor
.cmd("cont")
236 if dest_host
== 'localhost':
237 dest_vm
.destroy(gracefully
=False)
241 if (dest_host
== 'localhost') and stable_check
:
242 save_path
= None or "/tmp"
243 save1
= os
.path
.join(save_path
, "src")
244 save2
= os
.path
.join(save_path
, "dst")
246 vm
.save_to_file(save1
)
247 dest_vm
.save_to_file(save2
)
249 # Fail if we see deltas
250 md5_save1
= utils
.hash_file(save1
)
251 md5_save2
= utils
.hash_file(save2
)
252 if md5_save1
!= md5_save2
:
253 raise error
.TestFail("Mismatch of VM state before "
254 "and after migration")
256 if (dest_host
== 'localhost') and offline
:
257 dest_vm
.monitor
.cmd("cont")
259 if dest_host
== 'localhost':
264 if (dest_host
== 'localhost') and stable_check
and clean
:
265 logging
.debug("Cleaning the state files")
266 if os
.path
.isfile(save1
):
268 if os
.path
.isfile(save2
):
271 # Report migration status
273 logging
.info("Migration finished successfully")
275 raise error
.TestFail("Migration failed")
277 raise error
.TestFail("Migration ended with unknown status")
279 if dest_host
== 'localhost':
280 if "paused" in dest_vm
.monitor
.info("status"):
281 logging
.debug("Destination VM is paused, resuming it")
282 dest_vm
.monitor
.cmd("cont")
285 vm
.destroy(gracefully
=False)
287 # Replace the source VM with the new cloned VM
288 if (dest_host
== 'localhost') and (env
is not None):
289 env
.register_vm(vm
.name
, dest_vm
)
291 # Return the new cloned VM
292 if dest_host
== 'localhost':
298 def stop_windows_service(session
, service
, timeout
=120):
300 Stop a Windows service using sc.
301 If the service is already stopped or is not installed, do nothing.
303 @param service: The name of the service
304 @param timeout: Time duration to wait for service to stop
305 @raise error.TestError: Raised if the service can't be stopped
307 end_time
= time
.time() + timeout
308 while time
.time() < end_time
:
309 o
= session
.cmd_output("sc stop %s" % service
, timeout
=60)
310 # FAILED 1060 means the service isn't installed.
311 # FAILED 1062 means the service hasn't been started.
312 if re
.search(r
"\bFAILED (1060|1062)\b", o
, re
.I
):
316 raise error
.TestError("Could not stop service '%s'" % service
)
319 def start_windows_service(session
, service
, timeout
=120):
321 Start a Windows service using sc.
322 If the service is already running, do nothing.
323 If the service isn't installed, fail.
325 @param service: The name of the service
326 @param timeout: Time duration to wait for service to start
327 @raise error.TestError: Raised if the service can't be started
329 end_time
= time
.time() + timeout
330 while time
.time() < end_time
:
331 o
= session
.cmd_output("sc start %s" % service
, timeout
=60)
332 # FAILED 1060 means the service isn't installed.
333 if re
.search(r
"\bFAILED 1060\b", o
, re
.I
):
334 raise error
.TestError("Could not start service '%s' "
335 "(service not installed)" % service
)
336 # FAILED 1056 means the service is already running.
337 if re
.search(r
"\bFAILED 1056\b", o
, re
.I
):
341 raise error
.TestError("Could not start service '%s'" % service
)
344 def get_time(session
, time_command
, time_filter_re
, time_format
):
346 Return the host time and guest time. If the guest time cannot be fetched
347 a TestError exception is raised.
349 Note that the shell session should be ready to receive commands
350 (i.e. should "display" a command prompt and should be done with all
353 @param session: A shell session.
354 @param time_command: Command to issue to get the current guest time.
355 @param time_filter_re: Regex filter to apply on the output of
356 time_command in order to get the current time.
357 @param time_format: Format string to pass to time.strptime() with the
358 result of the regex filter.
359 @return: A tuple containing the host time and guest time.
361 if len(re
.findall("ntpdate|w32tm", time_command
)) == 0:
362 host_time
= time
.time()
363 s
= session
.cmd_output(time_command
)
366 s
= re
.findall(time_filter_re
, s
)[0]
368 logging
.debug("The time string from guest is:\n%s", s
)
369 raise error
.TestError("The time string from guest is unexpected.")
371 logging
.debug("(time_filter_re, time_string): (%s, %s)",
375 guest_time
= time
.mktime(time
.strptime(s
, time_format
))
377 o
= session
.cmd(time_command
)
378 if re
.match('ntpdate', time_command
):
379 offset
= re
.findall('offset (.*) sec', o
)[0]
380 host_main
, host_mantissa
= re
.findall(time_filter_re
, o
)[0]
381 host_time
= (time
.mktime(time
.strptime(host_main
, time_format
)) +
382 float("0.%s" % host_mantissa
))
383 guest_time
= host_time
- float(offset
)
385 guest_time
= re
.findall(time_filter_re
, o
)[0]
386 offset
= re
.findall("o:(.*)s", o
)[0]
387 if re
.match('PM', guest_time
):
388 hour
= re
.findall('\d+ (\d+):', guest_time
)[0]
389 hour
= str(int(hour
) + 12)
390 guest_time
= re
.sub('\d+\s\d+:', "\d+\s%s:" % hour
,
393 guest_time
= guest_time
[:-3]
394 guest_time
= time
.mktime(time
.strptime(guest_time
, time_format
))
395 host_time
= guest_time
+ float(offset
)
397 return (host_time
, guest_time
)
400 def get_memory_info(lvms
):
402 Get memory information from host and guests in format:
403 Host: memfree = XXXM; Guests memsh = {XXX,XXX,...}
405 @params lvms: List of VM objects
406 @return: String with memory info report
408 if not isinstance(lvms
, list):
409 raise error
.TestError("Invalid list passed to get_stat: %s " % lvms
)
412 meminfo
= "Host: memfree = "
413 meminfo
+= str(int(utils
.freememtotal()) / 1024) + "M; "
414 meminfo
+= "swapfree = "
415 mf
= int(utils
.read_from_meminfo("SwapFree")) / 1024
416 meminfo
+= str(mf
) + "M; "
418 raise error
.TestFail("Could not fetch host free memory info, "
421 meminfo
+= "Guests memsh = {"
423 shm
= vm
.get_shared_meminfo()
425 raise error
.TestError("Could not get shared meminfo from "
427 meminfo
+= "%dM; " % shm
428 meminfo
= meminfo
[0:-2] + "}"
433 def run_autotest(vm
, session
, control_path
, timeout
, outputdir
, params
):
435 Run an autotest control file inside a guest (linux only utility).
437 @param vm: VM object.
438 @param session: A shell session on the VM provided.
439 @param control_path: A path to an autotest control file.
440 @param timeout: Timeout under which the autotest control file must complete.
441 @param outputdir: Path on host where we should copy the guest autotest
444 The following params is used by the migration
445 @param params: Test params used in the migration test
447 def copy_if_hash_differs(vm
, local_path
, remote_path
):
449 Copy a file to a guest if it doesn't exist or if its MD5sum differs.
451 @param vm: VM object.
452 @param local_path: Local path.
453 @param remote_path: Remote path.
455 local_hash
= utils
.hash_file(local_path
)
456 basename
= os
.path
.basename(local_path
)
457 output
= session
.cmd_output("md5sum %s" % remote_path
)
458 if "such file" in output
:
461 remote_hash
= output
.split()[0]
463 logging
.warning("MD5 check for remote path %s did not return.",
465 # Let's be a little more lenient here and see if it wasn't a
468 if remote_hash
!= local_hash
:
469 logging
.debug("Copying %s to guest", basename
)
470 vm
.copy_files_to(local_path
, remote_path
)
473 def extract(vm
, remote_path
, dest_dir
="."):
475 Extract a .tar.bz2 file on the guest.
478 @param remote_path: Remote file path
479 @param dest_dir: Destination dir for the contents
481 basename
= os
.path
.basename(remote_path
)
482 logging
.debug("Extracting %s on VM %s", basename
, vm
.name
)
483 e_cmd
= "tar xjvf %s -C %s" % (remote_path
, dest_dir
)
484 session
.cmd(e_cmd
, timeout
=120)
489 Copy autotest results present on the guest back to the host.
491 logging
.debug("Trying to copy autotest results from guest")
492 guest_results_dir
= os
.path
.join(outputdir
, "guest_autotest_results")
493 if not os
.path
.exists(guest_results_dir
):
494 os
.mkdir(guest_results_dir
)
495 vm
.copy_files_from("%s/results/default/*" % autotest_path
,
499 def get_results_summary():
501 Get the status of the tests that were executed on the host and close
502 the session where autotest was being executed.
504 output
= session
.cmd_output("cat results/*/status")
506 results
= scan_results
.parse_results(output
)
507 # Report test results
508 logging
.info("Results (test, status, duration, info):")
509 for result
in results
:
510 logging
.info(str(result
))
514 logging
.error("Error processing guest autotest results: %s", e
)
518 if not os
.path
.isfile(control_path
):
519 raise error
.TestError("Invalid path to autotest control file: %s" %
522 migrate_background
= params
.get("migrate_background") == "yes"
523 if migrate_background
:
524 mig_timeout
= float(params
.get("mig_timeout", "3600"))
525 mig_protocol
= params
.get("migration_protocol", "tcp")
527 compressed_autotest_path
= os
.path
.relpath("/tmp/autotest.tar.bz2")
529 # To avoid problems, let's make the test use the current AUTODIR
530 # (autotest client path) location
531 autotest_path
= os
.path
.relpath(os
.environ
['AUTODIR'])
533 # tar the contents of bindir/autotest
534 cmd
= "tar cvjf %s %s/*" % (compressed_autotest_path
, autotest_path
)
535 # Until we have nested virtualization, we don't need the kvm test :)
536 cmd
+= " --exclude=%s/tests/kvm" % autotest_path
537 cmd
+= " --exclude=%s/results" % autotest_path
538 cmd
+= " --exclude=%s/tmp" % autotest_path
539 cmd
+= " --exclude=%s/control*" % autotest_path
540 cmd
+= " --exclude=*.pyc"
541 cmd
+= " --exclude=*.svn"
542 cmd
+= " --exclude=*.git"
545 # Copy autotest.tar.bz2
546 copy_if_hash_differs(vm
, compressed_autotest_path
, compressed_autotest_path
)
548 # Extract autotest.tar.bz2
549 extract(vm
, compressed_autotest_path
, "/")
551 vm
.copy_files_to(control_path
, os
.path
.join(autotest_path
, 'control'))
554 logging
.info("Running autotest control file %s on guest, timeout %ss",
555 os
.path
.basename(control_path
), timeout
)
556 session
.cmd("cd %s" % autotest_path
)
558 session
.cmd("rm -f control.state")
559 session
.cmd("rm -rf results/*")
560 except aexpect
.ShellError
:
565 logging
.info("---------------- Test output ----------------")
566 if migrate_background
:
567 mig_timeout
= float(params
.get("mig_timeout", "3600"))
568 mig_protocol
= params
.get("migration_protocol", "tcp")
570 bg
= virt_utils
.Thread(session
.cmd_output
,
571 kwargs
={'cmd': "bin/autotest control",
573 'print_func': logging
.info
})
578 logging
.info("Tests is not ended, start a round of"
580 vm
.migrate(timeout
=mig_timeout
, protocol
=mig_protocol
)
582 session
.cmd_output("bin/autotest control", timeout
=timeout
,
583 print_func
=logging
.info
)
585 logging
.info("------------- End of test output ------------")
586 if migrate_background
and bg
:
588 except aexpect
.ShellTimeoutError
:
591 get_results_summary()
592 raise error
.TestError("Timeout elapsed while waiting for job to "
595 raise error
.TestError("Autotest job on guest failed "
596 "(VM terminated during job)")
597 except aexpect
.ShellProcessTerminatedError
:
599 raise error
.TestError("Autotest job on guest failed "
600 "(Remote session terminated during job)")
602 results
= get_results_summary()
605 # Make a list of FAIL/ERROR/ABORT results (make sure FAIL results appear
606 # before ERROR results, and ERROR results appear before ABORT results)
607 bad_results
= [r
[0] for r
in results
if r
[1] == "FAIL"]
608 bad_results
+= [r
[0] for r
in results
if r
[1] == "ERROR"]
609 bad_results
+= [r
[0] for r
in results
if r
[1] == "ABORT"]
611 # Fail the test if necessary
613 raise error
.TestFail("Autotest control file run did not produce any "
614 "recognizable results")
616 if len(bad_results
) == 1:
617 e_msg
= ("Test %s failed during control file execution" %
620 e_msg
= ("Tests %s failed during control file execution" %
621 " ".join(bad_results
))
622 raise error
.TestFail(e_msg
)
625 def get_loss_ratio(output
):
627 Get the packet loss ratio from the output of ping
629 @param output: Ping output.
632 return int(re
.findall('(\d+)% packet loss', output
)[0])
634 logging
.debug(output
)
638 def raw_ping(command
, timeout
, session
, output_func
):
640 Low-level ping command execution.
642 @param command: Ping command.
643 @param timeout: Timeout of the ping command.
644 @param session: Local executon hint or session to execute the ping command.
647 process
= aexpect
.run_bg(command
, output_func
=output_func
,
650 # Send SIGINT signal to notify the timeout of running ping process,
651 # Because ping have the ability to catch the SIGINT signal so we can
652 # always get the packet loss ratio even if timeout.
653 if process
.is_alive():
654 virt_utils
.kill_process_tree(process
.get_pid(), signal
.SIGINT
)
656 status
= process
.get_status()
657 output
= process
.get_output()
660 return status
, output
664 output
= session
.cmd_output(command
, timeout
=timeout
,
665 print_func
=output_func
)
666 except aexpect
.ShellTimeoutError
:
667 # Send ctrl+c (SIGINT) through ssh session
670 output2
= session
.read_up_to_prompt(print_func
=output_func
)
672 except aexpect
.ExpectTimeoutError
, e
:
674 # We also need to use this session to query the return value
677 session
.sendline(session
.status_test_command
)
679 o2
= session
.read_up_to_prompt()
680 except aexpect
.ExpectError
:
684 status
= int(re
.findall("\d+", o2
)[0])
688 return status
, output
691 def ping(dest
=None, count
=None, interval
=None, interface
=None,
692 packetsize
=None, ttl
=None, hint
=None, adaptive
=False,
693 broadcast
=False, flood
=False, timeout
=0,
694 output_func
=logging
.debug
, session
=None):
698 @param dest: Destination address.
699 @param count: Count of icmp packet.
700 @param interval: Interval of two icmp echo request.
701 @param interface: Specified interface of the source address.
702 @param packetsize: Packet size of icmp.
703 @param ttl: IP time to live.
704 @param hint: Path mtu discovery hint.
705 @param adaptive: Adaptive ping flag.
706 @param broadcast: Broadcast ping flag.
707 @param flood: Flood ping flag.
708 @param timeout: Timeout for the ping command.
709 @param output_func: Function used to log the result of ping.
710 @param session: Local executon hint or session to execute the ping command.
713 command
= "ping %s " % dest
715 command
= "ping localhost "
716 if count
is not None:
717 command
+= " -c %s" % count
718 if interval
is not None:
719 command
+= " -i %s" % interval
720 if interface
is not None:
721 command
+= " -I %s" % interface
722 if packetsize
is not None:
723 command
+= " -s %s" % packetsize
725 command
+= " -t %s" % ttl
727 command
+= " -M %s" % hint
736 return raw_ping(command
, timeout
, session
, output_func
)
739 def get_linux_ifname(session
, mac_address
):
741 Get the interface name through the mac address.
743 @param session: session to the virtual machine
744 @mac_address: the macaddress of nic
747 output
= session
.cmd_output("ifconfig -a")
750 ethname
= re
.findall("(\w+)\s+Link.*%s" % mac_address
, output
,