blob: 5b47e0204924c0e8cfbaa613e22b3f1b77f01b4a [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)
lmr32525382009-08-10 13:53:37 +0000145 # In all regexp matches to something that looks like a release tag,
146 # get the largest integer. That will be our latest release tag.
147 latest_tag = max(int(x) for x in matches)
148 return str(latest_tag)
lmr3f0b0cc2009-06-10 02:25:23 +0000149 except Exception, e:
150 message = "Could not fetch latest KVM release tag: %s" % str(e)
151 logging.error(message)
152 raise error.TestError(message)
153
154
155def get_git_branch(repository, branch, srcdir, commit=None, lbranch=None):
156 """
157 Retrieves a given git code repository.
158
159 @param repository: Git repository URL
160 """
161 logging.info("Fetching git [REP '%s' BRANCH '%s' TAG '%s'] -> %s",
162 repository, branch, commit, srcdir)
163 if not os.path.exists(srcdir):
164 os.makedirs(srcdir)
165 os.chdir(srcdir)
166
167 if os.path.exists(".git"):
168 utils.system("git reset --hard")
169 else:
170 utils.system("git init")
171
172 if not lbranch:
173 lbranch = branch
174
175 utils.system("git fetch -q -f -u -t %s %s:%s" %
176 (repository, branch, lbranch))
177 utils.system("git checkout %s" % lbranch)
178 if commit:
179 utils.system("git checkout %s" % commit)
180
181 h = utils.system_output('git log --pretty=format:"%H" -1')
182 desc = utils.system_output("git describe")
183 logging.info("Commit hash for %s is %s (%s)" % (repository, h.strip(),
184 desc))
185 return srcdir
186
187
188def unload_module(module_name):
189 """
190 Removes a module. Handles dependencies. If even then it's not possible
191 to remove one of the modules, it will trhow an error.CmdError exception.
192
193 @param module_name: Name of the module we want to remove.
194 """
195 l_raw = utils.system_output("/sbin/lsmod").splitlines()
196 lsmod = [x for x in l_raw if x.split()[0] == module_name]
197 if len(lsmod) > 0:
198 line_parts = lsmod[0].split()
199 if len(line_parts) == 4:
200 submodules = line_parts[3].split(",")
201 for submodule in submodules:
202 unload_module(submodule)
203 utils.system("/sbin/modprobe -r %s" % module_name)
204 logging.info("Module %s unloaded" % module_name)
205 else:
206 logging.info("Module %s is already unloaded" % module_name)
207
208
209def check_kvm_source_dir(source_dir):
210 """
211 Inspects the kvm source directory and verifies its disposition. In some
212 occasions build may be dependant on the source directory disposition.
213 The reason why the return codes are numbers is that we might have more
214 changes on the source directory layout, so it's not scalable to just use
215 strings like 'old_repo', 'new_repo' and such.
216
217 @param source_dir: Source code path that will be inspected.
218 """
219 os.chdir(source_dir)
220 has_qemu_dir = os.path.isdir('qemu')
221 has_kvm_dir = os.path.isdir('kvm')
222 if has_qemu_dir and not has_kvm_dir:
223 logging.debug("qemu directory detected, source dir layout 1")
224 return 1
225 if has_kvm_dir and not has_qemu_dir:
226 logging.debug("kvm directory detected, source dir layout 2")
227 return 2
228 else:
229 raise error.TestError("Unknown source dir layout, cannot proceed.")
230
231
lmrf9349c32009-07-23 01:44:24 +0000232# The following are functions used for SSH, SCP and Telnet communication with
233# guests.
lmr6f669ce2009-05-31 19:02:42 +0000234
235def remote_login(command, password, prompt, linesep="\n", timeout=10):
236 """
237 Log into a remote host (guest) using SSH or Telnet. Run the given command
238 using kvm_spawn and provide answers to the questions asked. If timeout
239 expires while waiting for output from the child (e.g. a password prompt
240 or a shell prompt) -- fail.
241
242 @brief: Log into a remote host (guest) using SSH or Telnet.
243
244 @param command: The command to execute (e.g. "ssh root@localhost")
245 @param password: The password to send in reply to a password prompt
246 @param prompt: The shell prompt that indicates a successful login
247 @param linesep: The line separator to send instead of "\\n"
248 (sometimes "\\r\\n" is required)
249 @param timeout: The maximal time duration (in seconds) to wait for each
250 step of the login procedure (i.e. the "Are you sure" prompt, the
251 password prompt, the shell prompt, etc)
252
253 @return Return the kvm_spawn object on success and None on failure.
254 """
lmrdc3a5b12009-07-23 01:40:40 +0000255 sub = kvm_subprocess.kvm_shell_session(command,
256 linesep=linesep,
257 prompt=prompt)
lmr6f669ce2009-05-31 19:02:42 +0000258
259 password_prompt_count = 0
260
lmr8691f422009-07-28 02:52:30 +0000261 logging.debug("Trying to login with command '%s'" % command)
lmr6f669ce2009-05-31 19:02:42 +0000262
263 while True:
264 (match, text) = sub.read_until_last_line_matches(
lmr3ca79fe2009-06-10 19:24:26 +0000265 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"^\s*[Ll]ogin:\s*$",
266 r"[Cc]onnection.*closed", r"[Cc]onnection.*refused", prompt],
lmr6f669ce2009-05-31 19:02:42 +0000267 timeout=timeout, internal_timeout=0.5)
268 if match == 0: # "Are you sure you want to continue connecting"
269 logging.debug("Got 'Are you sure...'; sending 'yes'")
270 sub.sendline("yes")
271 continue
272 elif match == 1: # "password:"
273 if password_prompt_count == 0:
274 logging.debug("Got password prompt; sending '%s'" % password)
275 sub.sendline(password)
276 password_prompt_count += 1
277 continue
278 else:
279 logging.debug("Got password prompt again")
280 sub.close()
281 return None
282 elif match == 2: # "login:"
283 logging.debug("Got unexpected login prompt")
284 sub.close()
285 return None
286 elif match == 3: # "Connection closed"
287 logging.debug("Got 'Connection closed'")
288 sub.close()
289 return None
lmr3ca79fe2009-06-10 19:24:26 +0000290 elif match == 4: # "Connection refused"
lmr0d2ed1f2009-07-01 03:23:18 +0000291 logging.debug("Got 'Connection refused'")
lmr3ca79fe2009-06-10 19:24:26 +0000292 sub.close()
293 return None
294 elif match == 5: # prompt
lmr6f669ce2009-05-31 19:02:42 +0000295 logging.debug("Got shell prompt -- logged in")
296 return sub
297 else: # match == None
lmr3ca79fe2009-06-10 19:24:26 +0000298 logging.debug("Timeout elapsed or process terminated")
lmr6f669ce2009-05-31 19:02:42 +0000299 sub.close()
300 return None
301
302
303def remote_scp(command, password, timeout=300, login_timeout=10):
304 """
305 Run the given command using kvm_spawn and provide answers to the questions
306 asked. If timeout expires while waiting for the transfer to complete ,
307 fail. If login_timeout expires while waiting for output from the child
308 (e.g. a password prompt), fail.
309
310 @brief: Transfer files using SCP, given a command line.
311
312 @param command: The command to execute
313 (e.g. "scp -r foobar root@localhost:/tmp/").
314 @param password: The password to send in reply to a password prompt.
315 @param timeout: The time duration (in seconds) to wait for the transfer
316 to complete.
317 @param login_timeout: The maximal time duration (in seconds) to wait for
318 each step of the login procedure (i.e. the "Are you sure" prompt or the
319 password prompt)
320
321 @return: True if the transfer succeeds and False on failure.
322 """
lmrdc3a5b12009-07-23 01:40:40 +0000323 sub = kvm_subprocess.kvm_expect(command)
lmr6f669ce2009-05-31 19:02:42 +0000324
325 password_prompt_count = 0
326 _timeout = login_timeout
327
328 logging.debug("Trying to login...")
329
330 while True:
331 (match, text) = sub.read_until_last_line_matches(
lmr3ca79fe2009-06-10 19:24:26 +0000332 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"lost connection"],
lmr6f669ce2009-05-31 19:02:42 +0000333 timeout=_timeout, internal_timeout=0.5)
334 if match == 0: # "Are you sure you want to continue connecting"
335 logging.debug("Got 'Are you sure...'; sending 'yes'")
336 sub.sendline("yes")
337 continue
338 elif match == 1: # "password:"
339 if password_prompt_count == 0:
340 logging.debug("Got password prompt; sending '%s'" % password)
341 sub.sendline(password)
342 password_prompt_count += 1
343 _timeout = timeout
344 continue
345 else:
346 logging.debug("Got password prompt again")
347 sub.close()
348 return False
349 elif match == 2: # "lost connection"
350 logging.debug("Got 'lost connection'")
351 sub.close()
352 return False
353 else: # match == None
lmrdc3a5b12009-07-23 01:40:40 +0000354 logging.debug("Timeout elapsed or process terminated")
355 status = sub.get_status()
lmr6f669ce2009-05-31 19:02:42 +0000356 sub.close()
lmrdc3a5b12009-07-23 01:40:40 +0000357 return status == 0
lmr6f669ce2009-05-31 19:02:42 +0000358
359
360def scp_to_remote(host, port, username, password, local_path, remote_path,
361 timeout=300):
362 """
363 Copy files to a remote host (guest).
364
365 @param host: Hostname of the guest
366 @param username: User that will be used to copy the files
367 @param password: Host's password
368 @param local_path: Path on the local machine where we are copying from
369 @param remote_path: Path on the remote machine where we are copying to
370 @param timeout: Time in seconds that we will wait before giving up to
371 copy the files.
372
373 @return: True on success and False on failure.
374 """
lmrd16a67d2009-06-10 19:52:59 +0000375 command = ("scp -o UserKnownHostsFile=/dev/null -r -P %s %s %s@%s:%s" %
376 (port, local_path, username, host, remote_path))
lmr6f669ce2009-05-31 19:02:42 +0000377 return remote_scp(command, password, timeout)
378
379
380def scp_from_remote(host, port, username, password, remote_path, local_path,
381 timeout=300):
382 """
383 Copy files from a remote host (guest).
384
385 @param host: Hostname of the guest
386 @param username: User that will be used to copy the files
387 @param password: Host's password
388 @param local_path: Path on the local machine where we are copying from
389 @param remote_path: Path on the remote machine where we are copying to
390 @param timeout: Time in seconds that we will wait before giving up to copy
391 the files.
392
393 @return: True on success and False on failure.
394 """
lmrd16a67d2009-06-10 19:52:59 +0000395 command = ("scp -o UserKnownHostsFile=/dev/null -r -P %s %s@%s:%s %s" %
396 (port, username, host, remote_path, local_path))
lmr6f669ce2009-05-31 19:02:42 +0000397 return remote_scp(command, password, timeout)
398
399
400def ssh(host, port, username, password, prompt, timeout=10):
401 """
402 Log into a remote host (guest) using SSH.
403
404 @param host: Hostname of the guest
405 @param username: User that will be used to log into the host.
406 @param password: Host's password
407 @timeout: Time in seconds that we will wait before giving up on logging
408 into the host.
409
410 @return: kvm_spawn object on success and None on failure.
411 """
lmrd16a67d2009-06-10 19:52:59 +0000412 command = ("ssh -o UserKnownHostsFile=/dev/null -p %s %s@%s" %
413 (port, username, host))
lmr6f669ce2009-05-31 19:02:42 +0000414 return remote_login(command, password, prompt, "\n", timeout)
415
416
417def telnet(host, port, username, password, prompt, timeout=10):
418 """
419 Log into a remote host (guest) using Telnet.
420
421 @param host: Hostname of the guest
422 @param username: User that will be used to log into the host.
423 @param password: Host's password
424 @timeout: Time in seconds that we will wait before giving up on logging
425 into the host.
426
427 @return: kvm_spawn object on success and None on failure.
428 """
429 command = "telnet -l %s %s %s" % (username, host, port)
430 return remote_login(command, password, prompt, "\r\n", timeout)
431
432
lmr6f669ce2009-05-31 19:02:42 +0000433# The following are utility functions related to ports.
434
435def is_sshd_running(host, port, timeout=10.0):
436 """
437 Connect to the given host and port and wait for output.
438 Return True if the given host and port are responsive.
439
440 @param host: Host's hostname
441 @param port: Host's port
442 @param timeout: Time (seconds) before we giving up on checking the SSH
443 daemon.
444
445 @return: If output is available, return True. If timeout expires and no
446 output was available, return False.
447 """
448 try:
449 # Try to connect
450 #s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
451 s = socket.socket()
452 s.connect((host, port))
453 except socket.error:
454 # Can't connect -- return False
455 s.close()
456 logging.debug("Could not connect")
457 return False
458 s.setblocking(False)
459 # Wait up to 'timeout' seconds
460 end_time = time.time() + timeout
461 while time.time() < end_time:
462 try:
463 time.sleep(0.1)
464 # Try to receive some text
465 str = s.recv(1024)
466 if len(str) > 0:
467 s.shutdown(socket.SHUT_RDWR)
468 s.close()
469 logging.debug("Success! got string %r" % str)
470 return True
471 except socket.error:
472 # No text was available; try again
473 pass
474 # Timeout elapsed and no text was received
475 s.shutdown(socket.SHUT_RDWR)
476 s.close()
477 logging.debug("Timeout")
478 return False
479
480
481def is_port_free(port):
482 """
483 Return True if the given port is available for use.
484
485 @param port: Port number
486 """
487 try:
488 s = socket.socket()
489 #s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
490 s.bind(("localhost", port))
491 free = True
492 except socket.error:
493 free = False
494 s.close()
495 return free
496
497
498def find_free_port(start_port, end_port):
499 """
500 Return a free port in the range [start_port, end_port).
501
502 @param start_port: First port that will be checked.
503 @param end_port: Port immediately after the last one that will be checked.
504 """
505 for i in range(start_port, end_port):
506 if is_port_free(i):
507 return i
508 return None
509
510
511def find_free_ports(start_port, end_port, count):
512 """
513 Return count free ports in the range [start_port, end_port).
514
515 @count: Initial number of ports known to be free in the range.
516 @param start_port: First port that will be checked.
517 @param end_port: Port immediately after the last one that will be checked.
518 """
519 ports = []
520 i = start_port
521 while i < end_port and count > 0:
522 if is_port_free(i):
523 ports.append(i)
524 count -= 1
525 i += 1
526 return ports
527
528
529# The following are miscellaneous utility functions.
530
531def generate_random_string(length):
532 """
533 Return a random string using alphanumeric characters.
534
535 @length: length of the string that will be generated.
536 """
537 str = ""
538 chars = string.letters + string.digits
539 while length > 0:
540 str += random.choice(chars)
541 length -= 1
542 return str
543
544
545def format_str_for_message(str):
546 """
547 Format str so that it can be appended to a message.
548 If str consists of one line, prefix it with a space.
549 If str consists of multiple lines, prefix it with a newline.
550
551 @param str: string that will be formatted.
552 """
lmr57355592009-08-07 21:55:49 +0000553 lines = str.splitlines()
554 num_lines = len(lines)
555 str = "\n".join(lines)
lmr6f669ce2009-05-31 19:02:42 +0000556 if num_lines == 0:
557 return ""
558 elif num_lines == 1:
559 return " " + str
560 else:
561 return "\n" + str
562
563
564def wait_for(func, timeout, first=0.0, step=1.0, text=None):
565 """
566 If func() evaluates to True before timeout expires, return the
567 value of func(). Otherwise return None.
568
569 @brief: Wait until func() evaluates to True.
570
571 @param timeout: Timeout in seconds
572 @param first: Time to sleep before first attempt
573 @param steps: Time to sleep between attempts in seconds
574 @param text: Text to print while waiting, for debug purposes
575 """
576 start_time = time.time()
577 end_time = time.time() + timeout
578
579 time.sleep(first)
580
581 while time.time() < end_time:
582 if text:
583 logging.debug("%s (%f secs)" % (text, time.time() - start_time))
584
585 output = func()
586 if output:
587 return output
588
589 time.sleep(step)
590
591 logging.debug("Timeout elapsed")
592 return None
593
594
595def md5sum_file(filename, size=None):
596 """
597 Calculate the md5sum of filename.
598 If size is not None, limit to first size bytes.
599 Throw exception if something is wrong with filename.
600 Can be also implemented with bash one-liner (assuming size%1024==0):
601 dd if=filename bs=1024 count=size/1024 | md5sum -
602
603 @param filename: Path of the file that will have its md5sum calculated.
604 @param returns: md5sum of the file.
605 """
606 chunksize = 4096
607 fsize = os.path.getsize(filename)
608 if not size or size>fsize:
609 size = fsize
610 f = open(filename, 'rb')
611 o = md5.new()
612 while size > 0:
613 if chunksize > size:
614 chunksize = size
615 data = f.read(chunksize)
616 if len(data) == 0:
617 logging.debug("Nothing left to read but size=%d" % size)
618 break
619 o.update(data)
620 size -= len(data)
621 f.close()
622 return o.hexdigest()