blob: 93918749cdc8b815fce41ba8fa32c9e8453dbe05 [file] [log] [blame]
lmr6f669ce2009-05-31 19:02:42 +00001import md5, thread, subprocess, time, string, random, socket, os, signal, pty
2import select, re, logging
lmr3f0b0cc2009-06-10 02:25:23 +00003from autotest_lib.client.bin import utils
4from autotest_lib.client.common_lib import error
lmrdc3a5b12009-07-23 01:40:40 +00005import kvm_subprocess
lmr6f669ce2009-05-31 19:02:42 +00006
7"""
8KVM test utility functions.
9
10@copyright: 2008-2009 Red Hat Inc.
11"""
12
13
14def get_sub_dict(dict, name):
15 """
16 Return a "sub-dict" corresponding to a specific object.
17
18 Operate on a copy of dict: for each key that ends with the suffix
19 "_" + name, strip the suffix from the key, and set the value of
20 the stripped key to that of the key. Return the resulting dict.
21
22 @param name: Suffix of the key we want to set the value.
23 """
24 suffix = "_" + name
25 new_dict = dict.copy()
26 for key in dict.keys():
27 if key.endswith(suffix):
28 new_key = key.split(suffix)[0]
29 new_dict[new_key] = dict[key]
30 return new_dict
31
32
33def get_sub_dict_names(dict, keyword):
34 """
35 Return a list of "sub-dict" names that may be extracted with get_sub_dict.
36
37 This function may be modified to change the behavior of all functions that
38 deal with multiple objects defined in dicts (e.g. VMs, images, NICs).
39
40 @param keyword: A key in dict (e.g. "vms", "images", "nics").
41 """
42 names = dict.get(keyword)
43 if names:
44 return names.split()
45 else:
46 return []
47
48
49# Functions for working with the environment (a dict-like object)
50
51def is_vm(obj):
52 """
53 Tests whether a given object is a VM object.
54
55 @param obj: Python object (pretty much everything on python).
56 """
57 return obj.__class__.__name__ == "VM"
58
59
60def env_get_all_vms(env):
61 """
62 Return a list of all VM objects on a given environment.
63
64 @param env: Dictionary with environment items.
65 """
66 vms = []
67 for obj in env.values():
68 if is_vm(obj):
69 vms.append(obj)
70 return vms
71
72
73def env_get_vm(env, name):
74 """
75 Return a VM object by its name.
76
77 @param name: VM name.
78 """
79 return env.get("vm__%s" % name)
80
81
82def env_register_vm(env, name, vm):
83 """
84 Register a given VM in a given env.
85
86 @param env: Environment where we will register the VM.
87 @param name: VM name.
88 @param vm: VM object.
89 """
90 env["vm__%s" % name] = vm
91
92
93def env_unregister_vm(env, name):
94 """
95 Remove a given VM from a given env.
96
97 @param env: Environment where we will un-register the VM.
98 @param name: VM name.
99 """
100 del env["vm__%s" % name]
101
102
103# Utility functions for dealing with external processes
104
105def pid_exists(pid):
106 """
107 Return True if a given PID exists.
108
109 @param pid: Process ID number.
110 """
111 try:
112 os.kill(pid, 0)
113 return True
114 except:
115 return False
116
117
118def safe_kill(pid, signal):
119 """
120 Attempt to send a signal to a given process that may or may not exist.
121
122 @param signal: Signal number.
123 """
124 try:
125 os.kill(pid, signal)
126 return True
127 except:
128 return False
129
130
lmr3f0b0cc2009-06-10 02:25:23 +0000131def get_latest_kvm_release_tag(release_dir):
132 """
133 Fetches the latest release tag for KVM.
134
135 @param release_dir: KVM source forge download location.
136 """
137 try:
138 page_url = os.path.join(release_dir, "showfiles.php")
139 local_web_page = utils.unmap_url("/", page_url, "/tmp")
140 f = open(local_web_page, "r")
141 data = f.read()
142 f.close()
lmr8ea274b2009-07-06 13:42:35 +0000143 rx = re.compile("kvm-(\d+).tar.gz", re.IGNORECASE)
lmr3f0b0cc2009-06-10 02:25:23 +0000144 matches = rx.findall(data)
145 package_id = matches[0]
lmr3f0b0cc2009-06-10 02:25:23 +0000146 return matches[0] # the first match contains the latest release tag
147 except Exception, e:
148 message = "Could not fetch latest KVM release tag: %s" % str(e)
149 logging.error(message)
150 raise error.TestError(message)
151
152
153def get_git_branch(repository, branch, srcdir, commit=None, lbranch=None):
154 """
155 Retrieves a given git code repository.
156
157 @param repository: Git repository URL
158 """
159 logging.info("Fetching git [REP '%s' BRANCH '%s' TAG '%s'] -> %s",
160 repository, branch, commit, srcdir)
161 if not os.path.exists(srcdir):
162 os.makedirs(srcdir)
163 os.chdir(srcdir)
164
165 if os.path.exists(".git"):
166 utils.system("git reset --hard")
167 else:
168 utils.system("git init")
169
170 if not lbranch:
171 lbranch = branch
172
173 utils.system("git fetch -q -f -u -t %s %s:%s" %
174 (repository, branch, lbranch))
175 utils.system("git checkout %s" % lbranch)
176 if commit:
177 utils.system("git checkout %s" % commit)
178
179 h = utils.system_output('git log --pretty=format:"%H" -1')
180 desc = utils.system_output("git describe")
181 logging.info("Commit hash for %s is %s (%s)" % (repository, h.strip(),
182 desc))
183 return srcdir
184
185
186def unload_module(module_name):
187 """
188 Removes a module. Handles dependencies. If even then it's not possible
189 to remove one of the modules, it will trhow an error.CmdError exception.
190
191 @param module_name: Name of the module we want to remove.
192 """
193 l_raw = utils.system_output("/sbin/lsmod").splitlines()
194 lsmod = [x for x in l_raw if x.split()[0] == module_name]
195 if len(lsmod) > 0:
196 line_parts = lsmod[0].split()
197 if len(line_parts) == 4:
198 submodules = line_parts[3].split(",")
199 for submodule in submodules:
200 unload_module(submodule)
201 utils.system("/sbin/modprobe -r %s" % module_name)
202 logging.info("Module %s unloaded" % module_name)
203 else:
204 logging.info("Module %s is already unloaded" % module_name)
205
206
207def check_kvm_source_dir(source_dir):
208 """
209 Inspects the kvm source directory and verifies its disposition. In some
210 occasions build may be dependant on the source directory disposition.
211 The reason why the return codes are numbers is that we might have more
212 changes on the source directory layout, so it's not scalable to just use
213 strings like 'old_repo', 'new_repo' and such.
214
215 @param source_dir: Source code path that will be inspected.
216 """
217 os.chdir(source_dir)
218 has_qemu_dir = os.path.isdir('qemu')
219 has_kvm_dir = os.path.isdir('kvm')
220 if has_qemu_dir and not has_kvm_dir:
221 logging.debug("qemu directory detected, source dir layout 1")
222 return 1
223 if has_kvm_dir and not has_qemu_dir:
224 logging.debug("kvm directory detected, source dir layout 2")
225 return 2
226 else:
227 raise error.TestError("Unknown source dir layout, cannot proceed.")
228
229
lmrf9349c32009-07-23 01:44:24 +0000230# The following are functions used for SSH, SCP and Telnet communication with
231# guests.
lmr6f669ce2009-05-31 19:02:42 +0000232
233def remote_login(command, password, prompt, linesep="\n", timeout=10):
234 """
235 Log into a remote host (guest) using SSH or Telnet. Run the given command
236 using kvm_spawn and provide answers to the questions asked. If timeout
237 expires while waiting for output from the child (e.g. a password prompt
238 or a shell prompt) -- fail.
239
240 @brief: Log into a remote host (guest) using SSH or Telnet.
241
242 @param command: The command to execute (e.g. "ssh root@localhost")
243 @param password: The password to send in reply to a password prompt
244 @param prompt: The shell prompt that indicates a successful login
245 @param linesep: The line separator to send instead of "\\n"
246 (sometimes "\\r\\n" is required)
247 @param timeout: The maximal time duration (in seconds) to wait for each
248 step of the login procedure (i.e. the "Are you sure" prompt, the
249 password prompt, the shell prompt, etc)
250
251 @return Return the kvm_spawn object on success and None on failure.
252 """
lmrdc3a5b12009-07-23 01:40:40 +0000253 sub = kvm_subprocess.kvm_shell_session(command,
254 linesep=linesep,
255 prompt=prompt)
lmr6f669ce2009-05-31 19:02:42 +0000256
257 password_prompt_count = 0
258
259 logging.debug("Trying to login...")
260
261 while True:
262 (match, text) = sub.read_until_last_line_matches(
lmr3ca79fe2009-06-10 19:24:26 +0000263 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"^\s*[Ll]ogin:\s*$",
264 r"[Cc]onnection.*closed", r"[Cc]onnection.*refused", prompt],
lmr6f669ce2009-05-31 19:02:42 +0000265 timeout=timeout, internal_timeout=0.5)
266 if match == 0: # "Are you sure you want to continue connecting"
267 logging.debug("Got 'Are you sure...'; sending 'yes'")
268 sub.sendline("yes")
269 continue
270 elif match == 1: # "password:"
271 if password_prompt_count == 0:
272 logging.debug("Got password prompt; sending '%s'" % password)
273 sub.sendline(password)
274 password_prompt_count += 1
275 continue
276 else:
277 logging.debug("Got password prompt again")
278 sub.close()
279 return None
280 elif match == 2: # "login:"
281 logging.debug("Got unexpected login prompt")
282 sub.close()
283 return None
284 elif match == 3: # "Connection closed"
285 logging.debug("Got 'Connection closed'")
286 sub.close()
287 return None
lmr3ca79fe2009-06-10 19:24:26 +0000288 elif match == 4: # "Connection refused"
lmr0d2ed1f2009-07-01 03:23:18 +0000289 logging.debug("Got 'Connection refused'")
lmr3ca79fe2009-06-10 19:24:26 +0000290 sub.close()
291 return None
292 elif match == 5: # prompt
lmr6f669ce2009-05-31 19:02:42 +0000293 logging.debug("Got shell prompt -- logged in")
294 return sub
295 else: # match == None
lmr3ca79fe2009-06-10 19:24:26 +0000296 logging.debug("Timeout elapsed or process terminated")
lmr6f669ce2009-05-31 19:02:42 +0000297 sub.close()
298 return None
299
300
301def remote_scp(command, password, timeout=300, login_timeout=10):
302 """
303 Run the given command using kvm_spawn and provide answers to the questions
304 asked. If timeout expires while waiting for the transfer to complete ,
305 fail. If login_timeout expires while waiting for output from the child
306 (e.g. a password prompt), fail.
307
308 @brief: Transfer files using SCP, given a command line.
309
310 @param command: The command to execute
311 (e.g. "scp -r foobar root@localhost:/tmp/").
312 @param password: The password to send in reply to a password prompt.
313 @param timeout: The time duration (in seconds) to wait for the transfer
314 to complete.
315 @param login_timeout: The maximal time duration (in seconds) to wait for
316 each step of the login procedure (i.e. the "Are you sure" prompt or the
317 password prompt)
318
319 @return: True if the transfer succeeds and False on failure.
320 """
lmrdc3a5b12009-07-23 01:40:40 +0000321 sub = kvm_subprocess.kvm_expect(command)
lmr6f669ce2009-05-31 19:02:42 +0000322
323 password_prompt_count = 0
324 _timeout = login_timeout
325
326 logging.debug("Trying to login...")
327
328 while True:
329 (match, text) = sub.read_until_last_line_matches(
lmr3ca79fe2009-06-10 19:24:26 +0000330 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"lost connection"],
lmr6f669ce2009-05-31 19:02:42 +0000331 timeout=_timeout, internal_timeout=0.5)
332 if match == 0: # "Are you sure you want to continue connecting"
333 logging.debug("Got 'Are you sure...'; sending 'yes'")
334 sub.sendline("yes")
335 continue
336 elif match == 1: # "password:"
337 if password_prompt_count == 0:
338 logging.debug("Got password prompt; sending '%s'" % password)
339 sub.sendline(password)
340 password_prompt_count += 1
341 _timeout = timeout
342 continue
343 else:
344 logging.debug("Got password prompt again")
345 sub.close()
346 return False
347 elif match == 2: # "lost connection"
348 logging.debug("Got 'lost connection'")
349 sub.close()
350 return False
351 else: # match == None
lmrdc3a5b12009-07-23 01:40:40 +0000352 logging.debug("Timeout elapsed or process terminated")
353 status = sub.get_status()
lmr6f669ce2009-05-31 19:02:42 +0000354 sub.close()
lmrdc3a5b12009-07-23 01:40:40 +0000355 return status == 0
lmr6f669ce2009-05-31 19:02:42 +0000356
357
358def scp_to_remote(host, port, username, password, local_path, remote_path,
359 timeout=300):
360 """
361 Copy files to a remote host (guest).
362
363 @param host: Hostname of the guest
364 @param username: User that will be used to copy the files
365 @param password: Host's password
366 @param local_path: Path on the local machine where we are copying from
367 @param remote_path: Path on the remote machine where we are copying to
368 @param timeout: Time in seconds that we will wait before giving up to
369 copy the files.
370
371 @return: True on success and False on failure.
372 """
lmrd16a67d2009-06-10 19:52:59 +0000373 command = ("scp -o UserKnownHostsFile=/dev/null -r -P %s %s %s@%s:%s" %
374 (port, local_path, username, host, remote_path))
lmr6f669ce2009-05-31 19:02:42 +0000375 return remote_scp(command, password, timeout)
376
377
378def scp_from_remote(host, port, username, password, remote_path, local_path,
379 timeout=300):
380 """
381 Copy files from a remote host (guest).
382
383 @param host: Hostname of the guest
384 @param username: User that will be used to copy the files
385 @param password: Host's password
386 @param local_path: Path on the local machine where we are copying from
387 @param remote_path: Path on the remote machine where we are copying to
388 @param timeout: Time in seconds that we will wait before giving up to copy
389 the files.
390
391 @return: True on success and False on failure.
392 """
lmrd16a67d2009-06-10 19:52:59 +0000393 command = ("scp -o UserKnownHostsFile=/dev/null -r -P %s %s@%s:%s %s" %
394 (port, username, host, remote_path, local_path))
lmr6f669ce2009-05-31 19:02:42 +0000395 return remote_scp(command, password, timeout)
396
397
398def ssh(host, port, username, password, prompt, timeout=10):
399 """
400 Log into a remote host (guest) using SSH.
401
402 @param host: Hostname of the guest
403 @param username: User that will be used to log into the host.
404 @param password: Host's password
405 @timeout: Time in seconds that we will wait before giving up on logging
406 into the host.
407
408 @return: kvm_spawn object on success and None on failure.
409 """
lmrd16a67d2009-06-10 19:52:59 +0000410 command = ("ssh -o UserKnownHostsFile=/dev/null -p %s %s@%s" %
411 (port, username, host))
lmr6f669ce2009-05-31 19:02:42 +0000412 return remote_login(command, password, prompt, "\n", timeout)
413
414
415def telnet(host, port, username, password, prompt, timeout=10):
416 """
417 Log into a remote host (guest) using Telnet.
418
419 @param host: Hostname of the guest
420 @param username: User that will be used to log into the host.
421 @param password: Host's password
422 @timeout: Time in seconds that we will wait before giving up on logging
423 into the host.
424
425 @return: kvm_spawn object on success and None on failure.
426 """
427 command = "telnet -l %s %s %s" % (username, host, port)
428 return remote_login(command, password, prompt, "\r\n", timeout)
429
430
lmr6f669ce2009-05-31 19:02:42 +0000431# The following are utility functions related to ports.
432
433def is_sshd_running(host, port, timeout=10.0):
434 """
435 Connect to the given host and port and wait for output.
436 Return True if the given host and port are responsive.
437
438 @param host: Host's hostname
439 @param port: Host's port
440 @param timeout: Time (seconds) before we giving up on checking the SSH
441 daemon.
442
443 @return: If output is available, return True. If timeout expires and no
444 output was available, return False.
445 """
446 try:
447 # Try to connect
448 #s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
449 s = socket.socket()
450 s.connect((host, port))
451 except socket.error:
452 # Can't connect -- return False
453 s.close()
454 logging.debug("Could not connect")
455 return False
456 s.setblocking(False)
457 # Wait up to 'timeout' seconds
458 end_time = time.time() + timeout
459 while time.time() < end_time:
460 try:
461 time.sleep(0.1)
462 # Try to receive some text
463 str = s.recv(1024)
464 if len(str) > 0:
465 s.shutdown(socket.SHUT_RDWR)
466 s.close()
467 logging.debug("Success! got string %r" % str)
468 return True
469 except socket.error:
470 # No text was available; try again
471 pass
472 # Timeout elapsed and no text was received
473 s.shutdown(socket.SHUT_RDWR)
474 s.close()
475 logging.debug("Timeout")
476 return False
477
478
479def is_port_free(port):
480 """
481 Return True if the given port is available for use.
482
483 @param port: Port number
484 """
485 try:
486 s = socket.socket()
487 #s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
488 s.bind(("localhost", port))
489 free = True
490 except socket.error:
491 free = False
492 s.close()
493 return free
494
495
496def find_free_port(start_port, end_port):
497 """
498 Return a free port in the range [start_port, end_port).
499
500 @param start_port: First port that will be checked.
501 @param end_port: Port immediately after the last one that will be checked.
502 """
503 for i in range(start_port, end_port):
504 if is_port_free(i):
505 return i
506 return None
507
508
509def find_free_ports(start_port, end_port, count):
510 """
511 Return count free ports in the range [start_port, end_port).
512
513 @count: Initial number of ports known to be free in the range.
514 @param start_port: First port that will be checked.
515 @param end_port: Port immediately after the last one that will be checked.
516 """
517 ports = []
518 i = start_port
519 while i < end_port and count > 0:
520 if is_port_free(i):
521 ports.append(i)
522 count -= 1
523 i += 1
524 return ports
525
526
527# The following are miscellaneous utility functions.
528
529def generate_random_string(length):
530 """
531 Return a random string using alphanumeric characters.
532
533 @length: length of the string that will be generated.
534 """
535 str = ""
536 chars = string.letters + string.digits
537 while length > 0:
538 str += random.choice(chars)
539 length -= 1
540 return str
541
542
543def format_str_for_message(str):
544 """
545 Format str so that it can be appended to a message.
546 If str consists of one line, prefix it with a space.
547 If str consists of multiple lines, prefix it with a newline.
548
549 @param str: string that will be formatted.
550 """
551 num_lines = len(str.splitlines())
552 if num_lines == 0:
553 return ""
554 elif num_lines == 1:
555 return " " + str
556 else:
557 return "\n" + str
558
559
560def wait_for(func, timeout, first=0.0, step=1.0, text=None):
561 """
562 If func() evaluates to True before timeout expires, return the
563 value of func(). Otherwise return None.
564
565 @brief: Wait until func() evaluates to True.
566
567 @param timeout: Timeout in seconds
568 @param first: Time to sleep before first attempt
569 @param steps: Time to sleep between attempts in seconds
570 @param text: Text to print while waiting, for debug purposes
571 """
572 start_time = time.time()
573 end_time = time.time() + timeout
574
575 time.sleep(first)
576
577 while time.time() < end_time:
578 if text:
579 logging.debug("%s (%f secs)" % (text, time.time() - start_time))
580
581 output = func()
582 if output:
583 return output
584
585 time.sleep(step)
586
587 logging.debug("Timeout elapsed")
588 return None
589
590
591def md5sum_file(filename, size=None):
592 """
593 Calculate the md5sum of filename.
594 If size is not None, limit to first size bytes.
595 Throw exception if something is wrong with filename.
596 Can be also implemented with bash one-liner (assuming size%1024==0):
597 dd if=filename bs=1024 count=size/1024 | md5sum -
598
599 @param filename: Path of the file that will have its md5sum calculated.
600 @param returns: md5sum of the file.
601 """
602 chunksize = 4096
603 fsize = os.path.getsize(filename)
604 if not size or size>fsize:
605 size = fsize
606 f = open(filename, 'rb')
607 o = md5.new()
608 while size > 0:
609 if chunksize > size:
610 chunksize = size
611 data = f.read(chunksize)
612 if len(data) == 0:
613 logging.debug("Nothing left to read but size=%d" % size)
614 break
615 o.update(data)
616 size -= len(data)
617 f.close()
618 return o.hexdigest()