blob: a13936717880e95e2592b5d1258de577850bdbbf [file] [log] [blame]
lmr6f669ce2009-05-31 19:02:42 +00001"""
2KVM test utility functions.
3
4@copyright: 2008-2009 Red Hat Inc.
5"""
6
lmrb635b862009-09-10 14:53:21 +00007import md5, thread, subprocess, time, string, random, socket, os, signal, pty
8import select, re, logging, commands
9from autotest_lib.client.bin import utils
10from autotest_lib.client.common_lib import error
11import kvm_subprocess
12
lmr6f669ce2009-05-31 19:02:42 +000013
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
lmrac5089b2009-08-13 04:05:47 +000049# Functions related to MAC/IP addresses
50
51def mac_str_to_int(addr):
52 """
53 Convert MAC address string to integer.
54
55 @param addr: String representing the MAC address.
56 """
57 return sum(int(s, 16) * 256 ** i
58 for i, s in enumerate(reversed(addr.split(":"))))
59
60
61def mac_int_to_str(addr):
62 """
63 Convert MAC address integer to string.
64
65 @param addr: Integer representing the MAC address.
66 """
67 return ":".join("%02x" % (addr >> 8 * i & 0xFF)
68 for i in reversed(range(6)))
69
70
71def ip_str_to_int(addr):
72 """
73 Convert IP address string to integer.
74
75 @param addr: String representing the IP address.
76 """
77 return sum(int(s) * 256 ** i
78 for i, s in enumerate(reversed(addr.split("."))))
79
80
81def ip_int_to_str(addr):
82 """
83 Convert IP address integer to string.
84
85 @param addr: Integer representing the IP address.
86 """
87 return ".".join(str(addr >> 8 * i & 0xFF)
88 for i in reversed(range(4)))
89
90
91def offset_mac(base, offset):
92 """
93 Add offset to a given MAC address.
94
95 @param base: String representing a MAC address.
96 @param offset: Offset to add to base (integer)
97 @return: A string representing the offset MAC address.
98 """
99 return mac_int_to_str(mac_str_to_int(base) + offset)
100
101
102def offset_ip(base, offset):
103 """
104 Add offset to a given IP address.
105
106 @param base: String representing an IP address.
107 @param offset: Offset to add to base (integer)
108 @return: A string representing the offset IP address.
109 """
110 return ip_int_to_str(ip_str_to_int(base) + offset)
111
112
113def get_mac_ip_pair_from_dict(dict):
114 """
115 Fetch a MAC-IP address pair from dict and return it.
116
117 The parameters in dict are expected to conform to a certain syntax.
118 Typical usage may be:
119
120 address_ranges = r1 r2 r3
121
122 address_range_base_mac_r1 = 55:44:33:22:11:00
123 address_range_base_ip_r1 = 10.0.0.0
124 address_range_size_r1 = 16
125
126 address_range_base_mac_r2 = 55:44:33:22:11:40
127 address_range_base_ip_r2 = 10.0.0.60
128 address_range_size_r2 = 25
129
130 address_range_base_mac_r3 = 55:44:33:22:12:10
131 address_range_base_ip_r3 = 10.0.1.20
132 address_range_size_r3 = 230
133
134 address_index = 0
135
136 All parameters except address_index specify a MAC-IP address pool. The
137 pool consists of several MAC-IP address ranges.
138 address_index specified the index of the desired MAC-IP pair from the pool.
139
140 @param dict: The dictionary from which to fetch the addresses.
141 """
142 index = int(dict.get("address_index", 0))
143 for mac_range_name in get_sub_dict_names(dict, "address_ranges"):
144 mac_range_params = get_sub_dict(dict, mac_range_name)
145 mac_base = mac_range_params.get("address_range_base_mac")
146 ip_base = mac_range_params.get("address_range_base_ip")
147 size = int(mac_range_params.get("address_range_size", 1))
148 if index < size:
149 return (mac_base and offset_mac(mac_base, index),
150 ip_base and offset_ip(ip_base, index))
151 index -= size
152 return (None, None)
153
154
lmr53d3e932009-09-09 21:56:18 +0000155def verify_ip_address_ownership(ip, macs, timeout=10.0):
lmree90dd92009-08-13 04:13:39 +0000156 """
lmr53d3e932009-09-09 21:56:18 +0000157 Use arping and the ARP cache to make sure a given IP address belongs to one
158 of the given MAC addresses.
lmree90dd92009-08-13 04:13:39 +0000159
160 @param ip: An IP address.
161 @param macs: A list or tuple of MAC addresses.
162 @return: True iff ip is assigned to a MAC address in macs.
163 """
lmr53d3e932009-09-09 21:56:18 +0000164 # Compile a regex that matches the given IP address and any of the given
165 # MAC addresses
lmree90dd92009-08-13 04:13:39 +0000166 mac_regex = "|".join("(%s)" % mac for mac in macs)
167 regex = re.compile(r"\b%s\b.*\b(%s)\b" % (ip, mac_regex))
168
lmr53d3e932009-09-09 21:56:18 +0000169 # Check the ARP cache
170 o = commands.getoutput("/sbin/arp -n")
171 if re.search(regex, o, re.IGNORECASE):
lmree90dd92009-08-13 04:13:39 +0000172 return True
173
lmr53d3e932009-09-09 21:56:18 +0000174 # Get the name of the bridge device for arping
175 o = commands.getoutput("/sbin/ip route get %s" % ip)
176 dev = re.findall("dev\s+\S+", o, re.IGNORECASE)
177 if not dev:
178 return False
179 dev = dev[0].split()[-1]
180
181 # Send an ARP request
182 o = commands.getoutput("/sbin/arping -f -c 3 -I %s %s" % (dev, ip))
183 return bool(re.search(regex, o, re.IGNORECASE))
lmree90dd92009-08-13 04:13:39 +0000184
185
lmr6f669ce2009-05-31 19:02:42 +0000186# Functions for working with the environment (a dict-like object)
187
188def is_vm(obj):
189 """
190 Tests whether a given object is a VM object.
191
192 @param obj: Python object (pretty much everything on python).
193 """
194 return obj.__class__.__name__ == "VM"
195
196
197def env_get_all_vms(env):
198 """
199 Return a list of all VM objects on a given environment.
200
201 @param env: Dictionary with environment items.
202 """
203 vms = []
204 for obj in env.values():
205 if is_vm(obj):
206 vms.append(obj)
207 return vms
208
209
210def env_get_vm(env, name):
211 """
212 Return a VM object by its name.
213
214 @param name: VM name.
215 """
216 return env.get("vm__%s" % name)
217
218
219def env_register_vm(env, name, vm):
220 """
221 Register a given VM in a given env.
222
223 @param env: Environment where we will register the VM.
224 @param name: VM name.
225 @param vm: VM object.
226 """
227 env["vm__%s" % name] = vm
228
229
230def env_unregister_vm(env, name):
231 """
232 Remove a given VM from a given env.
233
234 @param env: Environment where we will un-register the VM.
235 @param name: VM name.
236 """
237 del env["vm__%s" % name]
238
239
240# Utility functions for dealing with external processes
241
242def pid_exists(pid):
243 """
244 Return True if a given PID exists.
245
246 @param pid: Process ID number.
247 """
248 try:
249 os.kill(pid, 0)
250 return True
251 except:
252 return False
253
254
255def safe_kill(pid, signal):
256 """
257 Attempt to send a signal to a given process that may or may not exist.
258
259 @param signal: Signal number.
260 """
261 try:
262 os.kill(pid, signal)
263 return True
264 except:
265 return False
266
267
lmr1aeaefb2009-09-09 22:12:49 +0000268def kill_process_tree(pid, sig=signal.SIGKILL):
269 """Signal a process and all of its children.
270
271 If the process does not exist -- return.
272
273 @param pid: The pid of the process to signal.
274 @param sig: The signal to send to the processes.
275 """
276 if not safe_kill(pid, signal.SIGSTOP):
277 return
278 children = commands.getoutput("ps --ppid=%d -o pid=" % pid).split()
279 for child in children:
280 kill_process_tree(int(child), sig)
281 safe_kill(pid, sig)
282 safe_kill(pid, signal.SIGCONT)
283
284
lmr3f0b0cc2009-06-10 02:25:23 +0000285def get_latest_kvm_release_tag(release_dir):
286 """
287 Fetches the latest release tag for KVM.
288
289 @param release_dir: KVM source forge download location.
290 """
291 try:
292 page_url = os.path.join(release_dir, "showfiles.php")
293 local_web_page = utils.unmap_url("/", page_url, "/tmp")
294 f = open(local_web_page, "r")
295 data = f.read()
296 f.close()
lmr8ea274b2009-07-06 13:42:35 +0000297 rx = re.compile("kvm-(\d+).tar.gz", re.IGNORECASE)
lmr3f0b0cc2009-06-10 02:25:23 +0000298 matches = rx.findall(data)
lmr32525382009-08-10 13:53:37 +0000299 # In all regexp matches to something that looks like a release tag,
300 # get the largest integer. That will be our latest release tag.
301 latest_tag = max(int(x) for x in matches)
302 return str(latest_tag)
lmr3f0b0cc2009-06-10 02:25:23 +0000303 except Exception, e:
304 message = "Could not fetch latest KVM release tag: %s" % str(e)
305 logging.error(message)
306 raise error.TestError(message)
307
308
309def get_git_branch(repository, branch, srcdir, commit=None, lbranch=None):
310 """
311 Retrieves a given git code repository.
312
313 @param repository: Git repository URL
314 """
315 logging.info("Fetching git [REP '%s' BRANCH '%s' TAG '%s'] -> %s",
316 repository, branch, commit, srcdir)
317 if not os.path.exists(srcdir):
318 os.makedirs(srcdir)
319 os.chdir(srcdir)
320
321 if os.path.exists(".git"):
322 utils.system("git reset --hard")
323 else:
324 utils.system("git init")
325
326 if not lbranch:
327 lbranch = branch
328
329 utils.system("git fetch -q -f -u -t %s %s:%s" %
330 (repository, branch, lbranch))
331 utils.system("git checkout %s" % lbranch)
332 if commit:
333 utils.system("git checkout %s" % commit)
334
335 h = utils.system_output('git log --pretty=format:"%H" -1')
336 desc = utils.system_output("git describe")
337 logging.info("Commit hash for %s is %s (%s)" % (repository, h.strip(),
338 desc))
339 return srcdir
340
341
342def unload_module(module_name):
343 """
344 Removes a module. Handles dependencies. If even then it's not possible
345 to remove one of the modules, it will trhow an error.CmdError exception.
346
347 @param module_name: Name of the module we want to remove.
348 """
349 l_raw = utils.system_output("/sbin/lsmod").splitlines()
350 lsmod = [x for x in l_raw if x.split()[0] == module_name]
351 if len(lsmod) > 0:
352 line_parts = lsmod[0].split()
353 if len(line_parts) == 4:
354 submodules = line_parts[3].split(",")
355 for submodule in submodules:
356 unload_module(submodule)
357 utils.system("/sbin/modprobe -r %s" % module_name)
358 logging.info("Module %s unloaded" % module_name)
359 else:
360 logging.info("Module %s is already unloaded" % module_name)
361
362
363def check_kvm_source_dir(source_dir):
364 """
365 Inspects the kvm source directory and verifies its disposition. In some
366 occasions build may be dependant on the source directory disposition.
367 The reason why the return codes are numbers is that we might have more
368 changes on the source directory layout, so it's not scalable to just use
369 strings like 'old_repo', 'new_repo' and such.
370
371 @param source_dir: Source code path that will be inspected.
372 """
373 os.chdir(source_dir)
374 has_qemu_dir = os.path.isdir('qemu')
375 has_kvm_dir = os.path.isdir('kvm')
376 if has_qemu_dir and not has_kvm_dir:
377 logging.debug("qemu directory detected, source dir layout 1")
378 return 1
379 if has_kvm_dir and not has_qemu_dir:
380 logging.debug("kvm directory detected, source dir layout 2")
381 return 2
382 else:
383 raise error.TestError("Unknown source dir layout, cannot proceed.")
384
385
lmrf9349c32009-07-23 01:44:24 +0000386# The following are functions used for SSH, SCP and Telnet communication with
387# guests.
lmr6f669ce2009-05-31 19:02:42 +0000388
389def remote_login(command, password, prompt, linesep="\n", timeout=10):
390 """
391 Log into a remote host (guest) using SSH or Telnet. Run the given command
392 using kvm_spawn and provide answers to the questions asked. If timeout
393 expires while waiting for output from the child (e.g. a password prompt
394 or a shell prompt) -- fail.
395
396 @brief: Log into a remote host (guest) using SSH or Telnet.
397
398 @param command: The command to execute (e.g. "ssh root@localhost")
399 @param password: The password to send in reply to a password prompt
400 @param prompt: The shell prompt that indicates a successful login
401 @param linesep: The line separator to send instead of "\\n"
402 (sometimes "\\r\\n" is required)
403 @param timeout: The maximal time duration (in seconds) to wait for each
404 step of the login procedure (i.e. the "Are you sure" prompt, the
405 password prompt, the shell prompt, etc)
406
407 @return Return the kvm_spawn object on success and None on failure.
408 """
lmrdc3a5b12009-07-23 01:40:40 +0000409 sub = kvm_subprocess.kvm_shell_session(command,
410 linesep=linesep,
411 prompt=prompt)
lmr6f669ce2009-05-31 19:02:42 +0000412
413 password_prompt_count = 0
414
lmr8691f422009-07-28 02:52:30 +0000415 logging.debug("Trying to login with command '%s'" % command)
lmr6f669ce2009-05-31 19:02:42 +0000416
417 while True:
418 (match, text) = sub.read_until_last_line_matches(
lmr3ca79fe2009-06-10 19:24:26 +0000419 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"^\s*[Ll]ogin:\s*$",
420 r"[Cc]onnection.*closed", r"[Cc]onnection.*refused", prompt],
lmr6f669ce2009-05-31 19:02:42 +0000421 timeout=timeout, internal_timeout=0.5)
422 if match == 0: # "Are you sure you want to continue connecting"
423 logging.debug("Got 'Are you sure...'; sending 'yes'")
424 sub.sendline("yes")
425 continue
426 elif match == 1: # "password:"
427 if password_prompt_count == 0:
428 logging.debug("Got password prompt; sending '%s'" % password)
429 sub.sendline(password)
430 password_prompt_count += 1
431 continue
432 else:
433 logging.debug("Got password prompt again")
434 sub.close()
435 return None
436 elif match == 2: # "login:"
437 logging.debug("Got unexpected login prompt")
438 sub.close()
439 return None
440 elif match == 3: # "Connection closed"
441 logging.debug("Got 'Connection closed'")
442 sub.close()
443 return None
lmr3ca79fe2009-06-10 19:24:26 +0000444 elif match == 4: # "Connection refused"
lmr0d2ed1f2009-07-01 03:23:18 +0000445 logging.debug("Got 'Connection refused'")
lmr3ca79fe2009-06-10 19:24:26 +0000446 sub.close()
447 return None
448 elif match == 5: # prompt
lmr6f669ce2009-05-31 19:02:42 +0000449 logging.debug("Got shell prompt -- logged in")
450 return sub
451 else: # match == None
lmr3ca79fe2009-06-10 19:24:26 +0000452 logging.debug("Timeout elapsed or process terminated")
lmr6f669ce2009-05-31 19:02:42 +0000453 sub.close()
454 return None
455
456
457def remote_scp(command, password, timeout=300, login_timeout=10):
458 """
459 Run the given command using kvm_spawn and provide answers to the questions
460 asked. If timeout expires while waiting for the transfer to complete ,
461 fail. If login_timeout expires while waiting for output from the child
462 (e.g. a password prompt), fail.
463
464 @brief: Transfer files using SCP, given a command line.
465
466 @param command: The command to execute
467 (e.g. "scp -r foobar root@localhost:/tmp/").
468 @param password: The password to send in reply to a password prompt.
469 @param timeout: The time duration (in seconds) to wait for the transfer
470 to complete.
471 @param login_timeout: The maximal time duration (in seconds) to wait for
472 each step of the login procedure (i.e. the "Are you sure" prompt or the
473 password prompt)
474
475 @return: True if the transfer succeeds and False on failure.
476 """
lmrdc3a5b12009-07-23 01:40:40 +0000477 sub = kvm_subprocess.kvm_expect(command)
lmr6f669ce2009-05-31 19:02:42 +0000478
479 password_prompt_count = 0
480 _timeout = login_timeout
481
482 logging.debug("Trying to login...")
483
484 while True:
485 (match, text) = sub.read_until_last_line_matches(
lmr3ca79fe2009-06-10 19:24:26 +0000486 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"lost connection"],
lmr6f669ce2009-05-31 19:02:42 +0000487 timeout=_timeout, internal_timeout=0.5)
488 if match == 0: # "Are you sure you want to continue connecting"
489 logging.debug("Got 'Are you sure...'; sending 'yes'")
490 sub.sendline("yes")
491 continue
492 elif match == 1: # "password:"
493 if password_prompt_count == 0:
494 logging.debug("Got password prompt; sending '%s'" % password)
495 sub.sendline(password)
496 password_prompt_count += 1
497 _timeout = timeout
498 continue
499 else:
500 logging.debug("Got password prompt again")
501 sub.close()
502 return False
503 elif match == 2: # "lost connection"
504 logging.debug("Got 'lost connection'")
505 sub.close()
506 return False
507 else: # match == None
lmrdc3a5b12009-07-23 01:40:40 +0000508 logging.debug("Timeout elapsed or process terminated")
509 status = sub.get_status()
lmr6f669ce2009-05-31 19:02:42 +0000510 sub.close()
lmrdc3a5b12009-07-23 01:40:40 +0000511 return status == 0
lmr6f669ce2009-05-31 19:02:42 +0000512
513
514def scp_to_remote(host, port, username, password, local_path, remote_path,
515 timeout=300):
516 """
517 Copy files to a remote host (guest).
518
lmr912c74b2009-08-17 20:43:27 +0000519 @param host: Hostname or IP address
520 @param username: Username (if required)
521 @param password: Password (if required)
lmr6f669ce2009-05-31 19:02:42 +0000522 @param local_path: Path on the local machine where we are copying from
523 @param remote_path: Path on the remote machine where we are copying to
524 @param timeout: Time in seconds that we will wait before giving up to
525 copy the files.
526
527 @return: True on success and False on failure.
528 """
lmrd16a67d2009-06-10 19:52:59 +0000529 command = ("scp -o UserKnownHostsFile=/dev/null -r -P %s %s %s@%s:%s" %
530 (port, local_path, username, host, remote_path))
lmr6f669ce2009-05-31 19:02:42 +0000531 return remote_scp(command, password, timeout)
532
533
534def scp_from_remote(host, port, username, password, remote_path, local_path,
535 timeout=300):
536 """
537 Copy files from a remote host (guest).
538
lmr912c74b2009-08-17 20:43:27 +0000539 @param host: Hostname or IP address
540 @param username: Username (if required)
541 @param password: Password (if required)
lmr6f669ce2009-05-31 19:02:42 +0000542 @param local_path: Path on the local machine where we are copying from
543 @param remote_path: Path on the remote machine where we are copying to
544 @param timeout: Time in seconds that we will wait before giving up to copy
545 the files.
546
547 @return: True on success and False on failure.
548 """
lmrd16a67d2009-06-10 19:52:59 +0000549 command = ("scp -o UserKnownHostsFile=/dev/null -r -P %s %s@%s:%s %s" %
550 (port, username, host, remote_path, local_path))
lmr6f669ce2009-05-31 19:02:42 +0000551 return remote_scp(command, password, timeout)
552
553
554def ssh(host, port, username, password, prompt, timeout=10):
555 """
556 Log into a remote host (guest) using SSH.
557
lmr912c74b2009-08-17 20:43:27 +0000558 @param host: Hostname or IP address
559 @param username: Username (if required)
560 @param password: Password (if required)
561 @param prompt: Shell prompt (regular expression)
lmr6f669ce2009-05-31 19:02:42 +0000562 @timeout: Time in seconds that we will wait before giving up on logging
563 into the host.
564
565 @return: kvm_spawn object on success and None on failure.
566 """
lmrd16a67d2009-06-10 19:52:59 +0000567 command = ("ssh -o UserKnownHostsFile=/dev/null -p %s %s@%s" %
568 (port, username, host))
lmr6f669ce2009-05-31 19:02:42 +0000569 return remote_login(command, password, prompt, "\n", timeout)
570
571
572def telnet(host, port, username, password, prompt, timeout=10):
573 """
574 Log into a remote host (guest) using Telnet.
575
lmr912c74b2009-08-17 20:43:27 +0000576 @param host: Hostname or IP address
577 @param username: Username (if required)
578 @param password: Password (if required)
579 @param prompt: Shell prompt (regular expression)
lmr6f669ce2009-05-31 19:02:42 +0000580 @timeout: Time in seconds that we will wait before giving up on logging
581 into the host.
582
583 @return: kvm_spawn object on success and None on failure.
584 """
585 command = "telnet -l %s %s %s" % (username, host, port)
586 return remote_login(command, password, prompt, "\r\n", timeout)
587
588
lmr9f6ebf12009-08-17 20:44:10 +0000589def netcat(host, port, username, password, prompt, timeout=10):
590 """
591 Log into a remote host (guest) using Netcat.
592
593 @param host: Hostname or IP address
594 @param username: Username (if required)
595 @param password: Password (if required)
596 @param prompt: Shell prompt (regular expression)
597 @timeout: Time in seconds that we will wait before giving up on logging
598 into the host.
599
600 @return: kvm_spawn object on success and None on failure.
601 """
602 command = "nc %s %s" % (host, port)
603 return remote_login(command, password, prompt, "\n", timeout)
604
605
lmr6f669ce2009-05-31 19:02:42 +0000606# The following are utility functions related to ports.
607
lmr6f669ce2009-05-31 19:02:42 +0000608def is_port_free(port):
609 """
610 Return True if the given port is available for use.
611
612 @param port: Port number
613 """
614 try:
615 s = socket.socket()
616 #s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
617 s.bind(("localhost", port))
618 free = True
619 except socket.error:
620 free = False
621 s.close()
622 return free
623
624
625def find_free_port(start_port, end_port):
626 """
627 Return a free port in the range [start_port, end_port).
628
629 @param start_port: First port that will be checked.
630 @param end_port: Port immediately after the last one that will be checked.
631 """
632 for i in range(start_port, end_port):
633 if is_port_free(i):
634 return i
635 return None
636
637
638def find_free_ports(start_port, end_port, count):
639 """
640 Return count free ports in the range [start_port, end_port).
641
642 @count: Initial number of ports known to be free in the range.
643 @param start_port: First port that will be checked.
644 @param end_port: Port immediately after the last one that will be checked.
645 """
646 ports = []
647 i = start_port
648 while i < end_port and count > 0:
649 if is_port_free(i):
650 ports.append(i)
651 count -= 1
652 i += 1
653 return ports
654
655
656# The following are miscellaneous utility functions.
657
lmrb4954e02009-08-17 20:44:42 +0000658def get_path(base_path, user_path):
659 """
660 Translate a user specified path to a real path.
661 If user_path is relative, append it to base_path.
662 If user_path is absolute, return it as is.
663
664 @param base_path: The base path of relative user specified paths.
665 @param user_path: The user specified path.
666 """
667 if os.path.isabs(user_path):
668 return user_path
669 else:
670 return os.path.join(base_path, user_path)
671
672
lmr6f669ce2009-05-31 19:02:42 +0000673def generate_random_string(length):
674 """
675 Return a random string using alphanumeric characters.
676
677 @length: length of the string that will be generated.
678 """
lmr45fc0c22009-09-15 05:16:45 +0000679 r = random.SystemRandom()
lmr6f669ce2009-05-31 19:02:42 +0000680 str = ""
681 chars = string.letters + string.digits
682 while length > 0:
lmr45fc0c22009-09-15 05:16:45 +0000683 str += r.choice(chars)
lmr6f669ce2009-05-31 19:02:42 +0000684 length -= 1
685 return str
686
687
688def format_str_for_message(str):
689 """
690 Format str so that it can be appended to a message.
691 If str consists of one line, prefix it with a space.
692 If str consists of multiple lines, prefix it with a newline.
693
694 @param str: string that will be formatted.
695 """
lmr57355592009-08-07 21:55:49 +0000696 lines = str.splitlines()
697 num_lines = len(lines)
698 str = "\n".join(lines)
lmr6f669ce2009-05-31 19:02:42 +0000699 if num_lines == 0:
700 return ""
701 elif num_lines == 1:
702 return " " + str
703 else:
704 return "\n" + str
705
706
707def wait_for(func, timeout, first=0.0, step=1.0, text=None):
708 """
709 If func() evaluates to True before timeout expires, return the
710 value of func(). Otherwise return None.
711
712 @brief: Wait until func() evaluates to True.
713
714 @param timeout: Timeout in seconds
715 @param first: Time to sleep before first attempt
716 @param steps: Time to sleep between attempts in seconds
717 @param text: Text to print while waiting, for debug purposes
718 """
719 start_time = time.time()
720 end_time = time.time() + timeout
721
722 time.sleep(first)
723
724 while time.time() < end_time:
725 if text:
726 logging.debug("%s (%f secs)" % (text, time.time() - start_time))
727
728 output = func()
729 if output:
730 return output
731
732 time.sleep(step)
733
734 logging.debug("Timeout elapsed")
735 return None
736
737
738def md5sum_file(filename, size=None):
739 """
740 Calculate the md5sum of filename.
741 If size is not None, limit to first size bytes.
742 Throw exception if something is wrong with filename.
743 Can be also implemented with bash one-liner (assuming size%1024==0):
744 dd if=filename bs=1024 count=size/1024 | md5sum -
745
746 @param filename: Path of the file that will have its md5sum calculated.
747 @param returns: md5sum of the file.
748 """
749 chunksize = 4096
750 fsize = os.path.getsize(filename)
751 if not size or size>fsize:
752 size = fsize
753 f = open(filename, 'rb')
754 o = md5.new()
755 while size > 0:
756 if chunksize > size:
757 chunksize = size
758 data = f.read(chunksize)
759 if len(data) == 0:
760 logging.debug("Nothing left to read but size=%d" % size)
761 break
762 o.update(data)
763 size -= len(data)
764 f.close()
765 return o.hexdigest()