blob: b2b0d1ab3a3c35d61d05d0725d78c40473b03d1b [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
lmr6f669ce2009-05-31 19:02:42 +00005
6"""
7KVM test utility functions.
8
9@copyright: 2008-2009 Red Hat Inc.
10"""
11
12
13def get_sub_dict(dict, name):
14 """
15 Return a "sub-dict" corresponding to a specific object.
16
17 Operate on a copy of dict: for each key that ends with the suffix
18 "_" + name, strip the suffix from the key, and set the value of
19 the stripped key to that of the key. Return the resulting dict.
20
21 @param name: Suffix of the key we want to set the value.
22 """
23 suffix = "_" + name
24 new_dict = dict.copy()
25 for key in dict.keys():
26 if key.endswith(suffix):
27 new_key = key.split(suffix)[0]
28 new_dict[new_key] = dict[key]
29 return new_dict
30
31
32def get_sub_dict_names(dict, keyword):
33 """
34 Return a list of "sub-dict" names that may be extracted with get_sub_dict.
35
36 This function may be modified to change the behavior of all functions that
37 deal with multiple objects defined in dicts (e.g. VMs, images, NICs).
38
39 @param keyword: A key in dict (e.g. "vms", "images", "nics").
40 """
41 names = dict.get(keyword)
42 if names:
43 return names.split()
44 else:
45 return []
46
47
48# Functions for working with the environment (a dict-like object)
49
50def is_vm(obj):
51 """
52 Tests whether a given object is a VM object.
53
54 @param obj: Python object (pretty much everything on python).
55 """
56 return obj.__class__.__name__ == "VM"
57
58
59def env_get_all_vms(env):
60 """
61 Return a list of all VM objects on a given environment.
62
63 @param env: Dictionary with environment items.
64 """
65 vms = []
66 for obj in env.values():
67 if is_vm(obj):
68 vms.append(obj)
69 return vms
70
71
72def env_get_vm(env, name):
73 """
74 Return a VM object by its name.
75
76 @param name: VM name.
77 """
78 return env.get("vm__%s" % name)
79
80
81def env_register_vm(env, name, vm):
82 """
83 Register a given VM in a given env.
84
85 @param env: Environment where we will register the VM.
86 @param name: VM name.
87 @param vm: VM object.
88 """
89 env["vm__%s" % name] = vm
90
91
92def env_unregister_vm(env, name):
93 """
94 Remove a given VM from a given env.
95
96 @param env: Environment where we will un-register the VM.
97 @param name: VM name.
98 """
99 del env["vm__%s" % name]
100
101
102# Utility functions for dealing with external processes
103
104def pid_exists(pid):
105 """
106 Return True if a given PID exists.
107
108 @param pid: Process ID number.
109 """
110 try:
111 os.kill(pid, 0)
112 return True
113 except:
114 return False
115
116
117def safe_kill(pid, signal):
118 """
119 Attempt to send a signal to a given process that may or may not exist.
120
121 @param signal: Signal number.
122 """
123 try:
124 os.kill(pid, signal)
125 return True
126 except:
127 return False
128
129
lmr3f0b0cc2009-06-10 02:25:23 +0000130def get_latest_kvm_release_tag(release_dir):
131 """
132 Fetches the latest release tag for KVM.
133
134 @param release_dir: KVM source forge download location.
135 """
136 try:
137 page_url = os.path.join(release_dir, "showfiles.php")
138 local_web_page = utils.unmap_url("/", page_url, "/tmp")
139 f = open(local_web_page, "r")
140 data = f.read()
141 f.close()
lmr8ea274b2009-07-06 13:42:35 +0000142 rx = re.compile("kvm-(\d+).tar.gz", re.IGNORECASE)
lmr3f0b0cc2009-06-10 02:25:23 +0000143 matches = rx.findall(data)
144 package_id = matches[0]
lmr3f0b0cc2009-06-10 02:25:23 +0000145 return matches[0] # the first match contains the latest release tag
146 except Exception, e:
147 message = "Could not fetch latest KVM release tag: %s" % str(e)
148 logging.error(message)
149 raise error.TestError(message)
150
151
152def get_git_branch(repository, branch, srcdir, commit=None, lbranch=None):
153 """
154 Retrieves a given git code repository.
155
156 @param repository: Git repository URL
157 """
158 logging.info("Fetching git [REP '%s' BRANCH '%s' TAG '%s'] -> %s",
159 repository, branch, commit, srcdir)
160 if not os.path.exists(srcdir):
161 os.makedirs(srcdir)
162 os.chdir(srcdir)
163
164 if os.path.exists(".git"):
165 utils.system("git reset --hard")
166 else:
167 utils.system("git init")
168
169 if not lbranch:
170 lbranch = branch
171
172 utils.system("git fetch -q -f -u -t %s %s:%s" %
173 (repository, branch, lbranch))
174 utils.system("git checkout %s" % lbranch)
175 if commit:
176 utils.system("git checkout %s" % commit)
177
178 h = utils.system_output('git log --pretty=format:"%H" -1')
179 desc = utils.system_output("git describe")
180 logging.info("Commit hash for %s is %s (%s)" % (repository, h.strip(),
181 desc))
182 return srcdir
183
184
185def unload_module(module_name):
186 """
187 Removes a module. Handles dependencies. If even then it's not possible
188 to remove one of the modules, it will trhow an error.CmdError exception.
189
190 @param module_name: Name of the module we want to remove.
191 """
192 l_raw = utils.system_output("/sbin/lsmod").splitlines()
193 lsmod = [x for x in l_raw if x.split()[0] == module_name]
194 if len(lsmod) > 0:
195 line_parts = lsmod[0].split()
196 if len(line_parts) == 4:
197 submodules = line_parts[3].split(",")
198 for submodule in submodules:
199 unload_module(submodule)
200 utils.system("/sbin/modprobe -r %s" % module_name)
201 logging.info("Module %s unloaded" % module_name)
202 else:
203 logging.info("Module %s is already unloaded" % module_name)
204
205
206def check_kvm_source_dir(source_dir):
207 """
208 Inspects the kvm source directory and verifies its disposition. In some
209 occasions build may be dependant on the source directory disposition.
210 The reason why the return codes are numbers is that we might have more
211 changes on the source directory layout, so it's not scalable to just use
212 strings like 'old_repo', 'new_repo' and such.
213
214 @param source_dir: Source code path that will be inspected.
215 """
216 os.chdir(source_dir)
217 has_qemu_dir = os.path.isdir('qemu')
218 has_kvm_dir = os.path.isdir('kvm')
219 if has_qemu_dir and not has_kvm_dir:
220 logging.debug("qemu directory detected, source dir layout 1")
221 return 1
222 if has_kvm_dir and not has_qemu_dir:
223 logging.debug("kvm directory detected, source dir layout 2")
224 return 2
225 else:
226 raise error.TestError("Unknown source dir layout, cannot proceed.")
227
228
lmr6f669ce2009-05-31 19:02:42 +0000229# The following are a class and functions used for SSH, SCP and Telnet
230# communication with guests.
231
232class kvm_spawn:
233 """
234 This class is used for spawning and controlling a child process.
235 """
236
237 def __init__(self, command, linesep="\n"):
238 """
239 Initialize the class and run command as a child process.
240
241 @param command: Command that will be run.
242 @param linesep: Line separator for the given platform.
243 """
244 self.exitstatus = None
245 self.linesep = linesep
246 (pid, fd) = pty.fork()
247 if pid == 0:
248 os.execv("/bin/sh", ["/bin/sh", "-c", command])
249 else:
250 self.pid = pid
251 self.fd = fd
252
253
254 def set_linesep(self, linesep):
255 """
256 Sets the line separator string (usually "\\n").
257
258 @param linesep: Line separator character.
259 """
260 self.linesep = linesep
261
262
263 def is_responsive(self, timeout=5.0):
264 """
265 Return True if the session is responsive.
266
267 Send a newline to the child process (e.g. SSH or Telnet) and read some
268 output using read_nonblocking.
269 If all is OK, some output should be available (e.g. the shell prompt).
270 In that case return True. Otherwise return False.
271
272 @param timeout: Timeout that will happen before we consider the
273 process unresponsive
274 """
275 self.read_nonblocking(timeout=0.1)
276 self.sendline()
277 output = self.read_nonblocking(timeout=timeout)
278 if output.strip():
279 return True
280 return False
281
282
283 def poll(self):
284 """
285 If the process exited, return its exit status. Otherwise return None.
286 The exit status is stored for use in subsequent calls.
287 """
288 if self.exitstatus != None:
289 return self.exitstatus
290 pid, status = os.waitpid(self.pid, os.WNOHANG)
291 if pid:
292 self.exitstatus = os.WEXITSTATUS(status)
293 return self.exitstatus
294 else:
295 return None
296
297
298 def close(self):
299 """
300 Close the session (close the process filedescriptors and kills the
301 process ID), and return the exit status.
302 """
303 try:
304 os.close(self.fd)
305 os.kill(self.pid, signal.SIGTERM)
306 except OSError:
307 pass
308 return self.poll()
309
310
311 def sendline(self, str=""):
312 """
313 Sends a string followed by a line separator to the child process.
314
315 @param str: String that will be sent to the child process.
316 """
317 try:
318 os.write(self.fd, str + self.linesep)
319 except OSError:
320 pass
321
322
323 def read_nonblocking(self, timeout=1.0):
324 """
325 Read from child until there is nothing to read for timeout seconds.
326
327 @param timeout: Time (seconds) of wait before we give up reading from
328 the child process.
329 """
330 data = ""
331 while True:
332 r, w, x = select.select([self.fd], [], [], timeout)
333 if self.fd in r:
334 try:
335 data += os.read(self.fd, 1024)
336 except OSError:
337 return data
338 else:
339 return data
340
341
342 def match_patterns(self, str, patterns):
343 """
344 Match str against a list of patterns.
345
346 Return the index of the first pattern that matches a substring of str.
347 None and empty strings in patterns are ignored.
348 If no match is found, return None.
349
350 @param patterns: List of strings (regular expression patterns).
351 """
352 for i in range(len(patterns)):
353 if not patterns[i]:
354 continue
355 exp = re.compile(patterns[i])
356 if exp.search(str):
357 return i
358
359
360 def read_until_output_matches(self, patterns, filter=lambda(x):x,
361 timeout=30.0, internal_timeout=1.0,
362 print_func=None):
363 """
364 Read using read_nonblocking until a match is found using match_patterns,
365 or until timeout expires. Before attempting to search for a match, the
366 data is filtered using the filter function provided.
367
368 @brief: Read from child using read_nonblocking until a pattern
369 matches.
370 @param patterns: List of strings (regular expression patterns)
371 @param filter: Function to apply to the data read from the child before
372 attempting to match it against the patterns (should take and
373 return a string)
374 @param timeout: The duration (in seconds) to wait until a match is
375 found
376 @param internal_timeout: The timeout to pass to read_nonblocking
377 @param print_func: A function to be used to print the data being read
378 (should take a string parameter)
379 @return: Tuple containing the match index (or None if no match was
380 found) and the data read so far.
381 """
382 match = None
383 data = ""
384
385 end_time = time.time() + timeout
386 while time.time() < end_time:
387 # Read data from child
388 newdata = self.read_nonblocking(internal_timeout)
389 # Print it if necessary
390 if print_func and newdata:
391 map(print_func, newdata.splitlines())
392 data += newdata
393
394 done = False
395 # Look for patterns
396 match = self.match_patterns(filter(data), patterns)
397 if match != None:
398 done = True
399 # Check if child has died
400 if self.poll() != None:
lmr3f0b0cc2009-06-10 02:25:23 +0000401 logging.debug("Process terminated with status %d", self.poll())
lmr6f669ce2009-05-31 19:02:42 +0000402 done = True
403 # Are we done?
404 if done: break
405
406 # Print some debugging info
407 if match == None and self.poll() != 0:
lmr3f0b0cc2009-06-10 02:25:23 +0000408 logging.debug("Timeout elapsed or process terminated. Output: %s",
lmr6f669ce2009-05-31 19:02:42 +0000409 format_str_for_message(data.strip()))
410
411 return (match, data)
412
413
414 def get_last_word(self, str):
415 """
416 Return the last word in str.
417
418 @param str: String that will be analyzed.
419 """
420 if str:
421 return str.split()[-1]
422 else:
423 return ""
424
425
426 def get_last_line(self, str):
427 """
428 Return the last non-empty line in str.
429
430 @param str: String that will be analyzed.
431 """
432 last_line = ""
433 for line in str.splitlines():
434 if line != "":
435 last_line = line
436 return last_line
437
438
439 def read_until_last_word_matches(self, patterns, timeout=30.0,
440 internal_timeout=1.0, print_func=None):
441 """
442 Read using read_nonblocking until the last word of the output matches
443 one of the patterns (using match_patterns), or until timeout expires.
444
445 @param patterns: A list of strings (regular expression patterns)
446 @param timeout: The duration (in seconds) to wait until a match is
447 found
448 @param internal_timeout: The timeout to pass to read_nonblocking
449 @param print_func: A function to be used to print the data being read
450 (should take a string parameter)
451 @return: A tuple containing the match index (or None if no match was
452 found) and the data read so far.
453 """
454 return self.read_until_output_matches(patterns, self.get_last_word,
455 timeout, internal_timeout,
456 print_func)
457
458
459 def read_until_last_line_matches(self, patterns, timeout=30.0,
460 internal_timeout=1.0, print_func=None):
461 """
462 Read using read_nonblocking until the last non-empty line of the output
463 matches one of the patterns (using match_patterns), or until timeout
464 expires. Return a tuple containing the match index (or None if no match
465 was found) and the data read so far.
466
467 @brief: Read using read_nonblocking until the last non-empty line
468 matches a pattern.
469
470 @param patterns: A list of strings (regular expression patterns)
471 @param timeout: The duration (in seconds) to wait until a match is
472 found
473 @param internal_timeout: The timeout to pass to read_nonblocking
474 @param print_func: A function to be used to print the data being read
475 (should take a string parameter)
476 """
477 return self.read_until_output_matches(patterns, self.get_last_line,
478 timeout, internal_timeout,
479 print_func)
480
481
482 def set_prompt(self, prompt):
483 """
484 Set the prompt attribute for later use by read_up_to_prompt.
485
486 @param: String that describes the prompt contents.
487 """
488 self.prompt = prompt
489
490
491 def read_up_to_prompt(self, timeout=30.0, internal_timeout=1.0,
492 print_func=None):
493 """
494 Read using read_nonblocking until the last non-empty line of the output
495 matches the prompt regular expression set by set_prompt, or until
496 timeout expires.
497
498 @brief: Read using read_nonblocking until the last non-empty line
499 matches the prompt.
500
501 @param timeout: The duration (in seconds) to wait until a match is
502 found
503 @param internal_timeout: The timeout to pass to read_nonblocking
504 @param print_func: A function to be used to print the data being
505 read (should take a string parameter)
506
507 @return: A tuple containing True/False indicating whether the prompt
508 was found, and the data read so far.
509 """
510 (match, output) = self.read_until_last_line_matches([self.prompt],
511 timeout,
512 internal_timeout,
513 print_func)
514 if match == None:
515 return (False, output)
516 else:
517 return (True, output)
518
519
520 def set_status_test_command(self, status_test_command):
521 """
522 Set the command to be sent in order to get the last exit status.
523
524 @param status_test_command: Command that will be sent to get the last
525 exit status.
526 """
527 self.status_test_command = status_test_command
528
529
530 def get_command_status_output(self, command, timeout=30.0,
531 internal_timeout=1.0, print_func=None):
532 """
533 Send a command and return its exit status and output.
534
535 @param command: Command to send
536 @param timeout: The duration (in seconds) to wait until a match is
537 found
538 @param internal_timeout: The timeout to pass to read_nonblocking
539 @param print_func: A function to be used to print the data being read
540 (should take a string parameter)
541
542 @return: A tuple (status, output) where status is the exit status or
543 None if no exit status is available (e.g. timeout elapsed), and
544 output is the output of command.
545 """
546 # Print some debugging info
547 logging.debug("Sending command: %s" % command)
548
549 # Read everything that's waiting to be read
550 self.read_nonblocking(0.1)
551
552 # Send the command and get its output
553 self.sendline(command)
554 (match, output) = self.read_up_to_prompt(timeout, internal_timeout,
555 print_func)
556 if not match:
557 return (None, "\n".join(output.splitlines()[1:]))
558 output = "\n".join(output.splitlines()[1:-1])
559
560 # Send the 'echo ...' command to get the last exit status
561 self.sendline(self.status_test_command)
562 (match, status) = self.read_up_to_prompt(10.0, internal_timeout)
563 if not match:
564 return (None, output)
565 status = int("\n".join(status.splitlines()[1:-1]).strip())
566
567 # Print some debugging info
568 if status != 0:
lmra10d17c2009-06-10 19:58:09 +0000569 logging.debug("Command failed; status: %d, output:%s", status,
570 format_str_for_message(output.strip()))
lmr6f669ce2009-05-31 19:02:42 +0000571
572 return (status, output)
573
574
575 def get_command_status(self, command, timeout=30.0, internal_timeout=1.0,
576 print_func=None):
577 """
578 Send a command and return its exit status.
579
580 @param command: Command to send
581 @param timeout: The duration (in seconds) to wait until a match is
582 found
583 @param internal_timeout: The timeout to pass to read_nonblocking
584 @param print_func: A function to be used to print the data being read
585 (should take a string parameter)
586
587 @return: Exit status or None if no exit status is available (e.g.
588 timeout elapsed).
589 """
590 (status, output) = self.get_command_status_output(command, timeout,
591 internal_timeout,
592 print_func)
593 return status
594
595
596 def get_command_output(self, command, timeout=30.0, internal_timeout=1.0,
597 print_func=None):
598 """
599 Send a command and return its output.
600
601 @param command: Command to send
602 @param timeout: The duration (in seconds) to wait until a match is
603 found
604 @param internal_timeout: The timeout to pass to read_nonblocking
605 @param print_func: A function to be used to print the data being read
606 (should take a string parameter)
607 """
608 (status, output) = self.get_command_status_output(command, timeout,
609 internal_timeout,
610 print_func)
611 return output
612
613
614def remote_login(command, password, prompt, linesep="\n", timeout=10):
615 """
616 Log into a remote host (guest) using SSH or Telnet. Run the given command
617 using kvm_spawn and provide answers to the questions asked. If timeout
618 expires while waiting for output from the child (e.g. a password prompt
619 or a shell prompt) -- fail.
620
621 @brief: Log into a remote host (guest) using SSH or Telnet.
622
623 @param command: The command to execute (e.g. "ssh root@localhost")
624 @param password: The password to send in reply to a password prompt
625 @param prompt: The shell prompt that indicates a successful login
626 @param linesep: The line separator to send instead of "\\n"
627 (sometimes "\\r\\n" is required)
628 @param timeout: The maximal time duration (in seconds) to wait for each
629 step of the login procedure (i.e. the "Are you sure" prompt, the
630 password prompt, the shell prompt, etc)
631
632 @return Return the kvm_spawn object on success and None on failure.
633 """
634 sub = kvm_spawn(command, linesep)
635 sub.set_prompt(prompt)
636
637 password_prompt_count = 0
638
639 logging.debug("Trying to login...")
640
641 while True:
642 (match, text) = sub.read_until_last_line_matches(
lmr3ca79fe2009-06-10 19:24:26 +0000643 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"^\s*[Ll]ogin:\s*$",
644 r"[Cc]onnection.*closed", r"[Cc]onnection.*refused", prompt],
lmr6f669ce2009-05-31 19:02:42 +0000645 timeout=timeout, internal_timeout=0.5)
646 if match == 0: # "Are you sure you want to continue connecting"
647 logging.debug("Got 'Are you sure...'; sending 'yes'")
648 sub.sendline("yes")
649 continue
650 elif match == 1: # "password:"
651 if password_prompt_count == 0:
652 logging.debug("Got password prompt; sending '%s'" % password)
653 sub.sendline(password)
654 password_prompt_count += 1
655 continue
656 else:
657 logging.debug("Got password prompt again")
658 sub.close()
659 return None
660 elif match == 2: # "login:"
661 logging.debug("Got unexpected login prompt")
662 sub.close()
663 return None
664 elif match == 3: # "Connection closed"
665 logging.debug("Got 'Connection closed'")
666 sub.close()
667 return None
lmr3ca79fe2009-06-10 19:24:26 +0000668 elif match == 4: # "Connection refused"
lmr0d2ed1f2009-07-01 03:23:18 +0000669 logging.debug("Got 'Connection refused'")
lmr3ca79fe2009-06-10 19:24:26 +0000670 sub.close()
671 return None
672 elif match == 5: # prompt
lmr6f669ce2009-05-31 19:02:42 +0000673 logging.debug("Got shell prompt -- logged in")
674 return sub
675 else: # match == None
lmr3ca79fe2009-06-10 19:24:26 +0000676 logging.debug("Timeout elapsed or process terminated")
lmr6f669ce2009-05-31 19:02:42 +0000677 sub.close()
678 return None
679
680
681def remote_scp(command, password, timeout=300, login_timeout=10):
682 """
683 Run the given command using kvm_spawn and provide answers to the questions
684 asked. If timeout expires while waiting for the transfer to complete ,
685 fail. If login_timeout expires while waiting for output from the child
686 (e.g. a password prompt), fail.
687
688 @brief: Transfer files using SCP, given a command line.
689
690 @param command: The command to execute
691 (e.g. "scp -r foobar root@localhost:/tmp/").
692 @param password: The password to send in reply to a password prompt.
693 @param timeout: The time duration (in seconds) to wait for the transfer
694 to complete.
695 @param login_timeout: The maximal time duration (in seconds) to wait for
696 each step of the login procedure (i.e. the "Are you sure" prompt or the
697 password prompt)
698
699 @return: True if the transfer succeeds and False on failure.
700 """
701 sub = kvm_spawn(command)
702
703 password_prompt_count = 0
704 _timeout = login_timeout
705
706 logging.debug("Trying to login...")
707
708 while True:
709 (match, text) = sub.read_until_last_line_matches(
lmr3ca79fe2009-06-10 19:24:26 +0000710 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"lost connection"],
lmr6f669ce2009-05-31 19:02:42 +0000711 timeout=_timeout, internal_timeout=0.5)
712 if match == 0: # "Are you sure you want to continue connecting"
713 logging.debug("Got 'Are you sure...'; sending 'yes'")
714 sub.sendline("yes")
715 continue
716 elif match == 1: # "password:"
717 if password_prompt_count == 0:
718 logging.debug("Got password prompt; sending '%s'" % password)
719 sub.sendline(password)
720 password_prompt_count += 1
721 _timeout = timeout
722 continue
723 else:
724 logging.debug("Got password prompt again")
725 sub.close()
726 return False
727 elif match == 2: # "lost connection"
728 logging.debug("Got 'lost connection'")
729 sub.close()
730 return False
731 else: # match == None
732 logging.debug("Timeout or process terminated")
733 sub.close()
734 return sub.poll() == 0
735
736
737def scp_to_remote(host, port, username, password, local_path, remote_path,
738 timeout=300):
739 """
740 Copy files to a remote host (guest).
741
742 @param host: Hostname of the guest
743 @param username: User that will be used to copy the files
744 @param password: Host's password
745 @param local_path: Path on the local machine where we are copying from
746 @param remote_path: Path on the remote machine where we are copying to
747 @param timeout: Time in seconds that we will wait before giving up to
748 copy the files.
749
750 @return: True on success and False on failure.
751 """
lmrd16a67d2009-06-10 19:52:59 +0000752 command = ("scp -o UserKnownHostsFile=/dev/null -r -P %s %s %s@%s:%s" %
753 (port, local_path, username, host, remote_path))
lmr6f669ce2009-05-31 19:02:42 +0000754 return remote_scp(command, password, timeout)
755
756
757def scp_from_remote(host, port, username, password, remote_path, local_path,
758 timeout=300):
759 """
760 Copy files from a remote host (guest).
761
762 @param host: Hostname of the guest
763 @param username: User that will be used to copy the files
764 @param password: Host's password
765 @param local_path: Path on the local machine where we are copying from
766 @param remote_path: Path on the remote machine where we are copying to
767 @param timeout: Time in seconds that we will wait before giving up to copy
768 the files.
769
770 @return: True on success and False on failure.
771 """
lmrd16a67d2009-06-10 19:52:59 +0000772 command = ("scp -o UserKnownHostsFile=/dev/null -r -P %s %s@%s:%s %s" %
773 (port, username, host, remote_path, local_path))
lmr6f669ce2009-05-31 19:02:42 +0000774 return remote_scp(command, password, timeout)
775
776
777def ssh(host, port, username, password, prompt, timeout=10):
778 """
779 Log into a remote host (guest) using SSH.
780
781 @param host: Hostname of the guest
782 @param username: User that will be used to log into the host.
783 @param password: Host's password
784 @timeout: Time in seconds that we will wait before giving up on logging
785 into the host.
786
787 @return: kvm_spawn object on success and None on failure.
788 """
lmrd16a67d2009-06-10 19:52:59 +0000789 command = ("ssh -o UserKnownHostsFile=/dev/null -p %s %s@%s" %
790 (port, username, host))
lmr6f669ce2009-05-31 19:02:42 +0000791 return remote_login(command, password, prompt, "\n", timeout)
792
793
794def telnet(host, port, username, password, prompt, timeout=10):
795 """
796 Log into a remote host (guest) using Telnet.
797
798 @param host: Hostname of the guest
799 @param username: User that will be used to log into the host.
800 @param password: Host's password
801 @timeout: Time in seconds that we will wait before giving up on logging
802 into the host.
803
804 @return: kvm_spawn object on success and None on failure.
805 """
806 command = "telnet -l %s %s %s" % (username, host, port)
807 return remote_login(command, password, prompt, "\r\n", timeout)
808
809
810# The following are functions used for running commands in the background.
811
812def track_process(sub, status_output=None, term_func=None, stdout_func=None,
813 prefix=""):
814 """
815 Read lines from the stdout pipe of the subprocess. Pass each line to
816 stdout_func prefixed by prefix. Place the lines in status_output[1].
817 When the subprocess exits, call term_func. Place the exit status in
818 status_output[0].
819
820 @brief Track a subprocess and report its output and termination.
821
822 @param sub: An object returned by subprocess.Popen
823 @param status_output: A list in which the exit status and output are to be
824 stored.
825 @param term_func: A function to call when the process terminates
826 (should take no parameters)
827 @param stdout_func: A function to call with each line of output from the
828 subprocess (should take a string parameter)
829
830 @param prefix -- a string to pre-pend to each line of the output, before
831 passing it to stdout_func
832 """
833 while True:
834 # Read a single line from stdout
835 text = sub.stdout.readline()
836 # If the subprocess exited...
837 if text == "":
838 # Get exit code
839 status = sub.wait()
840 # Report it
841 if status_output:
842 status_output[0] = status
843 # Call term_func
844 if term_func:
845 term_func()
846 return
847 # Report the text
848 if status_output:
849 status_output[1] += text
850 # Call stdout_func with the returned text
851 if stdout_func:
852 text = prefix + text.strip()
853 # We need to sanitize the text before passing it to the logging
854 # system
855 text = text.decode('utf-8', 'replace')
856 stdout_func(text)
857
858
859def run_bg(command, term_func=None, stdout_func=None, prefix="", timeout=1.0):
860 """
861 Run command as a subprocess. Call stdout_func with each line of output from
862 the subprocess (prefixed by prefix). Call term_func when the subprocess
863 terminates. If timeout expires and the subprocess is still running, return.
864
865 @brief: Run a subprocess in the background and collect its output and
866 exit status.
867
868 @param command: The shell command to execute
869 @param term_func: A function to call when the process terminates
870 (should take no parameters)
871 @param stdout_func: A function to call with each line of output from
872 the subprocess (should take a string parameter)
873 @param prefix: A string to pre-pend to each line of the output, before
874 passing it to stdout_func
875 @param timeout: Time duration (in seconds) to wait for the subprocess to
876 terminate before returning
877
878 @return: A 3-tuple containing the exit status (None if the subprocess is
879 still running), the PID of the subprocess (None if the subprocess
880 terminated), and the output collected so far.
881 """
882 # Start the process
883 sub = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE,
884 stderr=subprocess.STDOUT)
885 # Start the tracking thread
886 status_output = [None, ""]
887 thread.start_new_thread(track_process, (sub, status_output, term_func,
888 stdout_func, prefix))
889 # Wait up to timeout secs for the process to exit
890 end_time = time.time() + timeout
891 while time.time() < end_time:
892 # If the process exited, return
893 if status_output[0] != None:
894 return (status_output[0], None, status_output[1])
895 # Otherwise, sleep for a while
896 time.sleep(0.1)
897 # Report the PID and the output collected so far
898 return (None, sub.pid, status_output[1])
899
900
901# The following are utility functions related to ports.
902
903def is_sshd_running(host, port, timeout=10.0):
904 """
905 Connect to the given host and port and wait for output.
906 Return True if the given host and port are responsive.
907
908 @param host: Host's hostname
909 @param port: Host's port
910 @param timeout: Time (seconds) before we giving up on checking the SSH
911 daemon.
912
913 @return: If output is available, return True. If timeout expires and no
914 output was available, return False.
915 """
916 try:
917 # Try to connect
918 #s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
919 s = socket.socket()
920 s.connect((host, port))
921 except socket.error:
922 # Can't connect -- return False
923 s.close()
924 logging.debug("Could not connect")
925 return False
926 s.setblocking(False)
927 # Wait up to 'timeout' seconds
928 end_time = time.time() + timeout
929 while time.time() < end_time:
930 try:
931 time.sleep(0.1)
932 # Try to receive some text
933 str = s.recv(1024)
934 if len(str) > 0:
935 s.shutdown(socket.SHUT_RDWR)
936 s.close()
937 logging.debug("Success! got string %r" % str)
938 return True
939 except socket.error:
940 # No text was available; try again
941 pass
942 # Timeout elapsed and no text was received
943 s.shutdown(socket.SHUT_RDWR)
944 s.close()
945 logging.debug("Timeout")
946 return False
947
948
949def is_port_free(port):
950 """
951 Return True if the given port is available for use.
952
953 @param port: Port number
954 """
955 try:
956 s = socket.socket()
957 #s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
958 s.bind(("localhost", port))
959 free = True
960 except socket.error:
961 free = False
962 s.close()
963 return free
964
965
966def find_free_port(start_port, end_port):
967 """
968 Return a free port in the range [start_port, end_port).
969
970 @param start_port: First port that will be checked.
971 @param end_port: Port immediately after the last one that will be checked.
972 """
973 for i in range(start_port, end_port):
974 if is_port_free(i):
975 return i
976 return None
977
978
979def find_free_ports(start_port, end_port, count):
980 """
981 Return count free ports in the range [start_port, end_port).
982
983 @count: Initial number of ports known to be free in the range.
984 @param start_port: First port that will be checked.
985 @param end_port: Port immediately after the last one that will be checked.
986 """
987 ports = []
988 i = start_port
989 while i < end_port and count > 0:
990 if is_port_free(i):
991 ports.append(i)
992 count -= 1
993 i += 1
994 return ports
995
996
997# The following are miscellaneous utility functions.
998
999def generate_random_string(length):
1000 """
1001 Return a random string using alphanumeric characters.
1002
1003 @length: length of the string that will be generated.
1004 """
1005 str = ""
1006 chars = string.letters + string.digits
1007 while length > 0:
1008 str += random.choice(chars)
1009 length -= 1
1010 return str
1011
1012
1013def format_str_for_message(str):
1014 """
1015 Format str so that it can be appended to a message.
1016 If str consists of one line, prefix it with a space.
1017 If str consists of multiple lines, prefix it with a newline.
1018
1019 @param str: string that will be formatted.
1020 """
1021 num_lines = len(str.splitlines())
1022 if num_lines == 0:
1023 return ""
1024 elif num_lines == 1:
1025 return " " + str
1026 else:
1027 return "\n" + str
1028
1029
1030def wait_for(func, timeout, first=0.0, step=1.0, text=None):
1031 """
1032 If func() evaluates to True before timeout expires, return the
1033 value of func(). Otherwise return None.
1034
1035 @brief: Wait until func() evaluates to True.
1036
1037 @param timeout: Timeout in seconds
1038 @param first: Time to sleep before first attempt
1039 @param steps: Time to sleep between attempts in seconds
1040 @param text: Text to print while waiting, for debug purposes
1041 """
1042 start_time = time.time()
1043 end_time = time.time() + timeout
1044
1045 time.sleep(first)
1046
1047 while time.time() < end_time:
1048 if text:
1049 logging.debug("%s (%f secs)" % (text, time.time() - start_time))
1050
1051 output = func()
1052 if output:
1053 return output
1054
1055 time.sleep(step)
1056
1057 logging.debug("Timeout elapsed")
1058 return None
1059
1060
1061def md5sum_file(filename, size=None):
1062 """
1063 Calculate the md5sum of filename.
1064 If size is not None, limit to first size bytes.
1065 Throw exception if something is wrong with filename.
1066 Can be also implemented with bash one-liner (assuming size%1024==0):
1067 dd if=filename bs=1024 count=size/1024 | md5sum -
1068
1069 @param filename: Path of the file that will have its md5sum calculated.
1070 @param returns: md5sum of the file.
1071 """
1072 chunksize = 4096
1073 fsize = os.path.getsize(filename)
1074 if not size or size>fsize:
1075 size = fsize
1076 f = open(filename, 'rb')
1077 o = md5.new()
1078 while size > 0:
1079 if chunksize > size:
1080 chunksize = size
1081 data = f.read(chunksize)
1082 if len(data) == 0:
1083 logging.debug("Nothing left to read but size=%d" % size)
1084 break
1085 o.update(data)
1086 size -= len(data)
1087 f.close()
1088 return o.hexdigest()