2 KVM test utility functions.
4 @copyright: 2008-2009 Red Hat Inc.
7 import time
, string
, random
, socket
, os
, signal
, re
, logging
, commands
, cPickle
8 import fcntl
, shelve
, ConfigParser
, rss_file_transfer
, threading
, sys
, UserDict
9 from autotest_lib
.client
.bin
import utils
, os_dep
10 from autotest_lib
.client
.common_lib
import error
, logging_config
16 KOJI_INSTALLED
= False
19 def _lock_file(filename
):
20 f
= open(filename
, "w")
21 fcntl
.lockf(f
, fcntl
.LOCK_EX
)
26 fcntl
.lockf(f
, fcntl
.LOCK_UN
)
32 Tests whether a given object is a VM object.
34 @param obj: Python object.
36 return obj
.__class
__.__name
__ == "VM"
39 class Env(UserDict
.IterableUserDict
):
41 A dict-like object containing global objects used by tests.
43 def __init__(self
, filename
=None, version
=0):
45 Create an empty Env object or load an existing one from a file.
47 If the version recorded in the file is lower than version, or if some
48 error occurs during unpickling, or if filename is not supplied,
49 create an empty Env object.
51 @param filename: Path to an env file.
52 @param version: Required env version (int).
54 UserDict
.IterableUserDict
.__init
__(self
)
55 empty
= {"version": version
}
57 self
._filename
= filename
59 f
= open(filename
, "r")
62 if env
.get("version", 0) >= version
:
65 logging
.warn("Incompatible env file found. Not using it.")
67 # Almost any exception can be raised during unpickling, so let's
76 def save(self
, filename
=None):
78 Pickle the contents of the Env object into a file.
80 @param filename: Filename to pickle the dict into. If not supplied,
81 use the filename from which the dict was loaded.
83 filename
= filename
or self
._filename
84 f
= open(filename
, "w")
85 cPickle
.dump(self
.data
, f
)
89 def get_all_vms(self
):
91 Return a list of all VM objects in this Env object.
93 return [o
for o
in self
.values() if is_vm(o
)]
96 def get_vm(self
, name
):
98 Return a VM object by its name.
100 @param name: VM name.
102 return self
.get("vm__%s" % name
)
105 def register_vm(self
, name
, vm
):
107 Register a VM in this Env object.
109 @param name: VM name.
110 @param vm: VM object.
112 self
["vm__%s" % name
] = vm
115 def unregister_vm(self
, name
):
119 @param name: VM name.
121 del self
["vm__%s" % name
]
124 def register_installer(self
, installer
):
126 Register a installer that was just run
128 The installer will be available for other tests, so that
129 information about the installed KVM modules and qemu-kvm can be used by
132 self
['last_installer'] = installer
135 def previous_installer(self
):
137 Return the last installer that was registered
139 return self
.get('last_installer')
142 class Params(UserDict
.IterableUserDict
):
144 A dict-like object passed to every test.
146 def objects(self
, key
):
148 Return the names of objects defined using a given key.
150 @param key: The name of the key whose value lists the objects
153 return self
.get(key
, "").split()
156 def object_params(self
, obj_name
):
158 Return a dict-like object containing the parameters of an individual
161 This method behaves as follows: the suffix '_' + obj_name is removed
162 from all key names that have it. Other key names are left unchanged.
163 The values of keys with the suffix overwrite the values of their
166 @param obj_name: The name of the object (objects are listed by the
169 suffix
= "_" + obj_name
170 new_dict
= self
.copy()
172 if key
.endswith(suffix
):
173 new_key
= key
.split(suffix
)[0]
174 new_dict
[new_key
] = self
[key
]
178 # Functions related to MAC/IP addresses
180 def _open_mac_pool(lock_mode
):
181 lock_file
= open("/tmp/mac_lock", "w+")
182 fcntl
.lockf(lock_file
, lock_mode
)
183 pool
= shelve
.open("/tmp/address_pool")
184 return pool
, lock_file
187 def _close_mac_pool(pool
, lock_file
):
189 fcntl
.lockf(lock_file
, fcntl
.LOCK_UN
)
193 def _generate_mac_address_prefix(mac_pool
):
195 Generate a random MAC address prefix and add it to the MAC pool dictionary.
196 If there's a MAC prefix there already, do not update the MAC pool and just
197 return what's in there. By convention we will set KVM autotest MAC
198 addresses to start with 0x9a.
200 @param mac_pool: The MAC address pool object.
201 @return: The MAC address prefix.
203 if "prefix" in mac_pool
:
204 prefix
= mac_pool
["prefix"]
205 logging
.debug("Used previously generated MAC address prefix for this "
208 r
= random
.SystemRandom()
209 prefix
= "9a:%02x:%02x:%02x:" % (r
.randint(0x00, 0xff),
210 r
.randint(0x00, 0xff),
211 r
.randint(0x00, 0xff))
212 mac_pool
["prefix"] = prefix
213 logging
.debug("Generated MAC address prefix for this host: %s", prefix
)
217 def generate_mac_address(vm_instance
, nic_index
):
219 Randomly generate a MAC address and add it to the MAC address pool.
221 Try to generate a MAC address based on a randomly generated MAC address
222 prefix and add it to a persistent dictionary.
223 key = VM instance + NIC index, value = MAC address
224 e.g. {'20100310-165222-Wt7l:0': '9a:5d:94:6a:9b:f9'}
226 @param vm_instance: The instance attribute of a VM.
227 @param nic_index: The index of the NIC.
228 @return: MAC address string.
230 mac_pool
, lock_file
= _open_mac_pool(fcntl
.LOCK_EX
)
231 key
= "%s:%s" % (vm_instance
, nic_index
)
235 prefix
= _generate_mac_address_prefix(mac_pool
)
236 r
= random
.SystemRandom()
237 while key
not in mac_pool
:
238 mac
= prefix
+ "%02x:%02x" % (r
.randint(0x00, 0xff),
239 r
.randint(0x00, 0xff))
240 if mac
in mac_pool
.values():
243 logging
.debug("Generated MAC address for NIC %s: %s", key
, mac
)
244 _close_mac_pool(mac_pool
, lock_file
)
248 def free_mac_address(vm_instance
, nic_index
):
250 Remove a MAC address from the address pool.
252 @param vm_instance: The instance attribute of a VM.
253 @param nic_index: The index of the NIC.
255 mac_pool
, lock_file
= _open_mac_pool(fcntl
.LOCK_EX
)
256 key
= "%s:%s" % (vm_instance
, nic_index
)
258 logging
.debug("Freeing MAC address for NIC %s: %s", key
, mac_pool
[key
])
260 _close_mac_pool(mac_pool
, lock_file
)
263 def set_mac_address(vm_instance
, nic_index
, mac
):
265 Set a MAC address in the pool.
267 @param vm_instance: The instance attribute of a VM.
268 @param nic_index: The index of the NIC.
270 mac_pool
, lock_file
= _open_mac_pool(fcntl
.LOCK_EX
)
271 mac_pool
["%s:%s" % (vm_instance
, nic_index
)] = mac
272 _close_mac_pool(mac_pool
, lock_file
)
275 def get_mac_address(vm_instance
, nic_index
):
277 Return a MAC address from the pool.
279 @param vm_instance: The instance attribute of a VM.
280 @param nic_index: The index of the NIC.
281 @return: MAC address string.
283 mac_pool
, lock_file
= _open_mac_pool(fcntl
.LOCK_SH
)
284 mac
= mac_pool
.get("%s:%s" % (vm_instance
, nic_index
))
285 _close_mac_pool(mac_pool
, lock_file
)
289 def verify_ip_address_ownership(ip
, macs
, timeout
=10.0):
291 Use arping and the ARP cache to make sure a given IP address belongs to one
292 of the given MAC addresses.
294 @param ip: An IP address.
295 @param macs: A list or tuple of MAC addresses.
296 @return: True iff ip is assigned to a MAC address in macs.
298 # Compile a regex that matches the given IP address and any of the given
300 mac_regex
= "|".join("(%s)" % mac
for mac
in macs
)
301 regex
= re
.compile(r
"\b%s\b.*\b(%s)\b" % (ip
, mac_regex
), re
.IGNORECASE
)
303 # Check the ARP cache
304 o
= commands
.getoutput("%s -n" % find_command("arp"))
308 # Get the name of the bridge device for arping
309 o
= commands
.getoutput("%s route get %s" % (find_command("ip"), ip
))
310 dev
= re
.findall("dev\s+\S+", o
, re
.IGNORECASE
)
313 dev
= dev
[0].split()[-1]
315 # Send an ARP request
316 o
= commands
.getoutput("%s -f -c 3 -I %s %s" %
317 (find_command("arping"), dev
, ip
))
318 return bool(regex
.search(o
))
321 # Utility functions for dealing with external processes
323 def find_command(cmd
):
324 for dir in ["/usr/local/sbin", "/usr/local/bin",
325 "/usr/sbin", "/usr/bin", "/sbin", "/bin"]:
326 file = os
.path
.join(dir, cmd
)
327 if os
.path
.exists(file):
329 raise ValueError('Missing command: %s' % cmd
)
334 Return True if a given PID exists.
336 @param pid: Process ID number.
345 def safe_kill(pid
, signal
):
347 Attempt to send a signal to a given process that may or may not exist.
349 @param signal: Signal number.
358 def kill_process_tree(pid
, sig
=signal
.SIGKILL
):
359 """Signal a process and all of its children.
361 If the process does not exist -- return.
363 @param pid: The pid of the process to signal.
364 @param sig: The signal to send to the processes.
366 if not safe_kill(pid
, signal
.SIGSTOP
):
368 children
= commands
.getoutput("ps --ppid=%d -o pid=" % pid
).split()
369 for child
in children
:
370 kill_process_tree(int(child
), sig
)
372 safe_kill(pid
, signal
.SIGCONT
)
375 def get_latest_kvm_release_tag(release_listing
):
377 Fetches the latest release tag for KVM.
379 @param release_listing: URL that contains a list of the Source Forge
383 release_page
= utils
.urlopen(release_listing
)
384 data
= release_page
.read()
386 rx
= re
.compile("kvm-(\d+).tar.gz", re
.IGNORECASE
)
387 matches
= rx
.findall(data
)
388 # In all regexp matches to something that looks like a release tag,
389 # get the largest integer. That will be our latest release tag.
390 latest_tag
= max(int(x
) for x
in matches
)
391 return str(latest_tag
)
393 message
= "Could not fetch latest KVM release tag: %s" % str(e
)
394 logging
.error(message
)
395 raise error
.TestError(message
)
398 def get_git_branch(repository
, branch
, srcdir
, commit
=None, lbranch
=None):
400 Retrieves a given git code repository.
402 @param repository: Git repository URL
404 logging
.info("Fetching git [REP '%s' BRANCH '%s' COMMIT '%s'] -> %s",
405 repository
, branch
, commit
, srcdir
)
406 if not os
.path
.exists(srcdir
):
410 if os
.path
.exists(".git"):
411 utils
.system("git reset --hard")
413 utils
.system("git init")
418 utils
.system("git fetch -q -f -u -t %s %s:%s" %
419 (repository
, branch
, lbranch
))
420 utils
.system("git checkout %s" % lbranch
)
422 utils
.system("git checkout %s" % commit
)
424 h
= utils
.system_output('git log --pretty=format:"%H" -1')
426 desc
= "tag %s" % utils
.system_output("git describe")
427 except error
.CmdError
:
428 desc
= "no tag found"
430 logging
.info("Commit hash for %s is %s (%s)" % (repository
, h
.strip(),
435 def check_kvm_source_dir(source_dir
):
437 Inspects the kvm source directory and verifies its disposition. In some
438 occasions build may be dependant on the source directory disposition.
439 The reason why the return codes are numbers is that we might have more
440 changes on the source directory layout, so it's not scalable to just use
441 strings like 'old_repo', 'new_repo' and such.
443 @param source_dir: Source code path that will be inspected.
446 has_qemu_dir
= os
.path
.isdir('qemu')
447 has_kvm_dir
= os
.path
.isdir('kvm')
449 logging
.debug("qemu directory detected, source dir layout 1")
451 if has_kvm_dir
and not has_qemu_dir
:
452 logging
.debug("kvm directory detected, source dir layout 2")
455 raise error
.TestError("Unknown source dir layout, cannot proceed.")
458 # Functions and classes used for logging into guests and transferring files
460 class LoginError(Exception):
464 class LoginAuthenticationError(LoginError
):
468 class LoginTimeoutError(LoginError
):
469 def __init__(self
, output
):
470 LoginError
.__init
__(self
, output
)
474 return "Timeout expired (output so far: %r)" % self
.output
477 class LoginProcessTerminatedError(LoginError
):
478 def __init__(self
, status
, output
):
479 LoginError
.__init
__(self
, status
, output
)
484 return ("Client process terminated (status: %s, output: %r)" %
485 (self
.status
, self
.output
))
488 class LoginBadClientError(LoginError
):
489 def __init__(self
, client
):
490 LoginError
.__init
__(self
, client
)
494 return "Unknown remote shell client: %r" % self
.client
497 class SCPError(Exception):
501 class SCPAuthenticationError(SCPError
):
505 class SCPTransferTimeoutError(SCPError
):
506 def __init__(self
, output
):
507 SCPError
.__init
__(self
, output
)
511 return "Transfer timeout expired (output so far: %r)" % self
.output
514 class SCPTransferFailedError(SCPError
):
515 def __init__(self
, status
, output
):
516 SCPError
.__init
__(self
, status
, output
)
521 return "SCP transfer failed (status: %s, output: %r)" % (self
.status
,
525 def _remote_login(session
, username
, password
, prompt
, timeout
=10):
527 Log into a remote host (guest) using SSH or Telnet. Wait for questions
528 and provide answers. If timeout expires while waiting for output from the
529 child (e.g. a password prompt or a shell prompt) -- fail.
531 @brief: Log into a remote host (guest) using SSH or Telnet.
533 @param session: An Expect or ShellSession instance to operate on
534 @param username: The username to send in reply to a login prompt
535 @param password: The password to send in reply to a password prompt
536 @param prompt: The shell prompt that indicates a successful login
537 @param timeout: The maximal time duration (in seconds) to wait for each
538 step of the login procedure (i.e. the "Are you sure" prompt, the
539 password prompt, the shell prompt, etc)
541 @return: True on success and False otherwise.
543 password_prompt_count
= 0
544 login_prompt_count
= 0
548 match
, text
= session
.read_until_last_line_matches(
549 [r
"[Aa]re you sure", r
"[Pp]assword:\s*$", r
"[Ll]ogin:\s*$",
550 r
"[Cc]onnection.*closed", r
"[Cc]onnection.*refused",
551 r
"[Pp]lease wait", prompt
],
552 timeout
=timeout
, internal_timeout
=0.5)
553 if match
== 0: # "Are you sure you want to continue connecting"
554 logging
.debug("Got 'Are you sure...'; sending 'yes'")
555 session
.sendline("yes")
557 elif match
== 1: # "password:"
558 if password_prompt_count
== 0:
559 logging
.debug("Got password prompt; sending '%s'" % password
)
560 session
.sendline(password
)
561 password_prompt_count
+= 1
564 logging
.debug("Got password prompt again")
566 elif match
== 2: # "login:"
567 if login_prompt_count
== 0:
568 logging
.debug("Got username prompt; sending '%s'" % username
)
569 session
.sendline(username
)
570 login_prompt_count
+= 1
573 logging
.debug("Got username prompt again")
575 elif match
== 3: # "Connection closed"
576 logging
.debug("Got 'Connection closed'")
578 elif match
== 4: # "Connection refused"
579 logging
.debug("Got 'Connection refused'")
581 elif match
== 5: # "Please wait"
582 logging
.debug("Got 'Please wait'")
585 elif match
== 6: # prompt
586 logging
.debug("Got shell prompt -- logged in")
588 except kvm_subprocess
.ExpectTimeoutError
, e
:
589 logging
.debug("Timeout elapsed (output so far: %r)" % e
.output
)
591 except kvm_subprocess
.ExpectProcessTerminatedError
, e
:
592 logging
.debug("Process terminated (output so far: %r)" % e
.output
)
596 def _remote_scp(session
, password
, transfer_timeout
=600, login_timeout
=10):
598 Transfer file(s) to a remote host (guest) using SCP. Wait for questions
599 and provide answers. If login_timeout expires while waiting for output
600 from the child (e.g. a password prompt), fail. If transfer_timeout expires
601 while waiting for the transfer to complete, fail.
603 @brief: Transfer files using SCP, given a command line.
605 @param session: An Expect or ShellSession instance to operate on
606 @param password: The password to send in reply to a password prompt.
607 @param transfer_timeout: The time duration (in seconds) to wait for the
608 transfer to complete.
609 @param login_timeout: The maximal time duration (in seconds) to wait for
610 each step of the login procedure (i.e. the "Are you sure" prompt or
613 @return: True if the transfer succeeds and False on failure.
615 password_prompt_count
= 0
616 timeout
= login_timeout
620 match
, text
= session
.read_until_last_line_matches(
621 [r
"[Aa]re you sure", r
"[Pp]assword:\s*$", r
"lost connection"],
622 timeout
=timeout
, internal_timeout
=0.5)
623 if match
== 0: # "Are you sure you want to continue connecting"
624 logging
.debug("Got 'Are you sure...'; sending 'yes'")
625 session
.sendline("yes")
627 elif match
== 1: # "password:"
628 if password_prompt_count
== 0:
629 logging
.debug("Got password prompt; sending '%s'" % password
)
630 session
.sendline(password
)
631 password_prompt_count
+= 1
632 timeout
= transfer_timeout
635 logging
.debug("Got password prompt again")
637 elif match
== 2: # "lost connection"
638 logging
.debug("Got 'lost connection'")
640 except kvm_subprocess
.ExpectTimeoutError
, e
:
641 logging
.debug("Timeout expired")
643 except kvm_subprocess
.ExpectProcessTerminatedError
, e
:
644 logging
.debug("SCP process terminated with status %s", e
.status
)
648 def remote_login(client
, host
, port
, username
, password
, prompt
, linesep
="\n",
649 log_filename
=None, timeout
=10):
651 Log into a remote host (guest) using SSH/Telnet/Netcat.
653 @param client: The client to use ('ssh', 'telnet' or 'nc')
654 @param host: Hostname or IP address
655 @param port: Port to connect to
656 @param username: Username (if required)
657 @param password: Password (if required)
658 @param prompt: Shell prompt (regular expression)
659 @param linesep: The line separator to use when sending lines
660 (e.g. '\\n' or '\\r\\n')
661 @param log_filename: If specified, log all output to this file
662 @param timeout: The maximal time duration (in seconds) to wait for
663 each step of the login procedure (i.e. the "Are you sure" prompt
664 or the password prompt)
666 @return: ShellSession object on success and None on failure.
669 cmd
= ("ssh -o UserKnownHostsFile=/dev/null "
670 "-o PreferredAuthentications=password -p %s %s@%s" %
671 (port
, username
, host
))
672 elif client
== "telnet":
673 cmd
= "telnet -l %s %s %s" % (username
, host
, port
)
675 cmd
= "nc %s %s" % (host
, port
)
677 logging
.error("Unknown remote shell client: %s" % client
)
680 logging
.debug("Trying to login with command '%s'" % cmd
)
681 session
= kvm_subprocess
.ShellSession(cmd
, linesep
=linesep
, prompt
=prompt
)
682 if _remote_login(session
, username
, password
, prompt
, timeout
):
684 session
.set_output_func(log_line
)
685 session
.set_output_params((log_filename
,))
691 def remote_scp(command
, password
, log_filename
=None, transfer_timeout
=600,
694 Transfer file(s) to a remote host (guest) using SCP.
696 @brief: Transfer files using SCP, given a command line.
698 @param command: The command to execute
699 (e.g. "scp -r foobar root@localhost:/tmp/").
700 @param password: The password to send in reply to a password prompt.
701 @param log_filename: If specified, log all output to this file
702 @param transfer_timeout: The time duration (in seconds) to wait for the
703 transfer to complete.
704 @param login_timeout: The maximal time duration (in seconds) to wait for
705 each step of the login procedure (i.e. the "Are you sure" prompt
706 or the password prompt)
708 @return: True if the transfer succeeds and False on failure.
710 logging
.debug("Trying to SCP with command '%s', timeout %ss",
711 command
, transfer_timeout
)
714 output_func
= log_line
715 output_params
= (log_filename
,)
720 session
= kvm_subprocess
.Expect(command
,
721 output_func
=output_func
,
722 output_params
=output_params
)
724 return _remote_scp(session
, password
, transfer_timeout
, login_timeout
)
729 def copy_files_to(address
, client
, username
, password
, port
, local_path
,
730 remote_path
, log_filename
=None, timeout
=600):
732 Decide the transfer cleint and copy file to a remote host (guest).
734 @param client: Type of transfer client
735 @param username: Username (if required)
736 @param password: Password (if requried)
737 @param local_path: Path on the local machine where we are copying from
738 @param remote_path: Path on the remote machine where we are copying to
739 @param address: Address of remote host(guest)
740 @param log_filename: If specified, log all output to this file
741 @param timeout: The time duration (in seconds) to wait for the transfer to
744 @return: True on success and False on failure.
747 if not address
or not port
:
748 logging
.debug("IP address or port unavailable")
752 return scp_to_remote(address
, port
, username
, password
, local_path
,
753 remote_path
, log_filename
, timeout
)
754 elif client
== "rss":
755 c
= rss_file_transfer
.FileUploadClient(address
, port
)
756 c
.upload(local_path
, remote_path
, timeout
)
761 def copy_files_from(address
, client
, username
, password
, port
, local_path
,
762 remote_path
, log_filename
=None, timeout
=600):
764 Decide the transfer cleint and copy file from a remote host (guest).
766 @param client: Type of transfer client
767 @param username: Username (if required)
768 @param password: Password (if requried)
769 @param local_path: Path on the local machine where we are copying from
770 @param remote_path: Path on the remote machine where we are copying to
771 @param address: Address of remote host(guest)
772 @param log_filename: If specified, log all output to this file
773 @param timeout: The time duration (in seconds) to wait for the transfer to
776 @return: True on success and False on failure.
779 if not address
or not port
:
780 logging
.debug("IP address or port unavailable")
784 return scp_from_remote(address
, port
, username
, password
, remote_path
,
785 local_path
, log_filename
, timeout
)
786 elif client
== "rss":
787 c
= rss_file_transfer
.FileDownloadClient(address
, port
)
788 c
.download(remote_path
, local_path
, timeout
)
793 def scp_to_remote(host
, port
, username
, password
, local_path
, remote_path
,
794 log_filename
=None, timeout
=600):
796 Copy files to a remote host (guest) through scp.
798 @param host: Hostname or IP address
799 @param username: Username (if required)
800 @param password: Password (if required)
801 @param local_path: Path on the local machine where we are copying from
802 @param remote_path: Path on the remote machine where we are copying to
803 @param log_filename: If specified, log all output to this file
804 @param timeout: The time duration (in seconds) to wait for the transfer
807 @return: True on success and False on failure.
809 command
= ("scp -v -o UserKnownHostsFile=/dev/null "
810 "-o PreferredAuthentications=password -r -P %s %s %s@%s:%s" %
811 (port
, local_path
, username
, host
, remote_path
))
812 return remote_scp(command
, password
, log_filename
, timeout
)
815 def scp_from_remote(host
, port
, username
, password
, remote_path
, local_path
,
816 log_filename
=None, timeout
=600):
818 Copy files from a remote host (guest).
820 @param host: Hostname or IP address
821 @param username: Username (if required)
822 @param password: Password (if required)
823 @param local_path: Path on the local machine where we are copying from
824 @param remote_path: Path on the remote machine where we are copying to
825 @param log_filename: If specified, log all output to this file
826 @param timeout: The time duration (in seconds) to wait for the transfer
829 @return: True on success and False on failure.
831 command
= ("scp -v -o UserKnownHostsFile=/dev/null "
832 "-o PreferredAuthentications=password -r -P %s %s@%s:%s %s" %
833 (port
, username
, host
, remote_path
, local_path
))
834 return remote_scp(command
, password
, log_filename
, timeout
)
837 # The following are utility functions related to ports.
839 def is_port_free(port
, address
):
841 Return True if the given port is available for use.
843 @param port: Port number
847 #s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
848 if address
== "localhost":
849 s
.bind(("localhost", port
))
852 s
.connect((address
, port
))
855 if address
== "localhost":
863 def find_free_port(start_port
, end_port
, address
="localhost"):
865 Return a host free port in the range [start_port, end_port].
867 @param start_port: First port that will be checked.
868 @param end_port: Port immediately after the last one that will be checked.
870 for i
in range(start_port
, end_port
):
871 if is_port_free(i
, address
):
876 def find_free_ports(start_port
, end_port
, count
, address
="localhost"):
878 Return count of host free ports in the range [start_port, end_port].
880 @count: Initial number of ports known to be free in the range.
881 @param start_port: First port that will be checked.
882 @param end_port: Port immediately after the last one that will be checked.
886 while i
< end_port
and count
> 0:
887 if is_port_free(i
, address
):
894 # An easy way to log lines to files when the logging system can't be used
897 _log_file_dir
= "/tmp"
900 def log_line(filename
, line
):
902 Write a line to a file. '\n' is appended to the line.
904 @param filename: Path of file to write to, either absolute or relative to
905 the dir set by set_log_file_dir().
906 @param line: Line to write.
908 global _open_log_files
, _log_file_dir
909 if filename
not in _open_log_files
:
910 path
= get_path(_log_file_dir
, filename
)
912 os
.makedirs(os
.path
.dirname(path
))
915 _open_log_files
[filename
] = open(path
, "w")
916 timestr
= time
.strftime("%Y-%m-%d %H:%M:%S")
917 _open_log_files
[filename
].write("%s: %s\n" % (timestr
, line
))
918 _open_log_files
[filename
].flush()
921 def set_log_file_dir(dir):
923 Set the base directory for log files created by log_line().
925 @param dir: Directory for log files.
931 # The following are miscellaneous utility functions.
933 def get_path(base_path
, user_path
):
935 Translate a user specified path to a real path.
936 If user_path is relative, append it to base_path.
937 If user_path is absolute, return it as is.
939 @param base_path: The base path of relative user specified paths.
940 @param user_path: The user specified path.
942 if os
.path
.isabs(user_path
):
945 return os
.path
.join(base_path
, user_path
)
948 def generate_random_string(length
):
950 Return a random string using alphanumeric characters.
952 @length: length of the string that will be generated.
954 r
= random
.SystemRandom()
956 chars
= string
.letters
+ string
.digits
958 str += r
.choice(chars
)
962 def generate_random_id():
964 Return a random string suitable for use as a qemu id.
966 return "id" + generate_random_string(6)
969 def generate_tmp_file_name(file, ext
=None, dir='/tmp/'):
971 Returns a temporary file name. The file is not created.
974 file_name
= (file + '-' + time
.strftime("%Y%m%d-%H%M%S-") +
975 generate_random_string(4))
977 file_name
+= '.' + ext
978 file_name
= os
.path
.join(dir, file_name
)
979 if not os
.path
.exists(file_name
):
985 def format_str_for_message(str):
987 Format str so that it can be appended to a message.
988 If str consists of one line, prefix it with a space.
989 If str consists of multiple lines, prefix it with a newline.
991 @param str: string that will be formatted.
993 lines
= str.splitlines()
994 num_lines
= len(lines
)
995 str = "\n".join(lines
)
1004 def wait_for(func
, timeout
, first
=0.0, step
=1.0, text
=None):
1006 If func() evaluates to True before timeout expires, return the
1007 value of func(). Otherwise return None.
1009 @brief: Wait until func() evaluates to True.
1011 @param timeout: Timeout in seconds
1012 @param first: Time to sleep before first attempt
1013 @param steps: Time to sleep between attempts in seconds
1014 @param text: Text to print while waiting, for debug purposes
1016 start_time
= time
.time()
1017 end_time
= time
.time() + timeout
1021 while time
.time() < end_time
:
1023 logging
.debug("%s (%f secs)" % (text
, time
.time() - start_time
))
1031 logging
.debug("Timeout elapsed")
1035 def get_hash_from_file(hash_path
, dvd_basename
):
1037 Get the a hash from a given DVD image from a hash file
1038 (Hash files are usually named MD5SUM or SHA1SUM and are located inside the
1039 download directories of the DVDs)
1041 @param hash_path: Local path to a hash file.
1042 @param cd_image: Basename of a CD image
1044 hash_file
= open(hash_path
, 'r')
1045 for line
in hash_file
.readlines():
1046 if dvd_basename
in line
:
1047 return line
.split()[0]
1050 def run_tests(test_list
, job
):
1052 Runs the sequence of KVM tests based on the list of dictionaries
1053 generated by the configuration system, handling dependencies.
1055 @param test_list: List with all dictionary test parameters.
1056 @param job: Autotest job object.
1058 @return: True, if all tests ran passed, False if any of them failed.
1063 for dict in test_list
:
1064 if dict.get("skip") == "yes":
1066 dependencies_satisfied
= True
1067 for dep
in dict.get("depend"):
1068 for test_name
in status_dict
.keys():
1069 if not dep
in test_name
:
1071 if not status_dict
[test_name
]:
1072 dependencies_satisfied
= False
1074 if dependencies_satisfied
:
1075 test_iterations
= int(dict.get("iterations", 1))
1076 test_tag
= dict.get("shortname")
1077 # Setting up profilers during test execution.
1078 profilers
= dict.get("profilers", "").split()
1079 for profiler
in profilers
:
1080 job
.profilers
.add(profiler
)
1082 # We need only one execution, profiled, hence we're passing
1083 # the profile_only parameter to job.run_test().
1084 current_status
= job
.run_test("kvm", params
=dict, tag
=test_tag
,
1085 iterations
=test_iterations
,
1086 profile_only
= bool(profilers
) or None)
1088 for profiler
in profilers
:
1089 job
.profilers
.delete(profiler
)
1091 if not current_status
:
1094 current_status
= False
1095 status_dict
[dict.get("name")] = current_status
1100 def create_report(report_dir
, results_dir
):
1102 Creates a neatly arranged HTML results report in the results dir.
1104 @param report_dir: Directory where the report script is located.
1105 @param results_dir: Directory where the results will be output.
1107 reporter
= os
.path
.join(report_dir
, 'html_report.py')
1108 html_file
= os
.path
.join(results_dir
, 'results.html')
1109 os
.system('%s -r %s -f %s -R' % (reporter
, results_dir
, html_file
))
1112 def get_full_pci_id(pci_id
):
1114 Get full PCI ID of pci_id.
1116 @param pci_id: PCI ID of a device.
1118 cmd
= "lspci -D | awk '/%s/ {print $1}'" % pci_id
1119 status
, full_id
= commands
.getstatusoutput(cmd
)
1125 def get_vendor_from_pci_id(pci_id
):
1127 Check out the device vendor ID according to pci_id.
1129 @param pci_id: PCI ID of a device.
1131 cmd
= "lspci -n | awk '/%s/ {print $3}'" % pci_id
1132 return re
.sub(":", " ", commands
.getoutput(cmd
))
1135 class Thread(threading
.Thread
):
1137 Run a function in a background thread.
1139 def __init__(self
, target
, args
=(), kwargs
={}):
1141 Initialize the instance.
1143 @param target: Function to run in the thread.
1144 @param args: Arguments to pass to target.
1145 @param kwargs: Keyword arguments to pass to target.
1147 threading
.Thread
.__init
__(self
)
1148 self
._target
= target
1150 self
._kwargs
= kwargs
1155 Run target (passed to the constructor). No point in calling this
1156 function directly. Call start() to make this function run in a new
1163 self
._retval
= self
._target
(*self
._args
, **self
._kwargs
)
1165 self
._e
= sys
.exc_info()
1168 # Avoid circular references (start() may be called only once so
1169 # it's OK to delete these)
1170 del self
._target
, self
._args
, self
._kwargs
1173 def join(self
, timeout
=None):
1175 Join the thread. If target raised an exception, re-raise it.
1176 Otherwise, return the value returned by target.
1178 @param timeout: Timeout value to pass to threading.Thread.join().
1180 threading
.Thread
.join(self
, timeout
)
1183 raise self
._e
[0], self
._e
[1], self
._e
[2]
1187 # Avoid circular references (join() may be called multiple times
1188 # so we can't delete these)
1193 def parallel(targets
):
1195 Run multiple functions in parallel.
1197 @param targets: A sequence of tuples or functions. If it's a sequence of
1198 tuples, each tuple will be interpreted as (target, args, kwargs) or
1199 (target, args) or (target,) depending on its length. If it's a
1200 sequence of functions, the functions will be called without
1202 @return: A list of the values returned by the functions called.
1205 for target
in targets
:
1206 if isinstance(target
, tuple) or isinstance(target
, list):
1212 return [t
.join() for t
in threads
]
1215 class KvmLoggingConfig(logging_config
.LoggingConfig
):
1217 Used with the sole purpose of providing convenient logging setup
1218 for the KVM test auxiliary programs.
1220 def configure_logging(self
, results_dir
=None, verbose
=False):
1221 super(KvmLoggingConfig
, self
).configure_logging(use_console
=True,
1225 class PciAssignable(object):
1227 Request PCI assignable devices on host. It will check whether to request
1228 PF (physical Functions) or VF (Virtual Functions).
1230 def __init__(self
, type="vf", driver
=None, driver_option
=None,
1231 names
=None, devices_requested
=None):
1233 Initialize parameter 'type' which could be:
1234 vf: Virtual Functions
1235 pf: Physical Function (actual hardware)
1236 mixed: Both includes VFs and PFs
1238 If pass through Physical NIC cards, we need to specify which devices
1239 to be assigned, e.g. 'eth1 eth2'.
1241 If pass through Virtual Functions, we need to specify how many vfs
1242 are going to be assigned, e.g. passthrough_count = 8 and max_vfs in
1245 @param type: PCI device type.
1246 @param driver: Kernel module for the PCI assignable device.
1247 @param driver_option: Module option to specify the maximum number of
1248 VFs (eg 'max_vfs=7')
1249 @param names: Physical NIC cards correspondent network interfaces,
1251 @param devices_requested: Number of devices being requested.
1254 self
.driver
= driver
1255 self
.driver_option
= driver_option
1257 self
.name_list
= names
.split()
1258 if devices_requested
:
1259 self
.devices_requested
= int(devices_requested
)
1261 self
.devices_requested
= None
1264 def _get_pf_pci_id(self
, name
, search_str
):
1266 Get the PF PCI ID according to name.
1268 @param name: Name of the PCI device.
1269 @param search_str: Search string to be used on lspci.
1271 cmd
= "ethtool -i %s | awk '/bus-info/ {print $2}'" % name
1272 s
, pci_id
= commands
.getstatusoutput(cmd
)
1273 if not (s
or "Cannot get driver information" in pci_id
):
1275 cmd
= "lspci | awk '/%s/ {print $1}'" % search_str
1276 pci_ids
= [id for id in commands
.getoutput(cmd
).splitlines()]
1277 nic_id
= int(re
.search('[0-9]+', name
).group(0))
1278 if (len(pci_ids
) - 1) < nic_id
:
1280 return pci_ids
[nic_id
]
1283 def _release_dev(self
, pci_id
):
1285 Release a single PCI device.
1287 @param pci_id: PCI ID of a given PCI device.
1289 base_dir
= "/sys/bus/pci"
1290 full_id
= get_full_pci_id(pci_id
)
1291 vendor_id
= get_vendor_from_pci_id(pci_id
)
1292 drv_path
= os
.path
.join(base_dir
, "devices/%s/driver" % full_id
)
1293 if 'pci-stub' in os
.readlink(drv_path
):
1294 cmd
= "echo '%s' > %s/new_id" % (vendor_id
, drv_path
)
1298 stub_path
= os
.path
.join(base_dir
, "drivers/pci-stub")
1299 cmd
= "echo '%s' > %s/unbind" % (full_id
, stub_path
)
1303 driver
= self
.dev_drivers
[pci_id
]
1304 cmd
= "echo '%s' > %s/bind" % (full_id
, driver
)
1311 def get_vf_devs(self
):
1313 Catch all VFs PCI IDs.
1315 @return: List with all PCI IDs for the Virtual Functions avaliable
1317 if not self
.sr_iov_setup():
1320 cmd
= "lspci | awk '/Virtual Function/ {print $1}'"
1321 return commands
.getoutput(cmd
).split()
1324 def get_pf_devs(self
):
1326 Catch all PFs PCI IDs.
1328 @return: List with all PCI IDs for the physical hardware requested
1331 for name
in self
.name_list
:
1332 pf_id
= self
._get
_pf
_pci
_id
(name
, "Ethernet")
1335 pf_ids
.append(pf_id
)
1339 def get_devs(self
, count
):
1341 Check out all devices' PCI IDs according to their name.
1343 @param count: count number of PCI devices needed for pass through
1344 @return: a list of all devices' PCI IDs
1346 if self
.type == "vf":
1347 vf_ids
= self
.get_vf_devs()
1348 elif self
.type == "pf":
1349 vf_ids
= self
.get_pf_devs()
1350 elif self
.type == "mixed":
1351 vf_ids
= self
.get_vf_devs()
1352 vf_ids
.extend(self
.get_pf_devs())
1353 return vf_ids
[0:count
]
1356 def get_vfs_count(self
):
1358 Get VFs count number according to lspci.
1360 # FIXME: Need to think out a method of identify which
1361 # 'virtual function' belongs to which physical card considering
1362 # that if the host has more than one 82576 card. PCI_ID?
1363 cmd
= "lspci | grep 'Virtual Function' | wc -l"
1364 return int(commands
.getoutput(cmd
))
1367 def check_vfs_count(self
):
1369 Check VFs count number according to the parameter driver_options.
1371 # Network card 82576 has two network interfaces and each can be
1372 # virtualized up to 7 virtual functions, therefore we multiply
1373 # two for the value of driver_option 'max_vfs'.
1374 expected_count
= int((re
.findall("(\d)", self
.driver_option
)[0])) * 2
1375 return (self
.get_vfs_count
== expected_count
)
1378 def is_binded_to_stub(self
, full_id
):
1380 Verify whether the device with full_id is already binded to pci-stub.
1382 @param full_id: Full ID for the given PCI device
1384 base_dir
= "/sys/bus/pci"
1385 stub_path
= os
.path
.join(base_dir
, "drivers/pci-stub")
1386 if os
.path
.exists(os
.path
.join(stub_path
, full_id
)):
1391 def sr_iov_setup(self
):
1393 Ensure the PCI device is working in sr_iov mode.
1395 Check if the PCI hardware device drive is loaded with the appropriate,
1396 parameters (number of VFs), and if it's not, perform setup.
1398 @return: True, if the setup was completed successfuly, False otherwise.
1401 s
, o
= commands
.getstatusoutput('lsmod | grep %s' % self
.driver
)
1404 elif not self
.check_vfs_count():
1405 os
.system("modprobe -r %s" % self
.driver
)
1410 # Re-probe driver with proper number of VFs
1412 cmd
= "modprobe %s %s" % (self
.driver
, self
.driver_option
)
1413 logging
.info("Loading the driver '%s' with option '%s'" %
1414 (self
.driver
, self
.driver_option
))
1415 s
, o
= commands
.getstatusoutput(cmd
)
1421 def request_devs(self
):
1423 Implement setup process: unbind the PCI device and then bind it
1424 to the pci-stub driver.
1426 @return: a list of successfully requested devices' PCI IDs.
1428 base_dir
= "/sys/bus/pci"
1429 stub_path
= os
.path
.join(base_dir
, "drivers/pci-stub")
1431 self
.pci_ids
= self
.get_devs(self
.devices_requested
)
1432 logging
.debug("The following pci_ids were found: %s", self
.pci_ids
)
1433 requested_pci_ids
= []
1434 self
.dev_drivers
= {}
1436 # Setup all devices specified for assignment to guest
1437 for pci_id
in self
.pci_ids
:
1438 full_id
= get_full_pci_id(pci_id
)
1441 drv_path
= os
.path
.join(base_dir
, "devices/%s/driver" % full_id
)
1442 dev_prev_driver
= os
.path
.realpath(os
.path
.join(drv_path
,
1443 os
.readlink(drv_path
)))
1444 self
.dev_drivers
[pci_id
] = dev_prev_driver
1446 # Judge whether the device driver has been binded to stub
1447 if not self
.is_binded_to_stub(full_id
):
1448 logging
.debug("Binding device %s to stub", full_id
)
1449 vendor_id
= get_vendor_from_pci_id(pci_id
)
1450 stub_new_id
= os
.path
.join(stub_path
, 'new_id')
1451 unbind_dev
= os
.path
.join(drv_path
, 'unbind')
1452 stub_bind
= os
.path
.join(stub_path
, 'bind')
1454 info_write_to_files
= [(vendor_id
, stub_new_id
),
1455 (full_id
, unbind_dev
),
1456 (full_id
, stub_bind
)]
1458 for content
, file in info_write_to_files
:
1460 utils
.open_write_close(file, content
)
1462 logging
.debug("Failed to write %s to file %s", content
,
1466 if not self
.is_binded_to_stub(full_id
):
1467 logging
.error("Binding device %s to stub failed", pci_id
)
1470 logging
.debug("Device %s already binded to stub", pci_id
)
1471 requested_pci_ids
.append(pci_id
)
1472 self
.pci_ids
= requested_pci_ids
1476 def release_devs(self
):
1478 Release all PCI devices currently assigned to VMs back to the
1479 virtualization host.
1482 for pci_id
in self
.dev_drivers
:
1483 if not self
._release
_dev
(pci_id
):
1484 logging
.error("Failed to release device %s to host", pci_id
)
1486 logging
.info("Released device %s successfully", pci_id
)
1491 class KojiDownloader(object):
1493 Stablish a connection with the build system, either koji or brew.
1495 This class provides a convenience methods to retrieve packages hosted on
1498 def __init__(self
, cmd
):
1500 Verifies whether the system has koji or brew installed, then loads
1501 the configuration file that will be used to download the files.
1503 @param cmd: Command name, either 'brew' or 'koji'. It is important
1504 to figure out the appropriate configuration used by the
1506 @param dst_dir: Destination dir for the packages.
1508 if not KOJI_INSTALLED
:
1509 raise ValueError('No koji/brew installed on the machine')
1511 if os
.path
.isfile(cmd
):
1514 koji_cmd
= os_dep
.command(cmd
)
1516 logging
.debug("Found %s as the buildsystem interface", koji_cmd
)
1518 config_map
= {'/usr/bin/koji': '/etc/koji.conf',
1519 '/usr/bin/brew': '/etc/brewkoji.conf'}
1522 config_file
= config_map
[koji_cmd
]
1524 raise ValueError('Could not find config file for %s' % koji_cmd
)
1526 base_name
= os
.path
.basename(koji_cmd
)
1527 if os
.access(config_file
, os
.F_OK
):
1528 f
= open(config_file
)
1529 config
= ConfigParser
.ConfigParser()
1533 raise IOError('Configuration file %s missing or with wrong '
1534 'permissions' % config_file
)
1536 if config
.has_section(base_name
):
1537 self
.koji_options
= {}
1538 session_options
= {}
1540 for name
, value
in config
.items(base_name
):
1541 if name
in ('user', 'password', 'debug_xmlrpc', 'debug'):
1542 session_options
[name
] = value
1543 self
.koji_options
[name
] = value
1544 self
.session
= koji
.ClientSession(self
.koji_options
['server'],
1547 raise ValueError('Koji config file %s does not have a %s '
1548 'session' % (config_file
, base_name
))
1551 def get(self
, src_package
, dst_dir
, rfilter
=None, tag
=None, build
=None,
1554 Download a list of packages from the build system.
1556 This will download all packages originated from source package [package]
1557 with given [tag] or [build] for the architecture reported by the
1560 @param src_package: Source package name.
1561 @param dst_dir: Destination directory for the downloaded packages.
1562 @param rfilter: Regexp filter, only download the packages that match
1563 that particular filter.
1564 @param tag: Build system tag.
1565 @param build: Build system ID.
1566 @param arch: Package arch. Useful when you want to download noarch
1569 @return: List of paths with the downloaded rpm packages.
1571 if build
and build
.isdigit():
1575 logging
.info("Both tag and build parameters provided, ignoring tag "
1578 if not tag
and not build
:
1579 raise ValueError("Koji install selected but neither koji_tag "
1580 "nor koji_build parameters provided. Please "
1581 "provide an appropriate tag or build name.")
1584 builds
= self
.session
.listTagged(tag
, latest
=True, inherit
=True,
1585 package
=src_package
)
1587 raise ValueError("Tag %s has no builds of %s" % (tag
,
1591 info
= self
.session
.getBuild(build
)
1594 raise ValueError('No such brew/koji build: %s' % build
)
1597 arch
= utils
.get_arch()
1599 rpms
= self
.session
.listRPMs(buildID
=info
['id'],
1602 raise ValueError("No %s packages available for %s" %
1603 arch
, koji
.buildLabel(info
))
1607 rpm_name
= koji
.pathinfo
.rpm(rpm
)
1608 url
= ("%s/%s/%s/%s/%s" % (self
.koji_options
['pkgurl'],
1609 info
['package_name'],
1610 info
['version'], info
['release'],
1613 filter_regexp
= re
.compile(rfilter
, re
.IGNORECASE
)
1614 if filter_regexp
.match(os
.path
.basename(rpm_name
)):
1622 r
= utils
.get_file(url
,
1623 os
.path
.join(dst_dir
, os
.path
.basename(url
)))
1629 def umount(src
, mount_point
, type):
1631 Umount the src mounted in mount_point.
1634 @mount_point: mount point
1635 @type: file system type
1638 mount_string
= "%s %s %s" % (src
, mount_point
, type)
1639 if mount_string
in file("/etc/mtab").read():
1640 umount_cmd
= "umount %s" % mount_point
1642 utils
.system(umount_cmd
)
1644 except error
.CmdError
:
1647 logging
.debug("%s is not mounted under %s" % (src
, mount_point
))
1651 def mount(src
, mount_point
, type, perm
="rw"):
1653 Mount the src into mount_point of the host.
1656 @mount_point: mount point
1657 @type: file system type
1658 @perm: mount premission
1660 umount(src
, mount_point
, type)
1661 mount_string
= "%s %s %s %s" % (src
, mount_point
, type, perm
)
1663 if mount_string
in file("/etc/mtab").read():
1664 logging
.debug("%s is already mounted in %s with %s" %
1665 (src
, mount_point
, perm
))
1668 mount_cmd
= "mount -t %s %s %s -o %s" % (type, src
, mount_point
, perm
)
1670 utils
.system(mount_cmd
)
1671 except error
.CmdError
:
1674 logging
.debug("Verify the mount through /etc/mtab")
1675 if mount_string
in file("/etc/mtab").read():
1676 logging
.debug("%s is successfully mounted" % src
)
1679 logging
.error("Can't find mounted NFS share - /etc/mtab contents \n%s" %
1680 file("/etc/mtab").read())