blob: 53b664a902a13952422012c045f5a5b55dc6abbf [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
lmr3cda3552009-09-15 19:52:43 +00008import select, re, logging, commands, cPickle
lmrb635b862009-09-10 14:53:21 +00009from 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
lmre8a66dd2009-09-15 19:51:07 +000014def dump_env(obj, filename):
15 """
16 Dump KVM test environment to a file.
17
18 @param filename: Path to a file where the environment will be dumped to.
19 """
20 file = open(filename, "w")
21 cPickle.dump(obj, file)
22 file.close()
23
24
25def load_env(filename, default=None):
26 """
27 Load KVM test environment from an environment file.
28
29 @param filename: Path to a file where the environment was dumped to.
30 """
31 try:
32 file = open(filename, "r")
33 except:
34 return default
35 obj = cPickle.load(file)
36 file.close()
37 return obj
38
39
lmr6f669ce2009-05-31 19:02:42 +000040def get_sub_dict(dict, name):
41 """
42 Return a "sub-dict" corresponding to a specific object.
43
44 Operate on a copy of dict: for each key that ends with the suffix
45 "_" + name, strip the suffix from the key, and set the value of
46 the stripped key to that of the key. Return the resulting dict.
47
48 @param name: Suffix of the key we want to set the value.
49 """
50 suffix = "_" + name
51 new_dict = dict.copy()
52 for key in dict.keys():
53 if key.endswith(suffix):
54 new_key = key.split(suffix)[0]
55 new_dict[new_key] = dict[key]
56 return new_dict
57
58
59def get_sub_dict_names(dict, keyword):
60 """
61 Return a list of "sub-dict" names that may be extracted with get_sub_dict.
62
63 This function may be modified to change the behavior of all functions that
64 deal with multiple objects defined in dicts (e.g. VMs, images, NICs).
65
66 @param keyword: A key in dict (e.g. "vms", "images", "nics").
67 """
68 names = dict.get(keyword)
69 if names:
70 return names.split()
71 else:
72 return []
73
74
lmrac5089b2009-08-13 04:05:47 +000075# Functions related to MAC/IP addresses
76
77def mac_str_to_int(addr):
78 """
79 Convert MAC address string to integer.
80
81 @param addr: String representing the MAC address.
82 """
83 return sum(int(s, 16) * 256 ** i
84 for i, s in enumerate(reversed(addr.split(":"))))
85
86
87def mac_int_to_str(addr):
88 """
89 Convert MAC address integer to string.
90
91 @param addr: Integer representing the MAC address.
92 """
93 return ":".join("%02x" % (addr >> 8 * i & 0xFF)
94 for i in reversed(range(6)))
95
96
97def ip_str_to_int(addr):
98 """
99 Convert IP address string to integer.
100
101 @param addr: String representing the IP address.
102 """
103 return sum(int(s) * 256 ** i
104 for i, s in enumerate(reversed(addr.split("."))))
105
106
107def ip_int_to_str(addr):
108 """
109 Convert IP address integer to string.
110
111 @param addr: Integer representing the IP address.
112 """
113 return ".".join(str(addr >> 8 * i & 0xFF)
114 for i in reversed(range(4)))
115
116
117def offset_mac(base, offset):
118 """
119 Add offset to a given MAC address.
120
121 @param base: String representing a MAC address.
122 @param offset: Offset to add to base (integer)
123 @return: A string representing the offset MAC address.
124 """
125 return mac_int_to_str(mac_str_to_int(base) + offset)
126
127
128def offset_ip(base, offset):
129 """
130 Add offset to a given IP address.
131
132 @param base: String representing an IP address.
133 @param offset: Offset to add to base (integer)
134 @return: A string representing the offset IP address.
135 """
136 return ip_int_to_str(ip_str_to_int(base) + offset)
137
138
139def get_mac_ip_pair_from_dict(dict):
140 """
141 Fetch a MAC-IP address pair from dict and return it.
142
143 The parameters in dict are expected to conform to a certain syntax.
144 Typical usage may be:
145
146 address_ranges = r1 r2 r3
147
148 address_range_base_mac_r1 = 55:44:33:22:11:00
149 address_range_base_ip_r1 = 10.0.0.0
150 address_range_size_r1 = 16
151
152 address_range_base_mac_r2 = 55:44:33:22:11:40
153 address_range_base_ip_r2 = 10.0.0.60
154 address_range_size_r2 = 25
155
156 address_range_base_mac_r3 = 55:44:33:22:12:10
157 address_range_base_ip_r3 = 10.0.1.20
158 address_range_size_r3 = 230
159
160 address_index = 0
161
162 All parameters except address_index specify a MAC-IP address pool. The
163 pool consists of several MAC-IP address ranges.
164 address_index specified the index of the desired MAC-IP pair from the pool.
165
166 @param dict: The dictionary from which to fetch the addresses.
167 """
168 index = int(dict.get("address_index", 0))
169 for mac_range_name in get_sub_dict_names(dict, "address_ranges"):
170 mac_range_params = get_sub_dict(dict, mac_range_name)
171 mac_base = mac_range_params.get("address_range_base_mac")
172 ip_base = mac_range_params.get("address_range_base_ip")
173 size = int(mac_range_params.get("address_range_size", 1))
174 if index < size:
175 return (mac_base and offset_mac(mac_base, index),
176 ip_base and offset_ip(ip_base, index))
177 index -= size
178 return (None, None)
179
180
lmr53d3e932009-09-09 21:56:18 +0000181def verify_ip_address_ownership(ip, macs, timeout=10.0):
lmree90dd92009-08-13 04:13:39 +0000182 """
lmr53d3e932009-09-09 21:56:18 +0000183 Use arping and the ARP cache to make sure a given IP address belongs to one
184 of the given MAC addresses.
lmree90dd92009-08-13 04:13:39 +0000185
186 @param ip: An IP address.
187 @param macs: A list or tuple of MAC addresses.
188 @return: True iff ip is assigned to a MAC address in macs.
189 """
lmr53d3e932009-09-09 21:56:18 +0000190 # Compile a regex that matches the given IP address and any of the given
191 # MAC addresses
lmree90dd92009-08-13 04:13:39 +0000192 mac_regex = "|".join("(%s)" % mac for mac in macs)
193 regex = re.compile(r"\b%s\b.*\b(%s)\b" % (ip, mac_regex))
194
lmr53d3e932009-09-09 21:56:18 +0000195 # Check the ARP cache
196 o = commands.getoutput("/sbin/arp -n")
197 if re.search(regex, o, re.IGNORECASE):
lmree90dd92009-08-13 04:13:39 +0000198 return True
199
lmr53d3e932009-09-09 21:56:18 +0000200 # Get the name of the bridge device for arping
201 o = commands.getoutput("/sbin/ip route get %s" % ip)
202 dev = re.findall("dev\s+\S+", o, re.IGNORECASE)
203 if not dev:
204 return False
205 dev = dev[0].split()[-1]
206
207 # Send an ARP request
208 o = commands.getoutput("/sbin/arping -f -c 3 -I %s %s" % (dev, ip))
209 return bool(re.search(regex, o, re.IGNORECASE))
lmree90dd92009-08-13 04:13:39 +0000210
211
lmr6f669ce2009-05-31 19:02:42 +0000212# Functions for working with the environment (a dict-like object)
213
214def is_vm(obj):
215 """
216 Tests whether a given object is a VM object.
217
218 @param obj: Python object (pretty much everything on python).
219 """
220 return obj.__class__.__name__ == "VM"
221
222
223def env_get_all_vms(env):
224 """
225 Return a list of all VM objects on a given environment.
226
227 @param env: Dictionary with environment items.
228 """
229 vms = []
230 for obj in env.values():
231 if is_vm(obj):
232 vms.append(obj)
233 return vms
234
235
236def env_get_vm(env, name):
237 """
238 Return a VM object by its name.
239
240 @param name: VM name.
241 """
242 return env.get("vm__%s" % name)
243
244
245def env_register_vm(env, name, vm):
246 """
247 Register a given VM in a given env.
248
249 @param env: Environment where we will register the VM.
250 @param name: VM name.
251 @param vm: VM object.
252 """
253 env["vm__%s" % name] = vm
254
255
256def env_unregister_vm(env, name):
257 """
258 Remove a given VM from a given env.
259
260 @param env: Environment where we will un-register the VM.
261 @param name: VM name.
262 """
263 del env["vm__%s" % name]
264
265
266# Utility functions for dealing with external processes
267
268def pid_exists(pid):
269 """
270 Return True if a given PID exists.
271
272 @param pid: Process ID number.
273 """
274 try:
275 os.kill(pid, 0)
276 return True
277 except:
278 return False
279
280
281def safe_kill(pid, signal):
282 """
283 Attempt to send a signal to a given process that may or may not exist.
284
285 @param signal: Signal number.
286 """
287 try:
288 os.kill(pid, signal)
289 return True
290 except:
291 return False
292
293
lmr1aeaefb2009-09-09 22:12:49 +0000294def kill_process_tree(pid, sig=signal.SIGKILL):
295 """Signal a process and all of its children.
296
297 If the process does not exist -- return.
298
299 @param pid: The pid of the process to signal.
300 @param sig: The signal to send to the processes.
301 """
302 if not safe_kill(pid, signal.SIGSTOP):
303 return
304 children = commands.getoutput("ps --ppid=%d -o pid=" % pid).split()
305 for child in children:
306 kill_process_tree(int(child), sig)
307 safe_kill(pid, sig)
308 safe_kill(pid, signal.SIGCONT)
309
310
lmr117bbcf2009-09-15 07:12:39 +0000311def get_latest_kvm_release_tag(release_listing):
lmr3f0b0cc2009-06-10 02:25:23 +0000312 """
313 Fetches the latest release tag for KVM.
314
lmr117bbcf2009-09-15 07:12:39 +0000315 @param release_listing: URL that contains a list of the Source Forge
316 KVM project files.
lmr3f0b0cc2009-06-10 02:25:23 +0000317 """
318 try:
lmr117bbcf2009-09-15 07:12:39 +0000319 release_page = utils.urlopen(release_listing)
320 data = release_page.read()
321 release_page.close()
lmr8ea274b2009-07-06 13:42:35 +0000322 rx = re.compile("kvm-(\d+).tar.gz", re.IGNORECASE)
lmr3f0b0cc2009-06-10 02:25:23 +0000323 matches = rx.findall(data)
lmr32525382009-08-10 13:53:37 +0000324 # In all regexp matches to something that looks like a release tag,
325 # get the largest integer. That will be our latest release tag.
326 latest_tag = max(int(x) for x in matches)
327 return str(latest_tag)
lmr3f0b0cc2009-06-10 02:25:23 +0000328 except Exception, e:
329 message = "Could not fetch latest KVM release tag: %s" % str(e)
330 logging.error(message)
331 raise error.TestError(message)
332
333
334def get_git_branch(repository, branch, srcdir, commit=None, lbranch=None):
335 """
336 Retrieves a given git code repository.
337
338 @param repository: Git repository URL
339 """
340 logging.info("Fetching git [REP '%s' BRANCH '%s' TAG '%s'] -> %s",
341 repository, branch, commit, srcdir)
342 if not os.path.exists(srcdir):
343 os.makedirs(srcdir)
344 os.chdir(srcdir)
345
346 if os.path.exists(".git"):
347 utils.system("git reset --hard")
348 else:
349 utils.system("git init")
350
351 if not lbranch:
352 lbranch = branch
353
354 utils.system("git fetch -q -f -u -t %s %s:%s" %
355 (repository, branch, lbranch))
356 utils.system("git checkout %s" % lbranch)
357 if commit:
358 utils.system("git checkout %s" % commit)
359
360 h = utils.system_output('git log --pretty=format:"%H" -1')
361 desc = utils.system_output("git describe")
362 logging.info("Commit hash for %s is %s (%s)" % (repository, h.strip(),
363 desc))
364 return srcdir
365
366
367def unload_module(module_name):
368 """
369 Removes a module. Handles dependencies. If even then it's not possible
370 to remove one of the modules, it will trhow an error.CmdError exception.
371
372 @param module_name: Name of the module we want to remove.
373 """
374 l_raw = utils.system_output("/sbin/lsmod").splitlines()
375 lsmod = [x for x in l_raw if x.split()[0] == module_name]
376 if len(lsmod) > 0:
377 line_parts = lsmod[0].split()
378 if len(line_parts) == 4:
379 submodules = line_parts[3].split(",")
380 for submodule in submodules:
381 unload_module(submodule)
382 utils.system("/sbin/modprobe -r %s" % module_name)
383 logging.info("Module %s unloaded" % module_name)
384 else:
385 logging.info("Module %s is already unloaded" % module_name)
386
387
388def check_kvm_source_dir(source_dir):
389 """
390 Inspects the kvm source directory and verifies its disposition. In some
391 occasions build may be dependant on the source directory disposition.
392 The reason why the return codes are numbers is that we might have more
393 changes on the source directory layout, so it's not scalable to just use
394 strings like 'old_repo', 'new_repo' and such.
395
396 @param source_dir: Source code path that will be inspected.
397 """
398 os.chdir(source_dir)
399 has_qemu_dir = os.path.isdir('qemu')
400 has_kvm_dir = os.path.isdir('kvm')
401 if has_qemu_dir and not has_kvm_dir:
402 logging.debug("qemu directory detected, source dir layout 1")
403 return 1
404 if has_kvm_dir and not has_qemu_dir:
405 logging.debug("kvm directory detected, source dir layout 2")
406 return 2
407 else:
408 raise error.TestError("Unknown source dir layout, cannot proceed.")
409
410
lmrf9349c32009-07-23 01:44:24 +0000411# The following are functions used for SSH, SCP and Telnet communication with
412# guests.
lmr6f669ce2009-05-31 19:02:42 +0000413
414def remote_login(command, password, prompt, linesep="\n", timeout=10):
415 """
416 Log into a remote host (guest) using SSH or Telnet. Run the given command
417 using kvm_spawn and provide answers to the questions asked. If timeout
418 expires while waiting for output from the child (e.g. a password prompt
419 or a shell prompt) -- fail.
420
421 @brief: Log into a remote host (guest) using SSH or Telnet.
422
423 @param command: The command to execute (e.g. "ssh root@localhost")
424 @param password: The password to send in reply to a password prompt
425 @param prompt: The shell prompt that indicates a successful login
426 @param linesep: The line separator to send instead of "\\n"
427 (sometimes "\\r\\n" is required)
428 @param timeout: The maximal time duration (in seconds) to wait for each
429 step of the login procedure (i.e. the "Are you sure" prompt, the
430 password prompt, the shell prompt, etc)
431
432 @return Return the kvm_spawn object on success and None on failure.
433 """
lmrdc3a5b12009-07-23 01:40:40 +0000434 sub = kvm_subprocess.kvm_shell_session(command,
435 linesep=linesep,
436 prompt=prompt)
lmr6f669ce2009-05-31 19:02:42 +0000437
438 password_prompt_count = 0
439
lmr8691f422009-07-28 02:52:30 +0000440 logging.debug("Trying to login with command '%s'" % command)
lmr6f669ce2009-05-31 19:02:42 +0000441
442 while True:
443 (match, text) = sub.read_until_last_line_matches(
lmr3ca79fe2009-06-10 19:24:26 +0000444 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"^\s*[Ll]ogin:\s*$",
445 r"[Cc]onnection.*closed", r"[Cc]onnection.*refused", prompt],
lmr6f669ce2009-05-31 19:02:42 +0000446 timeout=timeout, internal_timeout=0.5)
447 if match == 0: # "Are you sure you want to continue connecting"
448 logging.debug("Got 'Are you sure...'; sending 'yes'")
449 sub.sendline("yes")
450 continue
451 elif match == 1: # "password:"
452 if password_prompt_count == 0:
453 logging.debug("Got password prompt; sending '%s'" % password)
454 sub.sendline(password)
455 password_prompt_count += 1
456 continue
457 else:
458 logging.debug("Got password prompt again")
459 sub.close()
460 return None
461 elif match == 2: # "login:"
462 logging.debug("Got unexpected login prompt")
463 sub.close()
464 return None
465 elif match == 3: # "Connection closed"
466 logging.debug("Got 'Connection closed'")
467 sub.close()
468 return None
lmr3ca79fe2009-06-10 19:24:26 +0000469 elif match == 4: # "Connection refused"
lmr0d2ed1f2009-07-01 03:23:18 +0000470 logging.debug("Got 'Connection refused'")
lmr3ca79fe2009-06-10 19:24:26 +0000471 sub.close()
472 return None
473 elif match == 5: # prompt
lmr6f669ce2009-05-31 19:02:42 +0000474 logging.debug("Got shell prompt -- logged in")
475 return sub
476 else: # match == None
lmr3ca79fe2009-06-10 19:24:26 +0000477 logging.debug("Timeout elapsed or process terminated")
lmr6f669ce2009-05-31 19:02:42 +0000478 sub.close()
479 return None
480
481
482def remote_scp(command, password, timeout=300, login_timeout=10):
483 """
484 Run the given command using kvm_spawn and provide answers to the questions
485 asked. If timeout expires while waiting for the transfer to complete ,
486 fail. If login_timeout expires while waiting for output from the child
487 (e.g. a password prompt), fail.
488
489 @brief: Transfer files using SCP, given a command line.
490
491 @param command: The command to execute
492 (e.g. "scp -r foobar root@localhost:/tmp/").
493 @param password: The password to send in reply to a password prompt.
494 @param timeout: The time duration (in seconds) to wait for the transfer
495 to complete.
496 @param login_timeout: The maximal time duration (in seconds) to wait for
497 each step of the login procedure (i.e. the "Are you sure" prompt or the
498 password prompt)
499
500 @return: True if the transfer succeeds and False on failure.
501 """
lmrdc3a5b12009-07-23 01:40:40 +0000502 sub = kvm_subprocess.kvm_expect(command)
lmr6f669ce2009-05-31 19:02:42 +0000503
504 password_prompt_count = 0
505 _timeout = login_timeout
506
507 logging.debug("Trying to login...")
508
509 while True:
510 (match, text) = sub.read_until_last_line_matches(
lmr3ca79fe2009-06-10 19:24:26 +0000511 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"lost connection"],
lmr6f669ce2009-05-31 19:02:42 +0000512 timeout=_timeout, internal_timeout=0.5)
513 if match == 0: # "Are you sure you want to continue connecting"
514 logging.debug("Got 'Are you sure...'; sending 'yes'")
515 sub.sendline("yes")
516 continue
517 elif match == 1: # "password:"
518 if password_prompt_count == 0:
519 logging.debug("Got password prompt; sending '%s'" % password)
520 sub.sendline(password)
521 password_prompt_count += 1
522 _timeout = timeout
523 continue
524 else:
525 logging.debug("Got password prompt again")
526 sub.close()
527 return False
528 elif match == 2: # "lost connection"
529 logging.debug("Got 'lost connection'")
530 sub.close()
531 return False
532 else: # match == None
lmrdc3a5b12009-07-23 01:40:40 +0000533 logging.debug("Timeout elapsed or process terminated")
534 status = sub.get_status()
lmr6f669ce2009-05-31 19:02:42 +0000535 sub.close()
lmrdc3a5b12009-07-23 01:40:40 +0000536 return status == 0
lmr6f669ce2009-05-31 19:02:42 +0000537
538
539def scp_to_remote(host, port, username, password, local_path, remote_path,
540 timeout=300):
541 """
542 Copy files to a remote host (guest).
543
lmr912c74b2009-08-17 20:43:27 +0000544 @param host: Hostname or IP address
545 @param username: Username (if required)
546 @param password: Password (if required)
lmr6f669ce2009-05-31 19:02:42 +0000547 @param local_path: Path on the local machine where we are copying from
548 @param remote_path: Path on the remote machine where we are copying to
549 @param timeout: Time in seconds that we will wait before giving up to
550 copy the files.
551
552 @return: True on success and False on failure.
553 """
lmrd16a67d2009-06-10 19:52:59 +0000554 command = ("scp -o UserKnownHostsFile=/dev/null -r -P %s %s %s@%s:%s" %
555 (port, local_path, username, host, remote_path))
lmr6f669ce2009-05-31 19:02:42 +0000556 return remote_scp(command, password, timeout)
557
558
559def scp_from_remote(host, port, username, password, remote_path, local_path,
560 timeout=300):
561 """
562 Copy files from a remote host (guest).
563
lmr912c74b2009-08-17 20:43:27 +0000564 @param host: Hostname or IP address
565 @param username: Username (if required)
566 @param password: Password (if required)
lmr6f669ce2009-05-31 19:02:42 +0000567 @param local_path: Path on the local machine where we are copying from
568 @param remote_path: Path on the remote machine where we are copying to
569 @param timeout: Time in seconds that we will wait before giving up to copy
570 the files.
571
572 @return: True on success and False on failure.
573 """
lmrd16a67d2009-06-10 19:52:59 +0000574 command = ("scp -o UserKnownHostsFile=/dev/null -r -P %s %s@%s:%s %s" %
575 (port, username, host, remote_path, local_path))
lmr6f669ce2009-05-31 19:02:42 +0000576 return remote_scp(command, password, timeout)
577
578
lmr59f9e2d2009-10-05 18:47:16 +0000579def ssh(host, port, username, password, prompt, linesep="\n", timeout=10):
lmr6f669ce2009-05-31 19:02:42 +0000580 """
581 Log into a remote host (guest) using SSH.
582
lmr912c74b2009-08-17 20:43:27 +0000583 @param host: Hostname or IP address
584 @param username: Username (if required)
585 @param password: Password (if required)
586 @param prompt: Shell prompt (regular expression)
lmr6f669ce2009-05-31 19:02:42 +0000587 @timeout: Time in seconds that we will wait before giving up on logging
588 into the host.
589
590 @return: kvm_spawn object on success and None on failure.
591 """
lmrd16a67d2009-06-10 19:52:59 +0000592 command = ("ssh -o UserKnownHostsFile=/dev/null -p %s %s@%s" %
593 (port, username, host))
lmr59f9e2d2009-10-05 18:47:16 +0000594 return remote_login(command, password, prompt, linesep, timeout)
lmr6f669ce2009-05-31 19:02:42 +0000595
596
lmr59f9e2d2009-10-05 18:47:16 +0000597def telnet(host, port, username, password, prompt, linesep="\n", timeout=10):
lmr6f669ce2009-05-31 19:02:42 +0000598 """
599 Log into a remote host (guest) using Telnet.
600
lmr912c74b2009-08-17 20:43:27 +0000601 @param host: Hostname or IP address
602 @param username: Username (if required)
603 @param password: Password (if required)
604 @param prompt: Shell prompt (regular expression)
lmr6f669ce2009-05-31 19:02:42 +0000605 @timeout: Time in seconds that we will wait before giving up on logging
606 into the host.
607
608 @return: kvm_spawn object on success and None on failure.
609 """
610 command = "telnet -l %s %s %s" % (username, host, port)
lmr59f9e2d2009-10-05 18:47:16 +0000611 return remote_login(command, password, prompt, linesep, timeout)
lmr6f669ce2009-05-31 19:02:42 +0000612
613
lmr59f9e2d2009-10-05 18:47:16 +0000614def netcat(host, port, username, password, prompt, linesep="\n", timeout=10):
lmr9f6ebf12009-08-17 20:44:10 +0000615 """
616 Log into a remote host (guest) using Netcat.
617
618 @param host: Hostname or IP address
619 @param username: Username (if required)
620 @param password: Password (if required)
621 @param prompt: Shell prompt (regular expression)
622 @timeout: Time in seconds that we will wait before giving up on logging
623 into the host.
624
625 @return: kvm_spawn object on success and None on failure.
626 """
627 command = "nc %s %s" % (host, port)
lmr59f9e2d2009-10-05 18:47:16 +0000628 return remote_login(command, password, prompt, linesep, timeout)
lmr9f6ebf12009-08-17 20:44:10 +0000629
630
lmr6f669ce2009-05-31 19:02:42 +0000631# The following are utility functions related to ports.
632
lmr6f669ce2009-05-31 19:02:42 +0000633def is_port_free(port):
634 """
635 Return True if the given port is available for use.
636
637 @param port: Port number
638 """
639 try:
640 s = socket.socket()
641 #s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
642 s.bind(("localhost", port))
643 free = True
644 except socket.error:
645 free = False
646 s.close()
647 return free
648
649
650def find_free_port(start_port, end_port):
651 """
652 Return a free port in the range [start_port, end_port).
653
654 @param start_port: First port that will be checked.
655 @param end_port: Port immediately after the last one that will be checked.
656 """
657 for i in range(start_port, end_port):
658 if is_port_free(i):
659 return i
660 return None
661
662
663def find_free_ports(start_port, end_port, count):
664 """
665 Return count free ports in the range [start_port, end_port).
666
667 @count: Initial number of ports known to be free in the range.
668 @param start_port: First port that will be checked.
669 @param end_port: Port immediately after the last one that will be checked.
670 """
671 ports = []
672 i = start_port
673 while i < end_port and count > 0:
674 if is_port_free(i):
675 ports.append(i)
676 count -= 1
677 i += 1
678 return ports
679
680
681# The following are miscellaneous utility functions.
682
lmrb4954e02009-08-17 20:44:42 +0000683def get_path(base_path, user_path):
684 """
685 Translate a user specified path to a real path.
686 If user_path is relative, append it to base_path.
687 If user_path is absolute, return it as is.
688
689 @param base_path: The base path of relative user specified paths.
690 @param user_path: The user specified path.
691 """
692 if os.path.isabs(user_path):
693 return user_path
694 else:
695 return os.path.join(base_path, user_path)
696
697
lmr6f669ce2009-05-31 19:02:42 +0000698def generate_random_string(length):
699 """
700 Return a random string using alphanumeric characters.
701
702 @length: length of the string that will be generated.
703 """
lmr45fc0c22009-09-15 05:16:45 +0000704 r = random.SystemRandom()
lmr6f669ce2009-05-31 19:02:42 +0000705 str = ""
706 chars = string.letters + string.digits
707 while length > 0:
lmr45fc0c22009-09-15 05:16:45 +0000708 str += r.choice(chars)
lmr6f669ce2009-05-31 19:02:42 +0000709 length -= 1
710 return str
711
712
713def format_str_for_message(str):
714 """
715 Format str so that it can be appended to a message.
716 If str consists of one line, prefix it with a space.
717 If str consists of multiple lines, prefix it with a newline.
718
719 @param str: string that will be formatted.
720 """
lmr57355592009-08-07 21:55:49 +0000721 lines = str.splitlines()
722 num_lines = len(lines)
723 str = "\n".join(lines)
lmr6f669ce2009-05-31 19:02:42 +0000724 if num_lines == 0:
725 return ""
726 elif num_lines == 1:
727 return " " + str
728 else:
729 return "\n" + str
730
731
732def wait_for(func, timeout, first=0.0, step=1.0, text=None):
733 """
734 If func() evaluates to True before timeout expires, return the
735 value of func(). Otherwise return None.
736
737 @brief: Wait until func() evaluates to True.
738
739 @param timeout: Timeout in seconds
740 @param first: Time to sleep before first attempt
741 @param steps: Time to sleep between attempts in seconds
742 @param text: Text to print while waiting, for debug purposes
743 """
744 start_time = time.time()
745 end_time = time.time() + timeout
746
747 time.sleep(first)
748
749 while time.time() < end_time:
750 if text:
751 logging.debug("%s (%f secs)" % (text, time.time() - start_time))
752
753 output = func()
754 if output:
755 return output
756
757 time.sleep(step)
758
759 logging.debug("Timeout elapsed")
760 return None
761
762
763def md5sum_file(filename, size=None):
764 """
765 Calculate the md5sum of filename.
766 If size is not None, limit to first size bytes.
767 Throw exception if something is wrong with filename.
768 Can be also implemented with bash one-liner (assuming size%1024==0):
769 dd if=filename bs=1024 count=size/1024 | md5sum -
770
771 @param filename: Path of the file that will have its md5sum calculated.
772 @param returns: md5sum of the file.
773 """
774 chunksize = 4096
775 fsize = os.path.getsize(filename)
776 if not size or size>fsize:
777 size = fsize
778 f = open(filename, 'rb')
779 o = md5.new()
780 while size > 0:
781 if chunksize > size:
782 chunksize = size
783 data = f.read(chunksize)
784 if len(data) == 0:
785 logging.debug("Nothing left to read but size=%d" % size)
786 break
787 o.update(data)
788 size -= len(data)
789 f.close()
790 return o.hexdigest()