blob: ceae4a4bdc976b24fa3255ae535cafd8c0244ec9 [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
lmr117bbcf2009-09-15 07:12:39 +0000285def get_latest_kvm_release_tag(release_listing):
lmr3f0b0cc2009-06-10 02:25:23 +0000286 """
287 Fetches the latest release tag for KVM.
288
lmr117bbcf2009-09-15 07:12:39 +0000289 @param release_listing: URL that contains a list of the Source Forge
290 KVM project files.
lmr3f0b0cc2009-06-10 02:25:23 +0000291 """
292 try:
lmr117bbcf2009-09-15 07:12:39 +0000293 release_page = utils.urlopen(release_listing)
294 data = release_page.read()
295 release_page.close()
lmr8ea274b2009-07-06 13:42:35 +0000296 rx = re.compile("kvm-(\d+).tar.gz", re.IGNORECASE)
lmr3f0b0cc2009-06-10 02:25:23 +0000297 matches = rx.findall(data)
lmr32525382009-08-10 13:53:37 +0000298 # In all regexp matches to something that looks like a release tag,
299 # get the largest integer. That will be our latest release tag.
300 latest_tag = max(int(x) for x in matches)
301 return str(latest_tag)
lmr3f0b0cc2009-06-10 02:25:23 +0000302 except Exception, e:
303 message = "Could not fetch latest KVM release tag: %s" % str(e)
304 logging.error(message)
305 raise error.TestError(message)
306
307
308def get_git_branch(repository, branch, srcdir, commit=None, lbranch=None):
309 """
310 Retrieves a given git code repository.
311
312 @param repository: Git repository URL
313 """
314 logging.info("Fetching git [REP '%s' BRANCH '%s' TAG '%s'] -> %s",
315 repository, branch, commit, srcdir)
316 if not os.path.exists(srcdir):
317 os.makedirs(srcdir)
318 os.chdir(srcdir)
319
320 if os.path.exists(".git"):
321 utils.system("git reset --hard")
322 else:
323 utils.system("git init")
324
325 if not lbranch:
326 lbranch = branch
327
328 utils.system("git fetch -q -f -u -t %s %s:%s" %
329 (repository, branch, lbranch))
330 utils.system("git checkout %s" % lbranch)
331 if commit:
332 utils.system("git checkout %s" % commit)
333
334 h = utils.system_output('git log --pretty=format:"%H" -1')
335 desc = utils.system_output("git describe")
336 logging.info("Commit hash for %s is %s (%s)" % (repository, h.strip(),
337 desc))
338 return srcdir
339
340
341def unload_module(module_name):
342 """
343 Removes a module. Handles dependencies. If even then it's not possible
344 to remove one of the modules, it will trhow an error.CmdError exception.
345
346 @param module_name: Name of the module we want to remove.
347 """
348 l_raw = utils.system_output("/sbin/lsmod").splitlines()
349 lsmod = [x for x in l_raw if x.split()[0] == module_name]
350 if len(lsmod) > 0:
351 line_parts = lsmod[0].split()
352 if len(line_parts) == 4:
353 submodules = line_parts[3].split(",")
354 for submodule in submodules:
355 unload_module(submodule)
356 utils.system("/sbin/modprobe -r %s" % module_name)
357 logging.info("Module %s unloaded" % module_name)
358 else:
359 logging.info("Module %s is already unloaded" % module_name)
360
361
362def check_kvm_source_dir(source_dir):
363 """
364 Inspects the kvm source directory and verifies its disposition. In some
365 occasions build may be dependant on the source directory disposition.
366 The reason why the return codes are numbers is that we might have more
367 changes on the source directory layout, so it's not scalable to just use
368 strings like 'old_repo', 'new_repo' and such.
369
370 @param source_dir: Source code path that will be inspected.
371 """
372 os.chdir(source_dir)
373 has_qemu_dir = os.path.isdir('qemu')
374 has_kvm_dir = os.path.isdir('kvm')
375 if has_qemu_dir and not has_kvm_dir:
376 logging.debug("qemu directory detected, source dir layout 1")
377 return 1
378 if has_kvm_dir and not has_qemu_dir:
379 logging.debug("kvm directory detected, source dir layout 2")
380 return 2
381 else:
382 raise error.TestError("Unknown source dir layout, cannot proceed.")
383
384
lmrf9349c32009-07-23 01:44:24 +0000385# The following are functions used for SSH, SCP and Telnet communication with
386# guests.
lmr6f669ce2009-05-31 19:02:42 +0000387
388def remote_login(command, password, prompt, linesep="\n", timeout=10):
389 """
390 Log into a remote host (guest) using SSH or Telnet. Run the given command
391 using kvm_spawn and provide answers to the questions asked. If timeout
392 expires while waiting for output from the child (e.g. a password prompt
393 or a shell prompt) -- fail.
394
395 @brief: Log into a remote host (guest) using SSH or Telnet.
396
397 @param command: The command to execute (e.g. "ssh root@localhost")
398 @param password: The password to send in reply to a password prompt
399 @param prompt: The shell prompt that indicates a successful login
400 @param linesep: The line separator to send instead of "\\n"
401 (sometimes "\\r\\n" is required)
402 @param timeout: The maximal time duration (in seconds) to wait for each
403 step of the login procedure (i.e. the "Are you sure" prompt, the
404 password prompt, the shell prompt, etc)
405
406 @return Return the kvm_spawn object on success and None on failure.
407 """
lmrdc3a5b12009-07-23 01:40:40 +0000408 sub = kvm_subprocess.kvm_shell_session(command,
409 linesep=linesep,
410 prompt=prompt)
lmr6f669ce2009-05-31 19:02:42 +0000411
412 password_prompt_count = 0
413
lmr8691f422009-07-28 02:52:30 +0000414 logging.debug("Trying to login with command '%s'" % command)
lmr6f669ce2009-05-31 19:02:42 +0000415
416 while True:
417 (match, text) = sub.read_until_last_line_matches(
lmr3ca79fe2009-06-10 19:24:26 +0000418 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"^\s*[Ll]ogin:\s*$",
419 r"[Cc]onnection.*closed", r"[Cc]onnection.*refused", prompt],
lmr6f669ce2009-05-31 19:02:42 +0000420 timeout=timeout, internal_timeout=0.5)
421 if match == 0: # "Are you sure you want to continue connecting"
422 logging.debug("Got 'Are you sure...'; sending 'yes'")
423 sub.sendline("yes")
424 continue
425 elif match == 1: # "password:"
426 if password_prompt_count == 0:
427 logging.debug("Got password prompt; sending '%s'" % password)
428 sub.sendline(password)
429 password_prompt_count += 1
430 continue
431 else:
432 logging.debug("Got password prompt again")
433 sub.close()
434 return None
435 elif match == 2: # "login:"
436 logging.debug("Got unexpected login prompt")
437 sub.close()
438 return None
439 elif match == 3: # "Connection closed"
440 logging.debug("Got 'Connection closed'")
441 sub.close()
442 return None
lmr3ca79fe2009-06-10 19:24:26 +0000443 elif match == 4: # "Connection refused"
lmr0d2ed1f2009-07-01 03:23:18 +0000444 logging.debug("Got 'Connection refused'")
lmr3ca79fe2009-06-10 19:24:26 +0000445 sub.close()
446 return None
447 elif match == 5: # prompt
lmr6f669ce2009-05-31 19:02:42 +0000448 logging.debug("Got shell prompt -- logged in")
449 return sub
450 else: # match == None
lmr3ca79fe2009-06-10 19:24:26 +0000451 logging.debug("Timeout elapsed or process terminated")
lmr6f669ce2009-05-31 19:02:42 +0000452 sub.close()
453 return None
454
455
456def remote_scp(command, password, timeout=300, login_timeout=10):
457 """
458 Run the given command using kvm_spawn and provide answers to the questions
459 asked. If timeout expires while waiting for the transfer to complete ,
460 fail. If login_timeout expires while waiting for output from the child
461 (e.g. a password prompt), fail.
462
463 @brief: Transfer files using SCP, given a command line.
464
465 @param command: The command to execute
466 (e.g. "scp -r foobar root@localhost:/tmp/").
467 @param password: The password to send in reply to a password prompt.
468 @param timeout: The time duration (in seconds) to wait for the transfer
469 to complete.
470 @param login_timeout: The maximal time duration (in seconds) to wait for
471 each step of the login procedure (i.e. the "Are you sure" prompt or the
472 password prompt)
473
474 @return: True if the transfer succeeds and False on failure.
475 """
lmrdc3a5b12009-07-23 01:40:40 +0000476 sub = kvm_subprocess.kvm_expect(command)
lmr6f669ce2009-05-31 19:02:42 +0000477
478 password_prompt_count = 0
479 _timeout = login_timeout
480
481 logging.debug("Trying to login...")
482
483 while True:
484 (match, text) = sub.read_until_last_line_matches(
lmr3ca79fe2009-06-10 19:24:26 +0000485 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"lost connection"],
lmr6f669ce2009-05-31 19:02:42 +0000486 timeout=_timeout, internal_timeout=0.5)
487 if match == 0: # "Are you sure you want to continue connecting"
488 logging.debug("Got 'Are you sure...'; sending 'yes'")
489 sub.sendline("yes")
490 continue
491 elif match == 1: # "password:"
492 if password_prompt_count == 0:
493 logging.debug("Got password prompt; sending '%s'" % password)
494 sub.sendline(password)
495 password_prompt_count += 1
496 _timeout = timeout
497 continue
498 else:
499 logging.debug("Got password prompt again")
500 sub.close()
501 return False
502 elif match == 2: # "lost connection"
503 logging.debug("Got 'lost connection'")
504 sub.close()
505 return False
506 else: # match == None
lmrdc3a5b12009-07-23 01:40:40 +0000507 logging.debug("Timeout elapsed or process terminated")
508 status = sub.get_status()
lmr6f669ce2009-05-31 19:02:42 +0000509 sub.close()
lmrdc3a5b12009-07-23 01:40:40 +0000510 return status == 0
lmr6f669ce2009-05-31 19:02:42 +0000511
512
513def scp_to_remote(host, port, username, password, local_path, remote_path,
514 timeout=300):
515 """
516 Copy files to a remote host (guest).
517
lmr912c74b2009-08-17 20:43:27 +0000518 @param host: Hostname or IP address
519 @param username: Username (if required)
520 @param password: Password (if required)
lmr6f669ce2009-05-31 19:02:42 +0000521 @param local_path: Path on the local machine where we are copying from
522 @param remote_path: Path on the remote machine where we are copying to
523 @param timeout: Time in seconds that we will wait before giving up to
524 copy the files.
525
526 @return: True on success and False on failure.
527 """
lmrd16a67d2009-06-10 19:52:59 +0000528 command = ("scp -o UserKnownHostsFile=/dev/null -r -P %s %s %s@%s:%s" %
529 (port, local_path, username, host, remote_path))
lmr6f669ce2009-05-31 19:02:42 +0000530 return remote_scp(command, password, timeout)
531
532
533def scp_from_remote(host, port, username, password, remote_path, local_path,
534 timeout=300):
535 """
536 Copy files from a remote host (guest).
537
lmr912c74b2009-08-17 20:43:27 +0000538 @param host: Hostname or IP address
539 @param username: Username (if required)
540 @param password: Password (if required)
lmr6f669ce2009-05-31 19:02:42 +0000541 @param local_path: Path on the local machine where we are copying from
542 @param remote_path: Path on the remote machine where we are copying to
543 @param timeout: Time in seconds that we will wait before giving up to copy
544 the files.
545
546 @return: True on success and False on failure.
547 """
lmrd16a67d2009-06-10 19:52:59 +0000548 command = ("scp -o UserKnownHostsFile=/dev/null -r -P %s %s@%s:%s %s" %
549 (port, username, host, remote_path, local_path))
lmr6f669ce2009-05-31 19:02:42 +0000550 return remote_scp(command, password, timeout)
551
552
553def ssh(host, port, username, password, prompt, timeout=10):
554 """
555 Log into a remote host (guest) using SSH.
556
lmr912c74b2009-08-17 20:43:27 +0000557 @param host: Hostname or IP address
558 @param username: Username (if required)
559 @param password: Password (if required)
560 @param prompt: Shell prompt (regular expression)
lmr6f669ce2009-05-31 19:02:42 +0000561 @timeout: Time in seconds that we will wait before giving up on logging
562 into the host.
563
564 @return: kvm_spawn object on success and None on failure.
565 """
lmrd16a67d2009-06-10 19:52:59 +0000566 command = ("ssh -o UserKnownHostsFile=/dev/null -p %s %s@%s" %
567 (port, username, host))
lmr6f669ce2009-05-31 19:02:42 +0000568 return remote_login(command, password, prompt, "\n", timeout)
569
570
571def telnet(host, port, username, password, prompt, timeout=10):
572 """
573 Log into a remote host (guest) using Telnet.
574
lmr912c74b2009-08-17 20:43:27 +0000575 @param host: Hostname or IP address
576 @param username: Username (if required)
577 @param password: Password (if required)
578 @param prompt: Shell prompt (regular expression)
lmr6f669ce2009-05-31 19:02:42 +0000579 @timeout: Time in seconds that we will wait before giving up on logging
580 into the host.
581
582 @return: kvm_spawn object on success and None on failure.
583 """
584 command = "telnet -l %s %s %s" % (username, host, port)
585 return remote_login(command, password, prompt, "\r\n", timeout)
586
587
lmr9f6ebf12009-08-17 20:44:10 +0000588def netcat(host, port, username, password, prompt, timeout=10):
589 """
590 Log into a remote host (guest) using Netcat.
591
592 @param host: Hostname or IP address
593 @param username: Username (if required)
594 @param password: Password (if required)
595 @param prompt: Shell prompt (regular expression)
596 @timeout: Time in seconds that we will wait before giving up on logging
597 into the host.
598
599 @return: kvm_spawn object on success and None on failure.
600 """
601 command = "nc %s %s" % (host, port)
602 return remote_login(command, password, prompt, "\n", timeout)
603
604
lmr6f669ce2009-05-31 19:02:42 +0000605# The following are utility functions related to ports.
606
lmr6f669ce2009-05-31 19:02:42 +0000607def is_port_free(port):
608 """
609 Return True if the given port is available for use.
610
611 @param port: Port number
612 """
613 try:
614 s = socket.socket()
615 #s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
616 s.bind(("localhost", port))
617 free = True
618 except socket.error:
619 free = False
620 s.close()
621 return free
622
623
624def find_free_port(start_port, end_port):
625 """
626 Return a free port in the range [start_port, end_port).
627
628 @param start_port: First port that will be checked.
629 @param end_port: Port immediately after the last one that will be checked.
630 """
631 for i in range(start_port, end_port):
632 if is_port_free(i):
633 return i
634 return None
635
636
637def find_free_ports(start_port, end_port, count):
638 """
639 Return count free ports in the range [start_port, end_port).
640
641 @count: Initial number of ports known to be free in the range.
642 @param start_port: First port that will be checked.
643 @param end_port: Port immediately after the last one that will be checked.
644 """
645 ports = []
646 i = start_port
647 while i < end_port and count > 0:
648 if is_port_free(i):
649 ports.append(i)
650 count -= 1
651 i += 1
652 return ports
653
654
655# The following are miscellaneous utility functions.
656
lmrb4954e02009-08-17 20:44:42 +0000657def get_path(base_path, user_path):
658 """
659 Translate a user specified path to a real path.
660 If user_path is relative, append it to base_path.
661 If user_path is absolute, return it as is.
662
663 @param base_path: The base path of relative user specified paths.
664 @param user_path: The user specified path.
665 """
666 if os.path.isabs(user_path):
667 return user_path
668 else:
669 return os.path.join(base_path, user_path)
670
671
lmr6f669ce2009-05-31 19:02:42 +0000672def generate_random_string(length):
673 """
674 Return a random string using alphanumeric characters.
675
676 @length: length of the string that will be generated.
677 """
lmr45fc0c22009-09-15 05:16:45 +0000678 r = random.SystemRandom()
lmr6f669ce2009-05-31 19:02:42 +0000679 str = ""
680 chars = string.letters + string.digits
681 while length > 0:
lmr45fc0c22009-09-15 05:16:45 +0000682 str += r.choice(chars)
lmr6f669ce2009-05-31 19:02:42 +0000683 length -= 1
684 return str
685
686
687def format_str_for_message(str):
688 """
689 Format str so that it can be appended to a message.
690 If str consists of one line, prefix it with a space.
691 If str consists of multiple lines, prefix it with a newline.
692
693 @param str: string that will be formatted.
694 """
lmr57355592009-08-07 21:55:49 +0000695 lines = str.splitlines()
696 num_lines = len(lines)
697 str = "\n".join(lines)
lmr6f669ce2009-05-31 19:02:42 +0000698 if num_lines == 0:
699 return ""
700 elif num_lines == 1:
701 return " " + str
702 else:
703 return "\n" + str
704
705
706def wait_for(func, timeout, first=0.0, step=1.0, text=None):
707 """
708 If func() evaluates to True before timeout expires, return the
709 value of func(). Otherwise return None.
710
711 @brief: Wait until func() evaluates to True.
712
713 @param timeout: Timeout in seconds
714 @param first: Time to sleep before first attempt
715 @param steps: Time to sleep between attempts in seconds
716 @param text: Text to print while waiting, for debug purposes
717 """
718 start_time = time.time()
719 end_time = time.time() + timeout
720
721 time.sleep(first)
722
723 while time.time() < end_time:
724 if text:
725 logging.debug("%s (%f secs)" % (text, time.time() - start_time))
726
727 output = func()
728 if output:
729 return output
730
731 time.sleep(step)
732
733 logging.debug("Timeout elapsed")
734 return None
735
736
737def md5sum_file(filename, size=None):
738 """
739 Calculate the md5sum of filename.
740 If size is not None, limit to first size bytes.
741 Throw exception if something is wrong with filename.
742 Can be also implemented with bash one-liner (assuming size%1024==0):
743 dd if=filename bs=1024 count=size/1024 | md5sum -
744
745 @param filename: Path of the file that will have its md5sum calculated.
746 @param returns: md5sum of the file.
747 """
748 chunksize = 4096
749 fsize = os.path.getsize(filename)
750 if not size or size>fsize:
751 size = fsize
752 f = open(filename, 'rb')
753 o = md5.new()
754 while size > 0:
755 if chunksize > size:
756 chunksize = size
757 data = f.read(chunksize)
758 if len(data) == 0:
759 logging.debug("Nothing left to read but size=%d" % size)
760 break
761 o.update(data)
762 size -= len(data)
763 f.close()
764 return o.hexdigest()