blob: 962e2c34e7e3fe24147c6947e08fffb0d7498229 [file] [log] [blame]
lmr6f669ce2009-05-31 19:02:42 +00001"""
2KVM test utility functions.
3
4@copyright: 2008-2009 Red Hat Inc.
5"""
6
lmr5ab537a2010-06-14 15:38:18 +00007import time, string, random, socket, os, signal, re, logging, commands, cPickle
Dale Curtis456d3c12011-07-19 11:42:51 -07008import fcntl, shelve, ConfigParser, threading, sys, UserDict, inspect, tarfile
9import struct, shutil
Eric Lie0493a42010-11-15 13:05:43 -080010from autotest_lib.client.bin import utils, os_dep
lmr84154412010-02-03 18:34:43 +000011from autotest_lib.client.common_lib import error, logging_config
Eric Li22434d42011-04-28 15:17:19 -070012import rss_client, aexpect
Eric Lie0493a42010-11-15 13:05:43 -080013try:
14 import koji
15 KOJI_INSTALLED = True
16except ImportError:
17 KOJI_INSTALLED = False
18
Dale Curtis74a314b2011-06-23 14:55:46 -070019# From include/linux/sockios.h
20SIOCSIFHWADDR = 0x8924
21SIOCGIFHWADDR = 0x8927
22SIOCSIFFLAGS = 0x8914
23SIOCGIFINDEX = 0x8933
24SIOCBRADDIF = 0x89a2
25# From linux/include/linux/if_tun.h
26TUNSETIFF = 0x400454ca
27TUNGETIFF = 0x800454d2
28TUNGETFEATURES = 0x800454cf
29IFF_UP = 0x1
30IFF_TAP = 0x0002
31IFF_NO_PI = 0x1000
32IFF_VNET_HDR = 0x4000
Eric Lie0493a42010-11-15 13:05:43 -080033
34def _lock_file(filename):
35 f = open(filename, "w")
36 fcntl.lockf(f, fcntl.LOCK_EX)
37 return f
38
39
40def _unlock_file(f):
41 fcntl.lockf(f, fcntl.LOCK_UN)
42 f.close()
lmrb635b862009-09-10 14:53:21 +000043
lmr6f669ce2009-05-31 19:02:42 +000044
Eric Li861b2d52011-02-04 14:50:35 -080045def is_vm(obj):
lmre8a66dd2009-09-15 19:51:07 +000046 """
Eric Li861b2d52011-02-04 14:50:35 -080047 Tests whether a given object is a VM object.
lmre8a66dd2009-09-15 19:51:07 +000048
Eric Li861b2d52011-02-04 14:50:35 -080049 @param obj: Python object.
lmre8a66dd2009-09-15 19:51:07 +000050 """
Eric Li861b2d52011-02-04 14:50:35 -080051 return obj.__class__.__name__ == "VM"
lmre8a66dd2009-09-15 19:51:07 +000052
53
Dale Curtis74a314b2011-06-23 14:55:46 -070054class NetError(Exception):
55 pass
56
57
58class TAPModuleError(NetError):
59 def __init__(self, devname, action="open", details=None):
60 NetError.__init__(self, devname)
61 self.devname = devname
62 self.details = details
63
64 def __str__(self):
65 e_msg = "Can't %s %s" % (self.action, self.devname)
66 if self.details is not None:
67 e_msg += " : %s" % self.details
68 return e_msg
69
70
71class TAPNotExistError(NetError):
72 def __init__(self, ifname):
73 NetError.__init__(self, ifname)
74 self.ifname = ifname
75
76 def __str__(self):
77 return "Interface %s does not exist" % self.ifname
78
79
80class TAPCreationError(NetError):
81 def __init__(self, ifname, details=None):
82 NetError.__init__(self, ifname, details)
83 self.ifname = ifname
84 self.details = details
85
86 def __str__(self):
87 e_msg = "Cannot create TAP device %s" % self.ifname
88 if self.details is not None:
89 e_msg += ": %s" % self.details
90 return e_msg
91
92
93class TAPBringUpError(NetError):
94 def __init__(self, ifname):
95 NetError.__init__(self, ifname)
96 self.ifname = ifname
97
98 def __str__(self):
99 return "Cannot bring up TAP %s" % self.ifname
100
101
102class BRAddIfError(NetError):
103 def __init__(self, ifname, brname, details):
104 NetError.__init__(self, ifname, brname, details)
105 self.ifname = ifname
106 self.brname = brname
107 self.details = details
108
109 def __str__(self):
110 return ("Can not add if %s to bridge %s: %s" %
111 (self.ifname, self.brname, self.details))
112
113
114class HwAddrSetError(NetError):
115 def __init__(self, ifname, mac):
116 NetError.__init__(self, ifname, mac)
117 self.ifname = ifname
118 self.mac = mac
119
120 def __str__(self):
121 return "Can not set mac %s to interface %s" % (self.mac, self.ifname)
122
123
124class HwAddrGetError(NetError):
125 def __init__(self, ifname):
126 NetError.__init__(self, ifname)
127 self.ifname = ifname
128
129 def __str__(self):
130 return "Can not get mac of interface %s" % self.ifname
131
132
Eric Li861b2d52011-02-04 14:50:35 -0800133class Env(UserDict.IterableUserDict):
lmre8a66dd2009-09-15 19:51:07 +0000134 """
Eric Li861b2d52011-02-04 14:50:35 -0800135 A dict-like object containing global objects used by tests.
lmre8a66dd2009-09-15 19:51:07 +0000136 """
Eric Li861b2d52011-02-04 14:50:35 -0800137 def __init__(self, filename=None, version=0):
138 """
139 Create an empty Env object or load an existing one from a file.
140
141 If the version recorded in the file is lower than version, or if some
142 error occurs during unpickling, or if filename is not supplied,
143 create an empty Env object.
144
145 @param filename: Path to an env file.
146 @param version: Required env version (int).
147 """
148 UserDict.IterableUserDict.__init__(self)
149 empty = {"version": version}
150 if filename:
151 self._filename = filename
152 try:
Dale Curtis456d3c12011-07-19 11:42:51 -0700153 if os.path.isfile(filename):
154 f = open(filename, "r")
155 env = cPickle.load(f)
156 f.close()
157 if env.get("version", 0) >= version:
158 self.data = env
159 else:
160 logging.warn("Incompatible env file found. Not using it.")
161 self.data = empty
Eric Li861b2d52011-02-04 14:50:35 -0800162 else:
Dale Curtis456d3c12011-07-19 11:42:51 -0700163 # No previous env file found, proceed...
Eric Li861b2d52011-02-04 14:50:35 -0800164 self.data = empty
165 # Almost any exception can be raised during unpickling, so let's
166 # catch them all
167 except Exception, e:
168 logging.warn(e)
169 self.data = empty
170 else:
171 self.data = empty
lmre8a66dd2009-09-15 19:51:07 +0000172
173
Eric Li861b2d52011-02-04 14:50:35 -0800174 def save(self, filename=None):
175 """
176 Pickle the contents of the Env object into a file.
177
178 @param filename: Filename to pickle the dict into. If not supplied,
179 use the filename from which the dict was loaded.
180 """
181 filename = filename or self._filename
182 f = open(filename, "w")
183 cPickle.dump(self.data, f)
184 f.close()
185
186
187 def get_all_vms(self):
188 """
189 Return a list of all VM objects in this Env object.
190 """
191 return [o for o in self.values() if is_vm(o)]
192
193
194 def get_vm(self, name):
195 """
196 Return a VM object by its name.
197
198 @param name: VM name.
199 """
200 return self.get("vm__%s" % name)
201
202
203 def register_vm(self, name, vm):
204 """
205 Register a VM in this Env object.
206
207 @param name: VM name.
208 @param vm: VM object.
209 """
210 self["vm__%s" % name] = vm
211
212
213 def unregister_vm(self, name):
214 """
215 Remove a given VM.
216
217 @param name: VM name.
218 """
219 del self["vm__%s" % name]
220
221
222 def register_installer(self, installer):
223 """
224 Register a installer that was just run
225
226 The installer will be available for other tests, so that
227 information about the installed KVM modules and qemu-kvm can be used by
228 them.
229 """
230 self['last_installer'] = installer
231
232
233 def previous_installer(self):
234 """
235 Return the last installer that was registered
236 """
237 return self.get('last_installer')
238
239
240class Params(UserDict.IterableUserDict):
lmr6f669ce2009-05-31 19:02:42 +0000241 """
Eric Li861b2d52011-02-04 14:50:35 -0800242 A dict-like object passed to every test.
lmr6f669ce2009-05-31 19:02:42 +0000243 """
Eric Li861b2d52011-02-04 14:50:35 -0800244 def objects(self, key):
245 """
246 Return the names of objects defined using a given key.
247
248 @param key: The name of the key whose value lists the objects
249 (e.g. 'nics').
250 """
251 return self.get(key, "").split()
lmr6f669ce2009-05-31 19:02:42 +0000252
253
Eric Li861b2d52011-02-04 14:50:35 -0800254 def object_params(self, obj_name):
255 """
256 Return a dict-like object containing the parameters of an individual
257 object.
lmr6f669ce2009-05-31 19:02:42 +0000258
Eric Li861b2d52011-02-04 14:50:35 -0800259 This method behaves as follows: the suffix '_' + obj_name is removed
260 from all key names that have it. Other key names are left unchanged.
261 The values of keys with the suffix overwrite the values of their
262 suffixless versions.
lmr6f669ce2009-05-31 19:02:42 +0000263
Eric Li861b2d52011-02-04 14:50:35 -0800264 @param obj_name: The name of the object (objects are listed by the
265 objects() method).
266 """
267 suffix = "_" + obj_name
268 new_dict = self.copy()
269 for key in self:
270 if key.endswith(suffix):
271 new_key = key.split(suffix)[0]
272 new_dict[new_key] = self[key]
273 return new_dict
lmr6f669ce2009-05-31 19:02:42 +0000274
275
lmrac5089b2009-08-13 04:05:47 +0000276# Functions related to MAC/IP addresses
277
Eric Lie0493a42010-11-15 13:05:43 -0800278def _open_mac_pool(lock_mode):
279 lock_file = open("/tmp/mac_lock", "w+")
280 fcntl.lockf(lock_file, lock_mode)
281 pool = shelve.open("/tmp/address_pool")
282 return pool, lock_file
283
284
285def _close_mac_pool(pool, lock_file):
286 pool.close()
287 fcntl.lockf(lock_file, fcntl.LOCK_UN)
288 lock_file.close()
289
290
291def _generate_mac_address_prefix(mac_pool):
lmrac5089b2009-08-13 04:05:47 +0000292 """
Eric Lie0493a42010-11-15 13:05:43 -0800293 Generate a random MAC address prefix and add it to the MAC pool dictionary.
294 If there's a MAC prefix there already, do not update the MAC pool and just
295 return what's in there. By convention we will set KVM autotest MAC
296 addresses to start with 0x9a.
lmrac5089b2009-08-13 04:05:47 +0000297
Eric Lie0493a42010-11-15 13:05:43 -0800298 @param mac_pool: The MAC address pool object.
299 @return: The MAC address prefix.
lmrac5089b2009-08-13 04:05:47 +0000300 """
Eric Lie0493a42010-11-15 13:05:43 -0800301 if "prefix" in mac_pool:
302 prefix = mac_pool["prefix"]
Eric Lie0493a42010-11-15 13:05:43 -0800303 else:
304 r = random.SystemRandom()
305 prefix = "9a:%02x:%02x:%02x:" % (r.randint(0x00, 0xff),
306 r.randint(0x00, 0xff),
307 r.randint(0x00, 0xff))
308 mac_pool["prefix"] = prefix
Eric Lie0493a42010-11-15 13:05:43 -0800309 return prefix
lmrac5089b2009-08-13 04:05:47 +0000310
311
Eric Lie0493a42010-11-15 13:05:43 -0800312def generate_mac_address(vm_instance, nic_index):
lmrac5089b2009-08-13 04:05:47 +0000313 """
Eric Lie0493a42010-11-15 13:05:43 -0800314 Randomly generate a MAC address and add it to the MAC address pool.
lmrac5089b2009-08-13 04:05:47 +0000315
Eric Lie0493a42010-11-15 13:05:43 -0800316 Try to generate a MAC address based on a randomly generated MAC address
317 prefix and add it to a persistent dictionary.
318 key = VM instance + NIC index, value = MAC address
319 e.g. {'20100310-165222-Wt7l:0': '9a:5d:94:6a:9b:f9'}
320
321 @param vm_instance: The instance attribute of a VM.
322 @param nic_index: The index of the NIC.
323 @return: MAC address string.
lmrac5089b2009-08-13 04:05:47 +0000324 """
Eric Lie0493a42010-11-15 13:05:43 -0800325 mac_pool, lock_file = _open_mac_pool(fcntl.LOCK_EX)
326 key = "%s:%s" % (vm_instance, nic_index)
327 if key in mac_pool:
328 mac = mac_pool[key]
329 else:
330 prefix = _generate_mac_address_prefix(mac_pool)
331 r = random.SystemRandom()
332 while key not in mac_pool:
333 mac = prefix + "%02x:%02x" % (r.randint(0x00, 0xff),
334 r.randint(0x00, 0xff))
335 if mac in mac_pool.values():
336 continue
337 mac_pool[key] = mac
Eric Lie0493a42010-11-15 13:05:43 -0800338 _close_mac_pool(mac_pool, lock_file)
339 return mac
lmrac5089b2009-08-13 04:05:47 +0000340
341
Eric Lie0493a42010-11-15 13:05:43 -0800342def free_mac_address(vm_instance, nic_index):
lmrac5089b2009-08-13 04:05:47 +0000343 """
Eric Lie0493a42010-11-15 13:05:43 -0800344 Remove a MAC address from the address pool.
lmrac5089b2009-08-13 04:05:47 +0000345
Eric Lie0493a42010-11-15 13:05:43 -0800346 @param vm_instance: The instance attribute of a VM.
347 @param nic_index: The index of the NIC.
lmrac5089b2009-08-13 04:05:47 +0000348 """
Eric Lie0493a42010-11-15 13:05:43 -0800349 mac_pool, lock_file = _open_mac_pool(fcntl.LOCK_EX)
350 key = "%s:%s" % (vm_instance, nic_index)
351 if key in mac_pool:
Eric Lie0493a42010-11-15 13:05:43 -0800352 del mac_pool[key]
353 _close_mac_pool(mac_pool, lock_file)
lmrac5089b2009-08-13 04:05:47 +0000354
355
Eric Lie0493a42010-11-15 13:05:43 -0800356def set_mac_address(vm_instance, nic_index, mac):
lmrac5089b2009-08-13 04:05:47 +0000357 """
Eric Lie0493a42010-11-15 13:05:43 -0800358 Set a MAC address in the pool.
lmrac5089b2009-08-13 04:05:47 +0000359
Eric Lie0493a42010-11-15 13:05:43 -0800360 @param vm_instance: The instance attribute of a VM.
361 @param nic_index: The index of the NIC.
lmrac5089b2009-08-13 04:05:47 +0000362 """
Eric Lie0493a42010-11-15 13:05:43 -0800363 mac_pool, lock_file = _open_mac_pool(fcntl.LOCK_EX)
364 mac_pool["%s:%s" % (vm_instance, nic_index)] = mac
365 _close_mac_pool(mac_pool, lock_file)
lmrac5089b2009-08-13 04:05:47 +0000366
367
Eric Lie0493a42010-11-15 13:05:43 -0800368def get_mac_address(vm_instance, nic_index):
lmrac5089b2009-08-13 04:05:47 +0000369 """
Eric Lie0493a42010-11-15 13:05:43 -0800370 Return a MAC address from the pool.
lmrac5089b2009-08-13 04:05:47 +0000371
Eric Lie0493a42010-11-15 13:05:43 -0800372 @param vm_instance: The instance attribute of a VM.
373 @param nic_index: The index of the NIC.
374 @return: MAC address string.
lmrac5089b2009-08-13 04:05:47 +0000375 """
Eric Lie0493a42010-11-15 13:05:43 -0800376 mac_pool, lock_file = _open_mac_pool(fcntl.LOCK_SH)
377 mac = mac_pool.get("%s:%s" % (vm_instance, nic_index))
378 _close_mac_pool(mac_pool, lock_file)
379 return mac
lmrf3d3e522010-03-23 16:38:12 +0000380
381
lmr53d3e932009-09-09 21:56:18 +0000382def verify_ip_address_ownership(ip, macs, timeout=10.0):
lmree90dd92009-08-13 04:13:39 +0000383 """
lmr53d3e932009-09-09 21:56:18 +0000384 Use arping and the ARP cache to make sure a given IP address belongs to one
385 of the given MAC addresses.
lmree90dd92009-08-13 04:13:39 +0000386
387 @param ip: An IP address.
388 @param macs: A list or tuple of MAC addresses.
389 @return: True iff ip is assigned to a MAC address in macs.
390 """
lmr53d3e932009-09-09 21:56:18 +0000391 # Compile a regex that matches the given IP address and any of the given
392 # MAC addresses
lmree90dd92009-08-13 04:13:39 +0000393 mac_regex = "|".join("(%s)" % mac for mac in macs)
lmre35507b2009-10-28 16:53:08 +0000394 regex = re.compile(r"\b%s\b.*\b(%s)\b" % (ip, mac_regex), re.IGNORECASE)
lmree90dd92009-08-13 04:13:39 +0000395
lmr53d3e932009-09-09 21:56:18 +0000396 # Check the ARP cache
lmr3eb39c72010-07-08 23:34:05 +0000397 o = commands.getoutput("%s -n" % find_command("arp"))
lmre35507b2009-10-28 16:53:08 +0000398 if regex.search(o):
lmree90dd92009-08-13 04:13:39 +0000399 return True
400
lmr53d3e932009-09-09 21:56:18 +0000401 # Get the name of the bridge device for arping
lmr3eb39c72010-07-08 23:34:05 +0000402 o = commands.getoutput("%s route get %s" % (find_command("ip"), ip))
lmr53d3e932009-09-09 21:56:18 +0000403 dev = re.findall("dev\s+\S+", o, re.IGNORECASE)
404 if not dev:
405 return False
406 dev = dev[0].split()[-1]
407
408 # Send an ARP request
lmr3eb39c72010-07-08 23:34:05 +0000409 o = commands.getoutput("%s -f -c 3 -I %s %s" %
410 (find_command("arping"), dev, ip))
lmre35507b2009-10-28 16:53:08 +0000411 return bool(regex.search(o))
lmree90dd92009-08-13 04:13:39 +0000412
413
lmr6f669ce2009-05-31 19:02:42 +0000414# Utility functions for dealing with external processes
415
lmr049239c2010-07-08 23:27:18 +0000416def find_command(cmd):
417 for dir in ["/usr/local/sbin", "/usr/local/bin",
418 "/usr/sbin", "/usr/bin", "/sbin", "/bin"]:
419 file = os.path.join(dir, cmd)
420 if os.path.exists(file):
421 return file
422 raise ValueError('Missing command: %s' % cmd)
423
424
lmr6f669ce2009-05-31 19:02:42 +0000425def pid_exists(pid):
426 """
427 Return True if a given PID exists.
428
429 @param pid: Process ID number.
430 """
431 try:
432 os.kill(pid, 0)
433 return True
434 except:
435 return False
436
437
438def safe_kill(pid, signal):
439 """
440 Attempt to send a signal to a given process that may or may not exist.
441
442 @param signal: Signal number.
443 """
444 try:
445 os.kill(pid, signal)
446 return True
447 except:
448 return False
449
450
lmr1aeaefb2009-09-09 22:12:49 +0000451def kill_process_tree(pid, sig=signal.SIGKILL):
452 """Signal a process and all of its children.
453
454 If the process does not exist -- return.
455
456 @param pid: The pid of the process to signal.
457 @param sig: The signal to send to the processes.
458 """
459 if not safe_kill(pid, signal.SIGSTOP):
460 return
461 children = commands.getoutput("ps --ppid=%d -o pid=" % pid).split()
462 for child in children:
463 kill_process_tree(int(child), sig)
464 safe_kill(pid, sig)
465 safe_kill(pid, signal.SIGCONT)
466
467
lmr3f0b0cc2009-06-10 02:25:23 +0000468def get_git_branch(repository, branch, srcdir, commit=None, lbranch=None):
469 """
470 Retrieves a given git code repository.
471
472 @param repository: Git repository URL
473 """
lmr160e9942010-06-09 14:09:19 +0000474 logging.info("Fetching git [REP '%s' BRANCH '%s' COMMIT '%s'] -> %s",
lmr3f0b0cc2009-06-10 02:25:23 +0000475 repository, branch, commit, srcdir)
476 if not os.path.exists(srcdir):
477 os.makedirs(srcdir)
478 os.chdir(srcdir)
479
480 if os.path.exists(".git"):
481 utils.system("git reset --hard")
482 else:
483 utils.system("git init")
484
485 if not lbranch:
486 lbranch = branch
487
488 utils.system("git fetch -q -f -u -t %s %s:%s" %
489 (repository, branch, lbranch))
490 utils.system("git checkout %s" % lbranch)
491 if commit:
492 utils.system("git checkout %s" % commit)
493
494 h = utils.system_output('git log --pretty=format:"%H" -1')
lmr05cec0f2010-01-26 17:50:29 +0000495 try:
496 desc = "tag %s" % utils.system_output("git describe")
497 except error.CmdError:
498 desc = "no tag found"
499
Eric Li861b2d52011-02-04 14:50:35 -0800500 logging.info("Commit hash for %s is %s (%s)", repository, h.strip(), desc)
lmr3f0b0cc2009-06-10 02:25:23 +0000501 return srcdir
502
503
lmr3f0b0cc2009-06-10 02:25:23 +0000504def check_kvm_source_dir(source_dir):
505 """
506 Inspects the kvm source directory and verifies its disposition. In some
507 occasions build may be dependant on the source directory disposition.
508 The reason why the return codes are numbers is that we might have more
509 changes on the source directory layout, so it's not scalable to just use
510 strings like 'old_repo', 'new_repo' and such.
511
512 @param source_dir: Source code path that will be inspected.
513 """
514 os.chdir(source_dir)
515 has_qemu_dir = os.path.isdir('qemu')
516 has_kvm_dir = os.path.isdir('kvm')
Eric Li861b2d52011-02-04 14:50:35 -0800517 if has_qemu_dir:
lmr3f0b0cc2009-06-10 02:25:23 +0000518 logging.debug("qemu directory detected, source dir layout 1")
519 return 1
520 if has_kvm_dir and not has_qemu_dir:
521 logging.debug("kvm directory detected, source dir layout 2")
522 return 2
523 else:
524 raise error.TestError("Unknown source dir layout, cannot proceed.")
525
526
Eric Li861b2d52011-02-04 14:50:35 -0800527# Functions and classes used for logging into guests and transferring files
528
529class LoginError(Exception):
530 def __init__(self, msg, output):
531 Exception.__init__(self, msg, output)
532 self.msg = msg
533 self.output = output
534
535 def __str__(self):
536 return "%s (output: %r)" % (self.msg, self.output)
537
538
539class LoginAuthenticationError(LoginError):
540 pass
541
542
543class LoginTimeoutError(LoginError):
544 def __init__(self, output):
545 LoginError.__init__(self, "Login timeout expired", output)
546
547
548class LoginProcessTerminatedError(LoginError):
549 def __init__(self, status, output):
550 LoginError.__init__(self, None, output)
551 self.status = status
552
553 def __str__(self):
554 return ("Client process terminated (status: %s, output: %r)" %
555 (self.status, self.output))
556
557
558class LoginBadClientError(LoginError):
559 def __init__(self, client):
560 LoginError.__init__(self, None, None)
561 self.client = client
562
563 def __str__(self):
564 return "Unknown remote shell client: %r" % self.client
565
566
567class SCPError(Exception):
568 def __init__(self, msg, output):
569 Exception.__init__(self, msg, output)
570 self.msg = msg
571 self.output = output
572
573 def __str__(self):
574 return "%s (output: %r)" % (self.msg, self.output)
575
576
577class SCPAuthenticationError(SCPError):
578 pass
579
580
581class SCPAuthenticationTimeoutError(SCPAuthenticationError):
582 def __init__(self, output):
583 SCPAuthenticationError.__init__(self, "Authentication timeout expired",
584 output)
585
586
587class SCPTransferTimeoutError(SCPError):
588 def __init__(self, output):
589 SCPError.__init__(self, "Transfer timeout expired", output)
590
591
592class SCPTransferFailedError(SCPError):
593 def __init__(self, status, output):
594 SCPError.__init__(self, None, output)
595 self.status = status
596
597 def __str__(self):
598 return ("SCP transfer failed (status: %s, output: %r)" %
599 (self.status, self.output))
600
lmr6f669ce2009-05-31 19:02:42 +0000601
lmra4cd1f12010-06-22 02:01:00 +0000602def _remote_login(session, username, password, prompt, timeout=10):
lmr6f669ce2009-05-31 19:02:42 +0000603 """
lmr158604f2010-06-22 01:57:34 +0000604 Log into a remote host (guest) using SSH or Telnet. Wait for questions
605 and provide answers. If timeout expires while waiting for output from the
606 child (e.g. a password prompt or a shell prompt) -- fail.
lmr6f669ce2009-05-31 19:02:42 +0000607
608 @brief: Log into a remote host (guest) using SSH or Telnet.
609
Eric Li861b2d52011-02-04 14:50:35 -0800610 @param session: An Expect or ShellSession instance to operate on
lmra4cd1f12010-06-22 02:01:00 +0000611 @param username: The username to send in reply to a login prompt
lmr6f669ce2009-05-31 19:02:42 +0000612 @param password: The password to send in reply to a password prompt
613 @param prompt: The shell prompt that indicates a successful login
lmr6f669ce2009-05-31 19:02:42 +0000614 @param timeout: The maximal time duration (in seconds) to wait for each
615 step of the login procedure (i.e. the "Are you sure" prompt, the
616 password prompt, the shell prompt, etc)
Eric Li861b2d52011-02-04 14:50:35 -0800617 @raise LoginTimeoutError: If timeout expires
618 @raise LoginAuthenticationError: If authentication fails
619 @raise LoginProcessTerminatedError: If the client terminates during login
620 @raise LoginError: If some other error occurs
lmr6f669ce2009-05-31 19:02:42 +0000621 """
lmr6f669ce2009-05-31 19:02:42 +0000622 password_prompt_count = 0
lmra4cd1f12010-06-22 02:01:00 +0000623 login_prompt_count = 0
lmr6f669ce2009-05-31 19:02:42 +0000624
lmr6f669ce2009-05-31 19:02:42 +0000625 while True:
Eric Li861b2d52011-02-04 14:50:35 -0800626 try:
627 match, text = session.read_until_last_line_matches(
lmr2c502672010-06-22 02:02:19 +0000628 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"[Ll]ogin:\s*$",
lmrba69bc52010-03-25 01:50:09 +0000629 r"[Cc]onnection.*closed", r"[Cc]onnection.*refused",
Eric Li0a993912011-05-17 12:56:25 -0700630 r"[Pp]lease wait", r"[Ww]arning", prompt],
lmr158604f2010-06-22 01:57:34 +0000631 timeout=timeout, internal_timeout=0.5)
Eric Li861b2d52011-02-04 14:50:35 -0800632 if match == 0: # "Are you sure you want to continue connecting"
Dale Curtis456d3c12011-07-19 11:42:51 -0700633 logging.debug("Got 'Are you sure...', sending 'yes'")
Eric Li861b2d52011-02-04 14:50:35 -0800634 session.sendline("yes")
lmr158604f2010-06-22 01:57:34 +0000635 continue
Eric Li861b2d52011-02-04 14:50:35 -0800636 elif match == 1: # "password:"
637 if password_prompt_count == 0:
Dale Curtis456d3c12011-07-19 11:42:51 -0700638 logging.debug("Got password prompt, sending '%s'", password)
Eric Li861b2d52011-02-04 14:50:35 -0800639 session.sendline(password)
640 password_prompt_count += 1
641 continue
642 else:
643 raise LoginAuthenticationError("Got password prompt twice",
644 text)
645 elif match == 2: # "login:"
646 if login_prompt_count == 0 and password_prompt_count == 0:
647 logging.debug("Got username prompt; sending '%s'", username)
648 session.sendline(username)
649 login_prompt_count += 1
650 continue
651 else:
652 if login_prompt_count > 0:
653 msg = "Got username prompt twice"
654 else:
655 msg = "Got username prompt after password prompt"
656 raise LoginAuthenticationError(msg, text)
657 elif match == 3: # "Connection closed"
658 raise LoginError("Client said 'connection closed'", text)
659 elif match == 4: # "Connection refused"
660 raise LoginError("Client said 'connection refused'", text)
661 elif match == 5: # "Please wait"
662 logging.debug("Got 'Please wait'")
663 timeout = 30
664 continue
Eric Li0a993912011-05-17 12:56:25 -0700665 elif match == 6: # "Warning added RSA"
666 logging.debug("Got 'Warning added RSA to known host list")
667 continue
668 elif match == 7: # prompt
Eric Li861b2d52011-02-04 14:50:35 -0800669 logging.debug("Got shell prompt -- logged in")
670 break
Eric Li22434d42011-04-28 15:17:19 -0700671 except aexpect.ExpectTimeoutError, e:
Eric Li861b2d52011-02-04 14:50:35 -0800672 raise LoginTimeoutError(e.output)
Eric Li22434d42011-04-28 15:17:19 -0700673 except aexpect.ExpectProcessTerminatedError, e:
Eric Li861b2d52011-02-04 14:50:35 -0800674 raise LoginProcessTerminatedError(e.status, e.output)
lmr158604f2010-06-22 01:57:34 +0000675
676
677def remote_login(client, host, port, username, password, prompt, linesep="\n",
lmre56903f2010-06-22 02:09:35 +0000678 log_filename=None, timeout=10):
lmr158604f2010-06-22 01:57:34 +0000679 """
680 Log into a remote host (guest) using SSH/Telnet/Netcat.
681
682 @param client: The client to use ('ssh', 'telnet' or 'nc')
683 @param host: Hostname or IP address
684 @param port: Port to connect to
685 @param username: Username (if required)
686 @param password: Password (if required)
687 @param prompt: Shell prompt (regular expression)
688 @param linesep: The line separator to use when sending lines
689 (e.g. '\\n' or '\\r\\n')
lmre56903f2010-06-22 02:09:35 +0000690 @param log_filename: If specified, log all output to this file
lmr158604f2010-06-22 01:57:34 +0000691 @param timeout: The maximal time duration (in seconds) to wait for
692 each step of the login procedure (i.e. the "Are you sure" prompt
693 or the password prompt)
Eric Li861b2d52011-02-04 14:50:35 -0800694 @raise LoginBadClientError: If an unknown client is requested
695 @raise: Whatever _remote_login() raises
696 @return: A ShellSession object.
lmr158604f2010-06-22 01:57:34 +0000697 """
698 if client == "ssh":
699 cmd = ("ssh -o UserKnownHostsFile=/dev/null "
700 "-o PreferredAuthentications=password -p %s %s@%s" %
701 (port, username, host))
702 elif client == "telnet":
703 cmd = "telnet -l %s %s %s" % (username, host, port)
704 elif client == "nc":
705 cmd = "nc %s %s" % (host, port)
706 else:
Eric Li861b2d52011-02-04 14:50:35 -0800707 raise LoginBadClientError(client)
lmre56903f2010-06-22 02:09:35 +0000708
Eric Li861b2d52011-02-04 14:50:35 -0800709 logging.debug("Trying to login with command '%s'", cmd)
Eric Li22434d42011-04-28 15:17:19 -0700710 session = aexpect.ShellSession(cmd, linesep=linesep, prompt=prompt)
Eric Li861b2d52011-02-04 14:50:35 -0800711 try:
712 _remote_login(session, username, password, prompt, timeout)
713 except:
lmr158604f2010-06-22 01:57:34 +0000714 session.close()
Eric Li861b2d52011-02-04 14:50:35 -0800715 raise
716 if log_filename:
717 session.set_output_func(log_line)
718 session.set_output_params((log_filename,))
719 return session
720
721
722def wait_for_login(client, host, port, username, password, prompt, linesep="\n",
723 log_filename=None, timeout=240, internal_timeout=10):
724 """
725 Make multiple attempts to log into a remote host (guest) until one succeeds
726 or timeout expires.
727
728 @param timeout: Total time duration to wait for a successful login
729 @param internal_timeout: The maximal time duration (in seconds) to wait for
730 each step of the login procedure (e.g. the "Are you sure" prompt
731 or the password prompt)
732 @see: remote_login()
733 @raise: Whatever remote_login() raises
734 @return: A ShellSession object.
735 """
736 logging.debug("Attempting to log into %s:%s using %s (timeout %ds)",
737 host, port, client, timeout)
738 end_time = time.time() + timeout
739 while time.time() < end_time:
740 try:
741 return remote_login(client, host, port, username, password, prompt,
742 linesep, log_filename, internal_timeout)
743 except LoginError, e:
744 logging.debug(e)
745 time.sleep(2)
746 # Timeout expired; try one more time but don't catch exceptions
747 return remote_login(client, host, port, username, password, prompt,
748 linesep, log_filename, internal_timeout)
749
750
Eric Lid656d562011-04-20 11:48:29 -0700751def _remote_scp(session, password_list, transfer_timeout=600, login_timeout=10):
Eric Li861b2d52011-02-04 14:50:35 -0800752 """
753 Transfer file(s) to a remote host (guest) using SCP. Wait for questions
754 and provide answers. If login_timeout expires while waiting for output
755 from the child (e.g. a password prompt), fail. If transfer_timeout expires
756 while waiting for the transfer to complete, fail.
757
758 @brief: Transfer files using SCP, given a command line.
759
760 @param session: An Expect or ShellSession instance to operate on
Eric Lid656d562011-04-20 11:48:29 -0700761 @param password_list: Password list to send in reply to the password prompt
Eric Li861b2d52011-02-04 14:50:35 -0800762 @param transfer_timeout: The time duration (in seconds) to wait for the
763 transfer to complete.
764 @param login_timeout: The maximal time duration (in seconds) to wait for
765 each step of the login procedure (i.e. the "Are you sure" prompt or
766 the password prompt)
767 @raise SCPAuthenticationError: If authentication fails
768 @raise SCPTransferTimeoutError: If the transfer fails to complete in time
769 @raise SCPTransferFailedError: If the process terminates with a nonzero
770 exit code
771 @raise SCPError: If some other error occurs
772 """
773 password_prompt_count = 0
774 timeout = login_timeout
775 authentication_done = False
776
Eric Lid656d562011-04-20 11:48:29 -0700777 scp_type = len(password_list)
778
Eric Li861b2d52011-02-04 14:50:35 -0800779 while True:
780 try:
781 match, text = session.read_until_last_line_matches(
782 [r"[Aa]re you sure", r"[Pp]assword:\s*$", r"lost connection"],
783 timeout=timeout, internal_timeout=0.5)
784 if match == 0: # "Are you sure you want to continue connecting"
Dale Curtis456d3c12011-07-19 11:42:51 -0700785 logging.debug("Got 'Are you sure...', sending 'yes'")
Eric Li861b2d52011-02-04 14:50:35 -0800786 session.sendline("yes")
787 continue
788 elif match == 1: # "password:"
789 if password_prompt_count == 0:
Dale Curtis456d3c12011-07-19 11:42:51 -0700790 logging.debug("Got password prompt, sending '%s'" %
Eric Lid656d562011-04-20 11:48:29 -0700791 password_list[password_prompt_count])
792 session.sendline(password_list[password_prompt_count])
793 password_prompt_count += 1
794 timeout = transfer_timeout
795 if scp_type == 1:
796 authentication_done = True
797 continue
798 elif password_prompt_count == 1 and scp_type == 2:
Dale Curtis456d3c12011-07-19 11:42:51 -0700799 logging.debug("Got password prompt, sending '%s'" %
Eric Lid656d562011-04-20 11:48:29 -0700800 password_list[password_prompt_count])
801 session.sendline(password_list[password_prompt_count])
Eric Li861b2d52011-02-04 14:50:35 -0800802 password_prompt_count += 1
803 timeout = transfer_timeout
804 authentication_done = True
805 continue
806 else:
807 raise SCPAuthenticationError("Got password prompt twice",
808 text)
809 elif match == 2: # "lost connection"
810 raise SCPError("SCP client said 'lost connection'", text)
Eric Li22434d42011-04-28 15:17:19 -0700811 except aexpect.ExpectTimeoutError, e:
Eric Li861b2d52011-02-04 14:50:35 -0800812 if authentication_done:
813 raise SCPTransferTimeoutError(e.output)
814 else:
815 raise SCPAuthenticationTimeoutError(e.output)
Eric Li22434d42011-04-28 15:17:19 -0700816 except aexpect.ExpectProcessTerminatedError, e:
Eric Li861b2d52011-02-04 14:50:35 -0800817 if e.status == 0:
818 logging.debug("SCP process terminated with status 0")
819 break
820 else:
821 raise SCPTransferFailedError(e.status, e.output)
lmr6f669ce2009-05-31 19:02:42 +0000822
823
Eric Lid656d562011-04-20 11:48:29 -0700824def remote_scp(command, password_list, log_filename=None, transfer_timeout=600,
lmre56903f2010-06-22 02:09:35 +0000825 login_timeout=10):
lmr6f669ce2009-05-31 19:02:42 +0000826 """
lmr158604f2010-06-22 01:57:34 +0000827 Transfer file(s) to a remote host (guest) using SCP.
lmr6f669ce2009-05-31 19:02:42 +0000828
829 @brief: Transfer files using SCP, given a command line.
830
831 @param command: The command to execute
832 (e.g. "scp -r foobar root@localhost:/tmp/").
Eric Lid656d562011-04-20 11:48:29 -0700833 @param password_list: Password list to send in reply to a password prompt.
lmre56903f2010-06-22 02:09:35 +0000834 @param log_filename: If specified, log all output to this file
lmrc5f42a32010-06-18 14:15:19 +0000835 @param transfer_timeout: The time duration (in seconds) to wait for the
lmr158604f2010-06-22 01:57:34 +0000836 transfer to complete.
lmr6f669ce2009-05-31 19:02:42 +0000837 @param login_timeout: The maximal time duration (in seconds) to wait for
lmr158604f2010-06-22 01:57:34 +0000838 each step of the login procedure (i.e. the "Are you sure" prompt
839 or the password prompt)
Eric Li861b2d52011-02-04 14:50:35 -0800840 @raise: Whatever _remote_scp() raises
lmr6f669ce2009-05-31 19:02:42 +0000841 """
lmr158604f2010-06-22 01:57:34 +0000842 logging.debug("Trying to SCP with command '%s', timeout %ss",
843 command, transfer_timeout)
lmre56903f2010-06-22 02:09:35 +0000844 if log_filename:
845 output_func = log_line
846 output_params = (log_filename,)
847 else:
848 output_func = None
849 output_params = ()
Eric Li22434d42011-04-28 15:17:19 -0700850 session = aexpect.Expect(command,
Eric Li861b2d52011-02-04 14:50:35 -0800851 output_func=output_func,
852 output_params=output_params)
lmr158604f2010-06-22 01:57:34 +0000853 try:
Eric Lid656d562011-04-20 11:48:29 -0700854 _remote_scp(session, password_list, transfer_timeout, login_timeout)
lmr158604f2010-06-22 01:57:34 +0000855 finally:
856 session.close()
lmr6f669ce2009-05-31 19:02:42 +0000857
858
859def scp_to_remote(host, port, username, password, local_path, remote_path,
lmre56903f2010-06-22 02:09:35 +0000860 log_filename=None, timeout=600):
lmr6f669ce2009-05-31 19:02:42 +0000861 """
Eric Li861b2d52011-02-04 14:50:35 -0800862 Copy files to a remote host (guest) through scp.
lmr6f669ce2009-05-31 19:02:42 +0000863
lmr912c74b2009-08-17 20:43:27 +0000864 @param host: Hostname or IP address
865 @param username: Username (if required)
866 @param password: Password (if required)
lmr6f669ce2009-05-31 19:02:42 +0000867 @param local_path: Path on the local machine where we are copying from
868 @param remote_path: Path on the remote machine where we are copying to
lmre56903f2010-06-22 02:09:35 +0000869 @param log_filename: If specified, log all output to this file
lmr158604f2010-06-22 01:57:34 +0000870 @param timeout: The time duration (in seconds) to wait for the transfer
871 to complete.
Eric Li861b2d52011-02-04 14:50:35 -0800872 @raise: Whatever remote_scp() raises
lmr6f669ce2009-05-31 19:02:42 +0000873 """
lmrc196d492010-05-07 14:14:26 +0000874 command = ("scp -v -o UserKnownHostsFile=/dev/null "
lmr1b7e1702009-11-10 16:30:39 +0000875 "-o PreferredAuthentications=password -r -P %s %s %s@%s:%s" %
lmrd16a67d2009-06-10 19:52:59 +0000876 (port, local_path, username, host, remote_path))
Eric Lid656d562011-04-20 11:48:29 -0700877 password_list = []
878 password_list.append(password)
879 return remote_scp(command, password_list, log_filename, timeout)
880
lmr6f669ce2009-05-31 19:02:42 +0000881
882
883def scp_from_remote(host, port, username, password, remote_path, local_path,
lmre56903f2010-06-22 02:09:35 +0000884 log_filename=None, timeout=600):
lmr6f669ce2009-05-31 19:02:42 +0000885 """
886 Copy files from a remote host (guest).
887
lmr912c74b2009-08-17 20:43:27 +0000888 @param host: Hostname or IP address
889 @param username: Username (if required)
890 @param password: Password (if required)
lmr6f669ce2009-05-31 19:02:42 +0000891 @param local_path: Path on the local machine where we are copying from
892 @param remote_path: Path on the remote machine where we are copying to
lmre56903f2010-06-22 02:09:35 +0000893 @param log_filename: If specified, log all output to this file
lmr158604f2010-06-22 01:57:34 +0000894 @param timeout: The time duration (in seconds) to wait for the transfer
895 to complete.
Eric Li861b2d52011-02-04 14:50:35 -0800896 @raise: Whatever remote_scp() raises
lmr6f669ce2009-05-31 19:02:42 +0000897 """
lmrc196d492010-05-07 14:14:26 +0000898 command = ("scp -v -o UserKnownHostsFile=/dev/null "
lmr1b7e1702009-11-10 16:30:39 +0000899 "-o PreferredAuthentications=password -r -P %s %s@%s:%s %s" %
lmrd16a67d2009-06-10 19:52:59 +0000900 (port, username, host, remote_path, local_path))
Eric Lid656d562011-04-20 11:48:29 -0700901 password_list = []
902 password_list.append(password)
903 remote_scp(command, password_list, log_filename, timeout)
904
905
906def scp_between_remotes(src, dst, port, s_passwd, d_passwd, s_name, d_name,
907 s_path, d_path, log_filename=None, timeout=600):
908 """
909 Copy files from a remote host (guest) to another remote host (guest).
910
911 @param src/dst: Hostname or IP address of src and dst
912 @param s_name/d_name: Username (if required)
913 @param s_passwd/d_passwd: Password (if required)
914 @param s_path/d_path: Path on the remote machine where we are copying
915 from/to
916 @param log_filename: If specified, log all output to this file
917 @param timeout: The time duration (in seconds) to wait for the transfer
918 to complete.
919
920 @return: True on success and False on failure.
921 """
922 command = ("scp -v -o UserKnownHostsFile=/dev/null -o "
923 "PreferredAuthentications=password -r -P %s %s@%s:%s %s@%s:%s" %
924 (port, s_name, src, s_path, d_name, dst, d_path))
925 password_list = []
926 password_list.append(s_passwd)
927 password_list.append(d_passwd)
928 return remote_scp(command, password_list, log_filename, timeout)
Eric Li861b2d52011-02-04 14:50:35 -0800929
930
931def copy_files_to(address, client, username, password, port, local_path,
932 remote_path, log_filename=None, verbose=False, timeout=600):
933 """
934 Copy files to a remote host (guest) using the selected client.
935
936 @param client: Type of transfer client
937 @param username: Username (if required)
938 @param password: Password (if requried)
939 @param local_path: Path on the local machine where we are copying from
940 @param remote_path: Path on the remote machine where we are copying to
941 @param address: Address of remote host(guest)
942 @param log_filename: If specified, log all output to this file (SCP only)
943 @param verbose: If True, log some stats using logging.debug (RSS only)
944 @param timeout: The time duration (in seconds) to wait for the transfer to
945 complete.
946 @raise: Whatever remote_scp() raises
947 """
948 if client == "scp":
949 scp_to_remote(address, port, username, password, local_path,
950 remote_path, log_filename, timeout)
951 elif client == "rss":
952 log_func = None
953 if verbose:
954 log_func = logging.debug
Eric Li22434d42011-04-28 15:17:19 -0700955 c = rss_client.FileUploadClient(address, port, log_func)
Eric Li861b2d52011-02-04 14:50:35 -0800956 c.upload(local_path, remote_path, timeout)
957 c.close()
958
959
960def copy_files_from(address, client, username, password, port, remote_path,
961 local_path, log_filename=None, verbose=False, timeout=600):
962 """
963 Copy files from a remote host (guest) using the selected client.
964
965 @param client: Type of transfer client
966 @param username: Username (if required)
967 @param password: Password (if requried)
968 @param remote_path: Path on the remote machine where we are copying from
969 @param local_path: Path on the local machine where we are copying to
970 @param address: Address of remote host(guest)
971 @param log_filename: If specified, log all output to this file (SCP only)
972 @param verbose: If True, log some stats using logging.debug (RSS only)
973 @param timeout: The time duration (in seconds) to wait for the transfer to
974 complete.
975 @raise: Whatever remote_scp() raises
976 """
977 if client == "scp":
978 scp_from_remote(address, port, username, password, remote_path,
979 local_path, log_filename, timeout)
980 elif client == "rss":
981 log_func = None
982 if verbose:
983 log_func = logging.debug
Eric Li22434d42011-04-28 15:17:19 -0700984 c = rss_client.FileDownloadClient(address, port, log_func)
Eric Li861b2d52011-02-04 14:50:35 -0800985 c.download(remote_path, local_path, timeout)
986 c.close()
lmr6f669ce2009-05-31 19:02:42 +0000987
988
lmr6f669ce2009-05-31 19:02:42 +0000989# The following are utility functions related to ports.
990
Eric Lie0493a42010-11-15 13:05:43 -0800991def is_port_free(port, address):
lmr6f669ce2009-05-31 19:02:42 +0000992 """
993 Return True if the given port is available for use.
994
995 @param port: Port number
996 """
997 try:
998 s = socket.socket()
999 #s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
Eric Lie0493a42010-11-15 13:05:43 -08001000 if address == "localhost":
1001 s.bind(("localhost", port))
1002 free = True
1003 else:
1004 s.connect((address, port))
1005 free = False
lmr6f669ce2009-05-31 19:02:42 +00001006 except socket.error:
Eric Lie0493a42010-11-15 13:05:43 -08001007 if address == "localhost":
1008 free = False
1009 else:
1010 free = True
lmr6f669ce2009-05-31 19:02:42 +00001011 s.close()
1012 return free
1013
1014
Eric Lie0493a42010-11-15 13:05:43 -08001015def find_free_port(start_port, end_port, address="localhost"):
lmr6f669ce2009-05-31 19:02:42 +00001016 """
lmra29a5cb2010-03-18 02:39:34 +00001017 Return a host free port in the range [start_port, end_port].
lmr6f669ce2009-05-31 19:02:42 +00001018
1019 @param start_port: First port that will be checked.
1020 @param end_port: Port immediately after the last one that will be checked.
1021 """
1022 for i in range(start_port, end_port):
Eric Lie0493a42010-11-15 13:05:43 -08001023 if is_port_free(i, address):
lmr6f669ce2009-05-31 19:02:42 +00001024 return i
1025 return None
1026
1027
Eric Lie0493a42010-11-15 13:05:43 -08001028def find_free_ports(start_port, end_port, count, address="localhost"):
lmr6f669ce2009-05-31 19:02:42 +00001029 """
lmra29a5cb2010-03-18 02:39:34 +00001030 Return count of host free ports in the range [start_port, end_port].
lmr6f669ce2009-05-31 19:02:42 +00001031
1032 @count: Initial number of ports known to be free in the range.
1033 @param start_port: First port that will be checked.
1034 @param end_port: Port immediately after the last one that will be checked.
1035 """
1036 ports = []
1037 i = start_port
1038 while i < end_port and count > 0:
Eric Lie0493a42010-11-15 13:05:43 -08001039 if is_port_free(i, address):
lmr6f669ce2009-05-31 19:02:42 +00001040 ports.append(i)
1041 count -= 1
1042 i += 1
1043 return ports
1044
1045
lmr23894542010-06-22 01:54:37 +00001046# An easy way to log lines to files when the logging system can't be used
1047
1048_open_log_files = {}
1049_log_file_dir = "/tmp"
1050
1051
1052def log_line(filename, line):
1053 """
1054 Write a line to a file. '\n' is appended to the line.
1055
1056 @param filename: Path of file to write to, either absolute or relative to
1057 the dir set by set_log_file_dir().
1058 @param line: Line to write.
1059 """
1060 global _open_log_files, _log_file_dir
1061 if filename not in _open_log_files:
1062 path = get_path(_log_file_dir, filename)
1063 try:
1064 os.makedirs(os.path.dirname(path))
1065 except OSError:
1066 pass
1067 _open_log_files[filename] = open(path, "w")
1068 timestr = time.strftime("%Y-%m-%d %H:%M:%S")
1069 _open_log_files[filename].write("%s: %s\n" % (timestr, line))
1070 _open_log_files[filename].flush()
1071
1072
1073def set_log_file_dir(dir):
1074 """
1075 Set the base directory for log files created by log_line().
1076
1077 @param dir: Directory for log files.
1078 """
1079 global _log_file_dir
1080 _log_file_dir = dir
1081
1082
lmr6f669ce2009-05-31 19:02:42 +00001083# The following are miscellaneous utility functions.
1084
lmrb4954e02009-08-17 20:44:42 +00001085def get_path(base_path, user_path):
1086 """
1087 Translate a user specified path to a real path.
1088 If user_path is relative, append it to base_path.
1089 If user_path is absolute, return it as is.
1090
1091 @param base_path: The base path of relative user specified paths.
1092 @param user_path: The user specified path.
1093 """
1094 if os.path.isabs(user_path):
1095 return user_path
1096 else:
1097 return os.path.join(base_path, user_path)
1098
1099
lmr6f669ce2009-05-31 19:02:42 +00001100def generate_random_string(length):
1101 """
1102 Return a random string using alphanumeric characters.
1103
1104 @length: length of the string that will be generated.
1105 """
lmr45fc0c22009-09-15 05:16:45 +00001106 r = random.SystemRandom()
lmr6f669ce2009-05-31 19:02:42 +00001107 str = ""
1108 chars = string.letters + string.digits
1109 while length > 0:
lmr45fc0c22009-09-15 05:16:45 +00001110 str += r.choice(chars)
lmr6f669ce2009-05-31 19:02:42 +00001111 length -= 1
1112 return str
1113
lmr42b16662010-06-22 11:55:23 +00001114def generate_random_id():
1115 """
1116 Return a random string suitable for use as a qemu id.
1117 """
1118 return "id" + generate_random_string(6)
1119
lmr6f669ce2009-05-31 19:02:42 +00001120
lmr0bee2342010-02-24 00:01:37 +00001121def generate_tmp_file_name(file, ext=None, dir='/tmp/'):
1122 """
1123 Returns a temporary file name. The file is not created.
1124 """
1125 while True:
1126 file_name = (file + '-' + time.strftime("%Y%m%d-%H%M%S-") +
1127 generate_random_string(4))
1128 if ext:
1129 file_name += '.' + ext
1130 file_name = os.path.join(dir, file_name)
1131 if not os.path.exists(file_name):
1132 break
1133
1134 return file_name
1135
1136
lmr6f669ce2009-05-31 19:02:42 +00001137def format_str_for_message(str):
1138 """
1139 Format str so that it can be appended to a message.
1140 If str consists of one line, prefix it with a space.
1141 If str consists of multiple lines, prefix it with a newline.
1142
1143 @param str: string that will be formatted.
1144 """
lmr57355592009-08-07 21:55:49 +00001145 lines = str.splitlines()
1146 num_lines = len(lines)
1147 str = "\n".join(lines)
lmr6f669ce2009-05-31 19:02:42 +00001148 if num_lines == 0:
1149 return ""
1150 elif num_lines == 1:
1151 return " " + str
1152 else:
1153 return "\n" + str
1154
1155
1156def wait_for(func, timeout, first=0.0, step=1.0, text=None):
1157 """
1158 If func() evaluates to True before timeout expires, return the
1159 value of func(). Otherwise return None.
1160
1161 @brief: Wait until func() evaluates to True.
1162
1163 @param timeout: Timeout in seconds
1164 @param first: Time to sleep before first attempt
1165 @param steps: Time to sleep between attempts in seconds
1166 @param text: Text to print while waiting, for debug purposes
1167 """
1168 start_time = time.time()
1169 end_time = time.time() + timeout
1170
1171 time.sleep(first)
1172
1173 while time.time() < end_time:
1174 if text:
Eric Li861b2d52011-02-04 14:50:35 -08001175 logging.debug("%s (%f secs)", text, (time.time() - start_time))
lmr6f669ce2009-05-31 19:02:42 +00001176
1177 output = func()
1178 if output:
1179 return output
1180
1181 time.sleep(step)
1182
lmr6f669ce2009-05-31 19:02:42 +00001183 return None
1184
1185
lmr03ba22e2009-10-23 12:07:44 +00001186def get_hash_from_file(hash_path, dvd_basename):
1187 """
1188 Get the a hash from a given DVD image from a hash file
1189 (Hash files are usually named MD5SUM or SHA1SUM and are located inside the
1190 download directories of the DVDs)
1191
1192 @param hash_path: Local path to a hash file.
1193 @param cd_image: Basename of a CD image
1194 """
1195 hash_file = open(hash_path, 'r')
1196 for line in hash_file.readlines():
1197 if dvd_basename in line:
1198 return line.split()[0]
1199
1200
Eric Lia82dc352011-02-23 13:15:52 -08001201def run_tests(parser, job):
lmr43beef12009-12-11 21:03:36 +00001202 """
1203 Runs the sequence of KVM tests based on the list of dictionaries
1204 generated by the configuration system, handling dependencies.
1205
Eric Lia82dc352011-02-23 13:15:52 -08001206 @param parser: Config parser object.
lmr43beef12009-12-11 21:03:36 +00001207 @param job: Autotest job object.
1208
1209 @return: True, if all tests ran passed, False if any of them failed.
1210 """
Eric Lia82dc352011-02-23 13:15:52 -08001211 for i, d in enumerate(parser.get_dicts()):
1212 logging.info("Test %4d: %s" % (i + 1, d["shortname"]))
1213
lmr43beef12009-12-11 21:03:36 +00001214 status_dict = {}
lmr43beef12009-12-11 21:03:36 +00001215 failed = False
lmr5df6eb42010-03-23 15:34:16 +00001216
Eric Lia82dc352011-02-23 13:15:52 -08001217 for dict in parser.get_dicts():
lmr43beef12009-12-11 21:03:36 +00001218 if dict.get("skip") == "yes":
1219 continue
1220 dependencies_satisfied = True
Eric Li8a12e802011-02-17 14:24:13 -08001221 for dep in dict.get("dep"):
lmr43beef12009-12-11 21:03:36 +00001222 for test_name in status_dict.keys():
1223 if not dep in test_name:
1224 continue
Eric Lid656d562011-04-20 11:48:29 -07001225 # So the only really non-fatal state is WARN,
1226 # All the others make it not safe to proceed with dependency
1227 # execution
1228 if status_dict[test_name] not in ['GOOD', 'WARN']:
lmr43beef12009-12-11 21:03:36 +00001229 dependencies_satisfied = False
1230 break
Eric Lid656d562011-04-20 11:48:29 -07001231 test_iterations = int(dict.get("iterations", 1))
1232 test_tag = dict.get("shortname")
1233
lmr43beef12009-12-11 21:03:36 +00001234 if dependencies_satisfied:
lmrd960a722010-04-01 02:32:59 +00001235 # Setting up profilers during test execution.
1236 profilers = dict.get("profilers", "").split()
1237 for profiler in profilers:
1238 job.profilers.add(profiler)
lmr7da99b22010-01-12 22:30:28 +00001239 # We need only one execution, profiled, hence we're passing
1240 # the profile_only parameter to job.run_test().
Eric Lid656d562011-04-20 11:48:29 -07001241 profile_only = bool(profilers) or None
Eric Li0a993912011-05-17 12:56:25 -07001242 current_status = job.run_test_detail(dict.get("vm_type"),
1243 params=dict,
Eric Lid656d562011-04-20 11:48:29 -07001244 tag=test_tag,
1245 iterations=test_iterations,
1246 profile_only=profile_only)
lmrd960a722010-04-01 02:32:59 +00001247 for profiler in profilers:
1248 job.profilers.delete(profiler)
lmr43beef12009-12-11 21:03:36 +00001249 else:
Eric Lid656d562011-04-20 11:48:29 -07001250 # We will force the test to fail as TestNA during preprocessing
1251 dict['dependency_failed'] = 'yes'
Eric Li0a993912011-05-17 12:56:25 -07001252 current_status = job.run_test_detail(dict.get("vm_type"),
1253 params=dict,
Eric Lid656d562011-04-20 11:48:29 -07001254 tag=test_tag,
1255 iterations=test_iterations)
1256
1257 if not current_status:
1258 failed = True
lmr43beef12009-12-11 21:03:36 +00001259 status_dict[dict.get("name")] = current_status
1260
1261 return not failed
1262
1263
Eric Lid656d562011-04-20 11:48:29 -07001264def display_attributes(instance):
1265 """
1266 Inspects a given class instance attributes and displays them, convenient
1267 for debugging.
1268 """
1269 logging.debug("Attributes set:")
1270 for member in inspect.getmembers(instance):
1271 name, value = member
1272 attribute = getattr(instance, name)
1273 if not (name.startswith("__") or callable(attribute) or not value):
1274 logging.debug(" %s: %s", name, value)
1275
1276
lmr31af3a12010-01-18 16:46:52 +00001277def get_full_pci_id(pci_id):
1278 """
1279 Get full PCI ID of pci_id.
1280
1281 @param pci_id: PCI ID of a device.
1282 """
1283 cmd = "lspci -D | awk '/%s/ {print $1}'" % pci_id
1284 status, full_id = commands.getstatusoutput(cmd)
1285 if status != 0:
1286 return None
1287 return full_id
1288
1289
1290def get_vendor_from_pci_id(pci_id):
1291 """
1292 Check out the device vendor ID according to pci_id.
1293
1294 @param pci_id: PCI ID of a device.
1295 """
1296 cmd = "lspci -n | awk '/%s/ {print $3}'" % pci_id
1297 return re.sub(":", " ", commands.getoutput(cmd))
1298
1299
Dale Curtis456d3c12011-07-19 11:42:51 -07001300def get_cpu_flags():
1301 """
1302 Returns a list of the CPU flags
1303 """
1304 flags_re = re.compile(r'^flags\s*:(.*)')
1305 for line in open('/proc/cpuinfo').readlines():
1306 match = flags_re.match(line)
1307 if match:
1308 return match.groups()[0].split()
1309 return []
1310
1311
1312def get_cpu_vendor(cpu_flags=[], verbose=True):
1313 """
1314 Returns the name of the CPU vendor, either intel, amd or unknown
1315 """
1316 if not cpu_flags:
1317 cpu_flags = get_cpu_flags()
1318
1319 if 'vmx' in cpu_flags:
1320 vendor = 'intel'
1321 elif 'svm' in cpu_flags:
1322 vendor = 'amd'
1323 else:
1324 vendor = 'unknown'
1325
1326 if verbose:
1327 logging.debug("Detected CPU vendor as '%s'", vendor)
1328 return vendor
1329
1330
1331def get_archive_tarball_name(source_dir, tarball_name, compression):
1332 '''
1333 Get the name for a tarball file, based on source, name and compression
1334 '''
1335 if tarball_name is None:
1336 tarball_name = os.path.basename(source_dir)
1337
1338 if not tarball_name.endswith('.tar'):
1339 tarball_name = '%s.tar' % tarball_name
1340
1341 if compression and not tarball_name.endswith('.%s' % compression):
1342 tarball_name = '%s.%s' % (tarball_name, compression)
1343
1344 return tarball_name
1345
1346
1347def archive_as_tarball(source_dir, dest_dir, tarball_name=None,
1348 compression='bz2', verbose=True):
1349 '''
1350 Saves the given source directory to the given destination as a tarball
1351
1352 If the name of the archive is omitted, it will be taken from the
1353 source_dir. If it is an absolute path, dest_dir will be ignored. But,
1354 if both the destination directory and tarball anem is given, and the
1355 latter is not an absolute path, they will be combined.
1356
1357 For archiving directory '/tmp' in '/net/server/backup' as file
1358 'tmp.tar.bz2', simply use:
1359
1360 >>> virt_utils.archive_as_tarball('/tmp', '/net/server/backup')
1361
1362 To save the file it with a different name, say 'host1-tmp.tar.bz2'
1363 and save it under '/net/server/backup', use:
1364
1365 >>> virt_utils.archive_as_tarball('/tmp', '/net/server/backup',
1366 'host1-tmp')
1367
1368 To save with gzip compression instead (resulting in the file
1369 '/net/server/backup/host1-tmp.tar.gz'), use:
1370
1371 >>> virt_utils.archive_as_tarball('/tmp', '/net/server/backup',
1372 'host1-tmp', 'gz')
1373 '''
1374 tarball_name = get_archive_tarball_name(source_dir,
1375 tarball_name,
1376 compression)
1377 if not os.path.isabs(tarball_name):
1378 tarball_path = os.path.join(dest_dir, tarball_name)
1379 else:
1380 tarball_path = tarball_name
1381
1382 if verbose:
1383 logging.debug('Archiving %s as %s' % (source_dir,
1384 tarball_path))
1385
1386 os.chdir(os.path.dirname(source_dir))
1387 tarball = tarfile.TarFile(name=tarball_path, mode='w')
1388 tarball = tarball.open(name=tarball_path, mode='w:%s' % compression)
1389 tarball.add(os.path.basename(source_dir))
1390 tarball.close()
1391
1392
Eric Li861b2d52011-02-04 14:50:35 -08001393class Thread(threading.Thread):
1394 """
1395 Run a function in a background thread.
1396 """
1397 def __init__(self, target, args=(), kwargs={}):
1398 """
1399 Initialize the instance.
1400
1401 @param target: Function to run in the thread.
1402 @param args: Arguments to pass to target.
1403 @param kwargs: Keyword arguments to pass to target.
1404 """
1405 threading.Thread.__init__(self)
1406 self._target = target
1407 self._args = args
1408 self._kwargs = kwargs
1409
1410
1411 def run(self):
1412 """
1413 Run target (passed to the constructor). No point in calling this
1414 function directly. Call start() to make this function run in a new
1415 thread.
1416 """
1417 self._e = None
1418 self._retval = None
1419 try:
1420 try:
1421 self._retval = self._target(*self._args, **self._kwargs)
1422 except:
1423 self._e = sys.exc_info()
1424 raise
1425 finally:
1426 # Avoid circular references (start() may be called only once so
1427 # it's OK to delete these)
1428 del self._target, self._args, self._kwargs
1429
1430
1431 def join(self, timeout=None, suppress_exception=False):
1432 """
1433 Join the thread. If target raised an exception, re-raise it.
1434 Otherwise, return the value returned by target.
1435
1436 @param timeout: Timeout value to pass to threading.Thread.join().
1437 @param suppress_exception: If True, don't re-raise the exception.
1438 """
1439 threading.Thread.join(self, timeout)
1440 try:
1441 if self._e:
1442 if not suppress_exception:
1443 # Because the exception was raised in another thread, we
1444 # need to explicitly insert the current context into it
1445 s = error.exception_context(self._e[1])
1446 s = error.join_contexts(error.get_context(), s)
1447 error.set_exception_context(self._e[1], s)
1448 raise self._e[0], self._e[1], self._e[2]
1449 else:
1450 return self._retval
1451 finally:
1452 # Avoid circular references (join() may be called multiple times
1453 # so we can't delete these)
1454 self._e = None
1455 self._retval = None
1456
1457
1458def parallel(targets):
1459 """
1460 Run multiple functions in parallel.
1461
1462 @param targets: A sequence of tuples or functions. If it's a sequence of
1463 tuples, each tuple will be interpreted as (target, args, kwargs) or
1464 (target, args) or (target,) depending on its length. If it's a
1465 sequence of functions, the functions will be called without
1466 arguments.
1467 @return: A list of the values returned by the functions called.
1468 """
1469 threads = []
1470 for target in targets:
1471 if isinstance(target, tuple) or isinstance(target, list):
1472 t = Thread(*target)
1473 else:
1474 t = Thread(target)
1475 threads.append(t)
1476 t.start()
1477 return [t.join() for t in threads]
1478
1479
Eric Li22434d42011-04-28 15:17:19 -07001480class VirtLoggingConfig(logging_config.LoggingConfig):
lmr84154412010-02-03 18:34:43 +00001481 """
1482 Used with the sole purpose of providing convenient logging setup
1483 for the KVM test auxiliary programs.
1484 """
1485 def configure_logging(self, results_dir=None, verbose=False):
Eric Li22434d42011-04-28 15:17:19 -07001486 super(VirtLoggingConfig, self).configure_logging(use_console=True,
1487 verbose=verbose)
lmr84154412010-02-03 18:34:43 +00001488
1489
lmr31af3a12010-01-18 16:46:52 +00001490class PciAssignable(object):
1491 """
1492 Request PCI assignable devices on host. It will check whether to request
1493 PF (physical Functions) or VF (Virtual Functions).
1494 """
lmr83d3b1a2010-01-19 08:14:36 +00001495 def __init__(self, type="vf", driver=None, driver_option=None,
lmr31af3a12010-01-18 16:46:52 +00001496 names=None, devices_requested=None):
1497 """
1498 Initialize parameter 'type' which could be:
lmr83d3b1a2010-01-19 08:14:36 +00001499 vf: Virtual Functions
1500 pf: Physical Function (actual hardware)
lmr31af3a12010-01-18 16:46:52 +00001501 mixed: Both includes VFs and PFs
1502
1503 If pass through Physical NIC cards, we need to specify which devices
1504 to be assigned, e.g. 'eth1 eth2'.
1505
1506 If pass through Virtual Functions, we need to specify how many vfs
1507 are going to be assigned, e.g. passthrough_count = 8 and max_vfs in
1508 config file.
1509
1510 @param type: PCI device type.
1511 @param driver: Kernel module for the PCI assignable device.
1512 @param driver_option: Module option to specify the maximum number of
1513 VFs (eg 'max_vfs=7')
1514 @param names: Physical NIC cards correspondent network interfaces,
1515 e.g.'eth1 eth2 ...'
1516 @param devices_requested: Number of devices being requested.
1517 """
1518 self.type = type
1519 self.driver = driver
1520 self.driver_option = driver_option
1521 if names:
1522 self.name_list = names.split()
1523 if devices_requested:
1524 self.devices_requested = int(devices_requested)
1525 else:
1526 self.devices_requested = None
1527
1528
1529 def _get_pf_pci_id(self, name, search_str):
1530 """
1531 Get the PF PCI ID according to name.
1532
1533 @param name: Name of the PCI device.
1534 @param search_str: Search string to be used on lspci.
1535 """
1536 cmd = "ethtool -i %s | awk '/bus-info/ {print $2}'" % name
1537 s, pci_id = commands.getstatusoutput(cmd)
1538 if not (s or "Cannot get driver information" in pci_id):
1539 return pci_id[5:]
1540 cmd = "lspci | awk '/%s/ {print $1}'" % search_str
1541 pci_ids = [id for id in commands.getoutput(cmd).splitlines()]
1542 nic_id = int(re.search('[0-9]+', name).group(0))
1543 if (len(pci_ids) - 1) < nic_id:
1544 return None
1545 return pci_ids[nic_id]
1546
1547
1548 def _release_dev(self, pci_id):
1549 """
1550 Release a single PCI device.
1551
1552 @param pci_id: PCI ID of a given PCI device.
1553 """
1554 base_dir = "/sys/bus/pci"
1555 full_id = get_full_pci_id(pci_id)
1556 vendor_id = get_vendor_from_pci_id(pci_id)
1557 drv_path = os.path.join(base_dir, "devices/%s/driver" % full_id)
1558 if 'pci-stub' in os.readlink(drv_path):
1559 cmd = "echo '%s' > %s/new_id" % (vendor_id, drv_path)
1560 if os.system(cmd):
1561 return False
1562
1563 stub_path = os.path.join(base_dir, "drivers/pci-stub")
1564 cmd = "echo '%s' > %s/unbind" % (full_id, stub_path)
1565 if os.system(cmd):
1566 return False
1567
1568 driver = self.dev_drivers[pci_id]
1569 cmd = "echo '%s' > %s/bind" % (full_id, driver)
1570 if os.system(cmd):
1571 return False
1572
1573 return True
1574
1575
1576 def get_vf_devs(self):
1577 """
1578 Catch all VFs PCI IDs.
1579
1580 @return: List with all PCI IDs for the Virtual Functions avaliable
1581 """
1582 if not self.sr_iov_setup():
1583 return []
1584
1585 cmd = "lspci | awk '/Virtual Function/ {print $1}'"
1586 return commands.getoutput(cmd).split()
1587
1588
1589 def get_pf_devs(self):
1590 """
1591 Catch all PFs PCI IDs.
1592
1593 @return: List with all PCI IDs for the physical hardware requested
1594 """
1595 pf_ids = []
1596 for name in self.name_list:
1597 pf_id = self._get_pf_pci_id(name, "Ethernet")
1598 if not pf_id:
1599 continue
1600 pf_ids.append(pf_id)
1601 return pf_ids
1602
1603
1604 def get_devs(self, count):
1605 """
1606 Check out all devices' PCI IDs according to their name.
1607
1608 @param count: count number of PCI devices needed for pass through
1609 @return: a list of all devices' PCI IDs
1610 """
lmr83d3b1a2010-01-19 08:14:36 +00001611 if self.type == "vf":
lmr31af3a12010-01-18 16:46:52 +00001612 vf_ids = self.get_vf_devs()
lmr83d3b1a2010-01-19 08:14:36 +00001613 elif self.type == "pf":
lmr31af3a12010-01-18 16:46:52 +00001614 vf_ids = self.get_pf_devs()
1615 elif self.type == "mixed":
1616 vf_ids = self.get_vf_devs()
1617 vf_ids.extend(self.get_pf_devs())
1618 return vf_ids[0:count]
1619
1620
1621 def get_vfs_count(self):
1622 """
1623 Get VFs count number according to lspci.
1624 """
lmr224a5412010-03-17 12:00:36 +00001625 # FIXME: Need to think out a method of identify which
1626 # 'virtual function' belongs to which physical card considering
1627 # that if the host has more than one 82576 card. PCI_ID?
lmr31af3a12010-01-18 16:46:52 +00001628 cmd = "lspci | grep 'Virtual Function' | wc -l"
lmr224a5412010-03-17 12:00:36 +00001629 return int(commands.getoutput(cmd))
lmr31af3a12010-01-18 16:46:52 +00001630
1631
1632 def check_vfs_count(self):
1633 """
1634 Check VFs count number according to the parameter driver_options.
1635 """
lmr224a5412010-03-17 12:00:36 +00001636 # Network card 82576 has two network interfaces and each can be
1637 # virtualized up to 7 virtual functions, therefore we multiply
1638 # two for the value of driver_option 'max_vfs'.
1639 expected_count = int((re.findall("(\d)", self.driver_option)[0])) * 2
1640 return (self.get_vfs_count == expected_count)
lmr31af3a12010-01-18 16:46:52 +00001641
1642
1643 def is_binded_to_stub(self, full_id):
1644 """
1645 Verify whether the device with full_id is already binded to pci-stub.
1646
1647 @param full_id: Full ID for the given PCI device
1648 """
1649 base_dir = "/sys/bus/pci"
1650 stub_path = os.path.join(base_dir, "drivers/pci-stub")
1651 if os.path.exists(os.path.join(stub_path, full_id)):
1652 return True
1653 return False
1654
1655
1656 def sr_iov_setup(self):
1657 """
1658 Ensure the PCI device is working in sr_iov mode.
1659
1660 Check if the PCI hardware device drive is loaded with the appropriate,
1661 parameters (number of VFs), and if it's not, perform setup.
1662
1663 @return: True, if the setup was completed successfuly, False otherwise.
1664 """
1665 re_probe = False
1666 s, o = commands.getstatusoutput('lsmod | grep %s' % self.driver)
1667 if s:
1668 re_probe = True
1669 elif not self.check_vfs_count():
1670 os.system("modprobe -r %s" % self.driver)
1671 re_probe = True
lmr224a5412010-03-17 12:00:36 +00001672 else:
1673 return True
lmr31af3a12010-01-18 16:46:52 +00001674
1675 # Re-probe driver with proper number of VFs
1676 if re_probe:
1677 cmd = "modprobe %s %s" % (self.driver, self.driver_option)
Eric Li861b2d52011-02-04 14:50:35 -08001678 logging.info("Loading the driver '%s' with option '%s'",
1679 self.driver, self.driver_option)
lmr31af3a12010-01-18 16:46:52 +00001680 s, o = commands.getstatusoutput(cmd)
1681 if s:
1682 return False
lmr31af3a12010-01-18 16:46:52 +00001683 return True
1684
1685
1686 def request_devs(self):
1687 """
1688 Implement setup process: unbind the PCI device and then bind it
1689 to the pci-stub driver.
1690
1691 @return: a list of successfully requested devices' PCI IDs.
1692 """
1693 base_dir = "/sys/bus/pci"
1694 stub_path = os.path.join(base_dir, "drivers/pci-stub")
1695
1696 self.pci_ids = self.get_devs(self.devices_requested)
lmrbc72f082010-02-08 10:02:40 +00001697 logging.debug("The following pci_ids were found: %s", self.pci_ids)
lmr31af3a12010-01-18 16:46:52 +00001698 requested_pci_ids = []
1699 self.dev_drivers = {}
1700
1701 # Setup all devices specified for assignment to guest
1702 for pci_id in self.pci_ids:
1703 full_id = get_full_pci_id(pci_id)
1704 if not full_id:
1705 continue
1706 drv_path = os.path.join(base_dir, "devices/%s/driver" % full_id)
Eric Li861b2d52011-02-04 14:50:35 -08001707 dev_prev_driver = os.path.realpath(os.path.join(drv_path,
1708 os.readlink(drv_path)))
lmr31af3a12010-01-18 16:46:52 +00001709 self.dev_drivers[pci_id] = dev_prev_driver
1710
1711 # Judge whether the device driver has been binded to stub
1712 if not self.is_binded_to_stub(full_id):
lmrbc72f082010-02-08 10:02:40 +00001713 logging.debug("Binding device %s to stub", full_id)
lmr31af3a12010-01-18 16:46:52 +00001714 vendor_id = get_vendor_from_pci_id(pci_id)
1715 stub_new_id = os.path.join(stub_path, 'new_id')
1716 unbind_dev = os.path.join(drv_path, 'unbind')
1717 stub_bind = os.path.join(stub_path, 'bind')
1718
1719 info_write_to_files = [(vendor_id, stub_new_id),
1720 (full_id, unbind_dev),
1721 (full_id, stub_bind)]
1722
1723 for content, file in info_write_to_files:
1724 try:
lmrbc72f082010-02-08 10:02:40 +00001725 utils.open_write_close(file, content)
lmr31af3a12010-01-18 16:46:52 +00001726 except IOError:
lmrbc72f082010-02-08 10:02:40 +00001727 logging.debug("Failed to write %s to file %s", content,
1728 file)
lmr31af3a12010-01-18 16:46:52 +00001729 continue
1730
1731 if not self.is_binded_to_stub(full_id):
lmrbc72f082010-02-08 10:02:40 +00001732 logging.error("Binding device %s to stub failed", pci_id)
lmr31af3a12010-01-18 16:46:52 +00001733 continue
1734 else:
lmrbc72f082010-02-08 10:02:40 +00001735 logging.debug("Device %s already binded to stub", pci_id)
lmr31af3a12010-01-18 16:46:52 +00001736 requested_pci_ids.append(pci_id)
1737 self.pci_ids = requested_pci_ids
1738 return self.pci_ids
1739
1740
1741 def release_devs(self):
1742 """
1743 Release all PCI devices currently assigned to VMs back to the
1744 virtualization host.
1745 """
1746 try:
1747 for pci_id in self.dev_drivers:
1748 if not self._release_dev(pci_id):
lmrbc72f082010-02-08 10:02:40 +00001749 logging.error("Failed to release device %s to host", pci_id)
lmr31af3a12010-01-18 16:46:52 +00001750 else:
lmrbc72f082010-02-08 10:02:40 +00001751 logging.info("Released device %s successfully", pci_id)
lmr31af3a12010-01-18 16:46:52 +00001752 except:
1753 return
Eric Lie0493a42010-11-15 13:05:43 -08001754
1755
Eric Lid656d562011-04-20 11:48:29 -07001756class KojiClient(object):
Eric Lie0493a42010-11-15 13:05:43 -08001757 """
Eric Lid656d562011-04-20 11:48:29 -07001758 Stablishes a connection with the build system, either koji or brew.
Eric Lie0493a42010-11-15 13:05:43 -08001759
Eric Lid656d562011-04-20 11:48:29 -07001760 This class provides convenience methods to retrieve information on packages
1761 and the packages themselves hosted on the build system. Packages should be
1762 specified in the KojiPgkSpec syntax.
Eric Lie0493a42010-11-15 13:05:43 -08001763 """
Eric Lid656d562011-04-20 11:48:29 -07001764
1765 CMD_LOOKUP_ORDER = ['/usr/bin/brew', '/usr/bin/koji' ]
1766
1767 CONFIG_MAP = {'/usr/bin/brew': '/etc/brewkoji.conf',
1768 '/usr/bin/koji': '/etc/koji.conf'}
1769
1770
1771 def __init__(self, cmd=None):
Eric Lie0493a42010-11-15 13:05:43 -08001772 """
1773 Verifies whether the system has koji or brew installed, then loads
1774 the configuration file that will be used to download the files.
1775
Eric Lid656d562011-04-20 11:48:29 -07001776 @type cmd: string
1777 @param cmd: Optional command name, either 'brew' or 'koji'. If not
1778 set, get_default_command() is used and to look for
1779 one of them.
1780 @raise: ValueError
Eric Lie0493a42010-11-15 13:05:43 -08001781 """
1782 if not KOJI_INSTALLED:
1783 raise ValueError('No koji/brew installed on the machine')
1784
Eric Lid656d562011-04-20 11:48:29 -07001785 # Instance variables used by many methods
1786 self.command = None
1787 self.config = None
1788 self.config_options = {}
1789 self.session = None
1790
1791 # Set koji command or get default
1792 if cmd is None:
1793 self.command = self.get_default_command()
Eric Lie0493a42010-11-15 13:05:43 -08001794 else:
Eric Lid656d562011-04-20 11:48:29 -07001795 self.command = cmd
Eric Lie0493a42010-11-15 13:05:43 -08001796
Eric Lid656d562011-04-20 11:48:29 -07001797 # Check koji command
1798 if not self.is_command_valid():
1799 raise ValueError('Koji command "%s" is not valid' % self.command)
Eric Lie0493a42010-11-15 13:05:43 -08001800
Eric Lid656d562011-04-20 11:48:29 -07001801 # Assuming command is valid, set configuration file and read it
1802 self.config = self.CONFIG_MAP[self.command]
1803 self.read_config()
Eric Lie0493a42010-11-15 13:05:43 -08001804
Eric Lid656d562011-04-20 11:48:29 -07001805 # Setup koji session
1806 server_url = self.config_options['server']
1807 session_options = self.get_session_options()
1808 self.session = koji.ClientSession(server_url,
1809 session_options)
Eric Lie0493a42010-11-15 13:05:43 -08001810
1811
Eric Lid656d562011-04-20 11:48:29 -07001812 def read_config(self, check_is_valid=True):
1813 '''
1814 Reads options from the Koji configuration file
Eric Lie0493a42010-11-15 13:05:43 -08001815
Eric Lid656d562011-04-20 11:48:29 -07001816 By default it checks if the koji configuration is valid
Eric Lie0493a42010-11-15 13:05:43 -08001817
Eric Lid656d562011-04-20 11:48:29 -07001818 @type check_valid: boolean
1819 @param check_valid: whether to include a check on the configuration
1820 @raises: ValueError
1821 @returns: None
1822 '''
1823 if check_is_valid:
1824 if not self.is_config_valid():
1825 raise ValueError('Koji config "%s" is not valid' % self.config)
Eric Lie0493a42010-11-15 13:05:43 -08001826
Eric Lid656d562011-04-20 11:48:29 -07001827 config = ConfigParser.ConfigParser()
1828 config.read(self.config)
Eric Lie0493a42010-11-15 13:05:43 -08001829
Eric Lid656d562011-04-20 11:48:29 -07001830 basename = os.path.basename(self.command)
1831 for name, value in config.items(basename):
1832 self.config_options[name] = value
Eric Lie0493a42010-11-15 13:05:43 -08001833
Eric Lie0493a42010-11-15 13:05:43 -08001834
Eric Lid656d562011-04-20 11:48:29 -07001835 def get_session_options(self):
1836 '''
1837 Filter only options necessary for setting up a cobbler client session
Eric Lie0493a42010-11-15 13:05:43 -08001838
Eric Lid656d562011-04-20 11:48:29 -07001839 @returns: only the options used for session setup
1840 '''
1841 session_options = {}
1842 for name, value in self.config_options.items():
1843 if name in ('user', 'password', 'debug_xmlrpc', 'debug'):
1844 session_options[name] = value
1845 return session_options
Eric Lie0493a42010-11-15 13:05:43 -08001846
Eric Lid656d562011-04-20 11:48:29 -07001847
1848 def is_command_valid(self):
1849 '''
1850 Checks if the currently set koji command is valid
1851
1852 @returns: True or False
1853 '''
1854 koji_command_ok = True
1855
1856 if not os.path.isfile(self.command):
1857 logging.error('Koji command "%s" is not a regular file',
1858 self.command)
1859 koji_command_ok = False
1860
1861 if not os.access(self.command, os.X_OK):
1862 logging.warn('Koji command "%s" is not executable: this is '
1863 'not fatal but indicates an unexpected situation',
1864 self.command)
1865
1866 if not self.command in self.CONFIG_MAP.keys():
1867 logging.error('Koji command "%s" does not have a configuration '
1868 'file associated to it', self.command)
1869 koji_command_ok = False
1870
1871 return koji_command_ok
1872
1873
1874 def is_config_valid(self):
1875 '''
1876 Checks if the currently set koji configuration is valid
1877
1878 @returns: True or False
1879 '''
1880 koji_config_ok = True
1881
1882 if not os.path.isfile(self.config):
1883 logging.error('Koji config "%s" is not a regular file', self.config)
1884 koji_config_ok = False
1885
1886 if not os.access(self.config, os.R_OK):
1887 logging.error('Koji config "%s" is not readable', self.config)
1888 koji_config_ok = False
1889
1890 config = ConfigParser.ConfigParser()
1891 config.read(self.config)
1892 basename = os.path.basename(self.command)
1893 if not config.has_section(basename):
1894 logging.error('Koji configuration file "%s" does not have a '
1895 'section "%s", named after the base name of the '
1896 'currently set koji command "%s"', self.config,
1897 basename, self.command)
1898 koji_config_ok = False
1899
1900 return koji_config_ok
1901
1902
1903 def get_default_command(self):
1904 '''
1905 Looks up for koji or brew "binaries" on the system
1906
1907 Systems with plain koji usually don't have a brew cmd, while systems
1908 with koji, have *both* koji and brew utilities. So we look for brew
1909 first, and if found, we consider that the system is configured for
1910 brew. If not, we consider this is a system with plain koji.
1911
1912 @returns: either koji or brew command line executable path, or None
1913 '''
1914 koji_command = None
1915 for command in self.CMD_LOOKUP_ORDER:
1916 if os.path.isfile(command):
1917 koji_command = command
1918 break
1919 else:
1920 koji_command_basename = os.path.basename(koji_command)
1921 try:
1922 koji_command = os_dep.command(koji_command_basename)
1923 break
1924 except ValueError:
1925 pass
1926 return koji_command
1927
1928
1929 def get_pkg_info(self, pkg):
1930 '''
1931 Returns information from Koji on the package
1932
1933 @type pkg: KojiPkgSpec
1934 @param pkg: information about the package, as a KojiPkgSpec instance
1935
1936 @returns: information from Koji about the specified package
1937 '''
1938 info = {}
1939 if pkg.build is not None:
1940 info = self.session.getBuild(int(pkg.build))
1941 elif pkg.tag is not None and pkg.package is not None:
1942 builds = self.session.listTagged(pkg.tag,
1943 latest=True,
1944 inherit=True,
1945 package=pkg.package)
1946 if builds:
1947 info = builds[0]
1948 return info
1949
1950
1951 def is_pkg_valid(self, pkg):
1952 '''
1953 Checks if this package is altogether valid on Koji
1954
1955 This verifies if the build or tag specified in the package
1956 specification actually exist on the Koji server
1957
1958 @returns: True or False
1959 '''
1960 valid = True
Dale Curtis74a314b2011-06-23 14:55:46 -07001961 if pkg.build:
1962 if not self.is_pkg_spec_build_valid(pkg):
1963 valid = False
1964 elif pkg.tag:
1965 if not self.is_pkg_spec_tag_valid(pkg):
1966 valid = False
1967 else:
Eric Lid656d562011-04-20 11:48:29 -07001968 valid = False
1969 return valid
1970
1971
1972 def is_pkg_spec_build_valid(self, pkg):
1973 '''
1974 Checks if build is valid on Koji
1975
1976 @param pkg: a Pkg instance
1977 '''
1978 if pkg.build is not None:
1979 info = self.session.getBuild(int(pkg.build))
1980 if info:
1981 return True
1982 return False
1983
1984
1985 def is_pkg_spec_tag_valid(self, pkg):
1986 '''
1987 Checks if tag is valid on Koji
1988
1989 @type pkg: KojiPkgSpec
1990 @param pkg: a package specification
1991 '''
1992 if pkg.tag is not None:
1993 tag = self.session.getTag(pkg.tag)
1994 if tag:
1995 return True
1996 return False
1997
1998
1999 def get_pkg_rpm_info(self, pkg, arch=None):
2000 '''
2001 Returns a list of infomation on the RPM packages found on koji
2002
2003 @type pkg: KojiPkgSpec
2004 @param pkg: a package specification
2005 @type arch: string
2006 @param arch: packages built for this architecture, but also including
2007 architecture independent (noarch) packages
2008 '''
Eric Lie0493a42010-11-15 13:05:43 -08002009 if arch is None:
2010 arch = utils.get_arch()
Eric Lid656d562011-04-20 11:48:29 -07002011 rpms = []
2012 info = self.get_pkg_info(pkg)
2013 if info:
2014 rpms = self.session.listRPMs(buildID=info['id'],
2015 arches=[arch, 'noarch'])
2016 if pkg.subpackages:
2017 rpms = [d for d in rpms if d['name'] in pkg.subpackages]
2018 return rpms
Eric Lie0493a42010-11-15 13:05:43 -08002019
Eric Lie0493a42010-11-15 13:05:43 -08002020
Eric Lid656d562011-04-20 11:48:29 -07002021 def get_pkg_rpm_names(self, pkg, arch=None):
2022 '''
2023 Gets the names for the RPM packages specified in pkg
2024
2025 @type pkg: KojiPkgSpec
2026 @param pkg: a package specification
2027 @type arch: string
2028 @param arch: packages built for this architecture, but also including
2029 architecture independent (noarch) packages
2030 '''
2031 if arch is None:
2032 arch = utils.get_arch()
2033 rpms = self.get_pkg_rpm_info(pkg, arch)
2034 return [rpm['name'] for rpm in rpms]
2035
2036
2037 def get_pkg_rpm_file_names(self, pkg, arch=None):
2038 '''
2039 Gets the file names for the RPM packages specified in pkg
2040
2041 @type pkg: KojiPkgSpec
2042 @param pkg: a package specification
2043 @type arch: string
2044 @param arch: packages built for this architecture, but also including
2045 architecture independent (noarch) packages
2046 '''
2047 if arch is None:
2048 arch = utils.get_arch()
2049 rpm_names = []
2050 rpms = self.get_pkg_rpm_info(pkg, arch)
2051 for rpm in rpms:
2052 arch_rpm_name = koji.pathinfo.rpm(rpm)
2053 rpm_name = os.path.basename(arch_rpm_name)
2054 rpm_names.append(rpm_name)
2055 return rpm_names
2056
2057
2058 def get_pkg_urls(self, pkg, arch=None):
2059 '''
2060 Gets the urls for the packages specified in pkg
2061
2062 @type pkg: KojiPkgSpec
2063 @param pkg: a package specification
2064 @type arch: string
2065 @param arch: packages built for this architecture, but also including
2066 architecture independent (noarch) packages
2067 '''
2068 info = self.get_pkg_info(pkg)
2069 rpms = self.get_pkg_rpm_info(pkg, arch)
2070 rpm_urls = []
Eric Lie0493a42010-11-15 13:05:43 -08002071 for rpm in rpms:
2072 rpm_name = koji.pathinfo.rpm(rpm)
Eric Lid656d562011-04-20 11:48:29 -07002073 url = ("%s/%s/%s/%s/%s" % (self.config_options['pkgurl'],
Eric Lie0493a42010-11-15 13:05:43 -08002074 info['package_name'],
2075 info['version'], info['release'],
2076 rpm_name))
Eric Lid656d562011-04-20 11:48:29 -07002077 rpm_urls.append(url)
2078 return rpm_urls
2079
2080
2081 def get_pkgs(self, pkg, dst_dir, arch=None):
2082 '''
2083 Download the packages
2084
2085 @type pkg: KojiPkgSpec
2086 @param pkg: a package specification
2087 @type dst_dir: string
2088 @param dst_dir: the destination directory, where the downloaded
2089 packages will be saved on
2090 @type arch: string
2091 @param arch: packages built for this architecture, but also including
2092 architecture independent (noarch) packages
2093 '''
2094 rpm_urls = self.get_pkg_urls(pkg, arch)
2095 for url in rpm_urls:
2096 utils.get_file(url,
2097 os.path.join(dst_dir, os.path.basename(url)))
2098
2099
2100DEFAULT_KOJI_TAG = None
2101def set_default_koji_tag(tag):
2102 '''
2103 Sets the default tag that will be used
2104 '''
2105 global DEFAULT_KOJI_TAG
2106 DEFAULT_KOJI_TAG = tag
2107
2108
2109def get_default_koji_tag():
2110 return DEFAULT_KOJI_TAG
2111
2112
Dale Curtis456d3c12011-07-19 11:42:51 -07002113class KojiPkgSpec(object):
Eric Lid656d562011-04-20 11:48:29 -07002114 '''
2115 A package specification syntax parser for Koji
2116
2117 This holds information on either tag or build, and packages to be fetched
2118 from koji and possibly installed (features external do this class).
2119
2120 New objects can be created either by providing information in the textual
2121 format or by using the actual parameters for tag, build, package and sub-
2122 packages. The textual format is useful for command line interfaces and
2123 configuration files, while using parameters is better for using this in
2124 a programatic fashion.
2125
2126 The following sets of examples are interchangeable. Specifying all packages
2127 part of build number 1000:
2128
2129 >>> from kvm_utils import KojiPkgSpec
2130 >>> pkg = KojiPkgSpec('1000')
2131
2132 >>> pkg = KojiPkgSpec(build=1000)
2133
2134 Specifying only a subset of packages of build number 1000:
2135
2136 >>> pkg = KojiPkgSpec('1000:kernel,kernel-devel')
2137
2138 >>> pkg = KojiPkgSpec(build=1000,
2139 subpackages=['kernel', 'kernel-devel'])
2140
2141 Specifying the latest build for the 'kernel' package tagged with 'dist-f14':
2142
2143 >>> pkg = KojiPkgSpec('dist-f14:kernel')
2144
2145 >>> pkg = KojiPkgSpec(tag='dist-f14', package='kernel')
2146
2147 Specifying the 'kernel' package using the default tag:
2148
2149 >>> kvm_utils.set_default_koji_tag('dist-f14')
2150 >>> pkg = KojiPkgSpec('kernel')
2151
2152 >>> pkg = KojiPkgSpec(package='kernel')
2153
2154 Specifying the 'kernel' package using the default tag:
2155
2156 >>> kvm_utils.set_default_koji_tag('dist-f14')
2157 >>> pkg = KojiPkgSpec('kernel')
2158
2159 >>> pkg = KojiPkgSpec(package='kernel')
2160
2161 If you do not specify a default tag, and give a package name without an
2162 explicit tag, your package specification is considered invalid:
2163
2164 >>> print kvm_utils.get_default_koji_tag()
2165 None
2166 >>> print kvm_utils.KojiPkgSpec('kernel').is_valid()
2167 False
2168
2169 >>> print kvm_utils.KojiPkgSpec(package='kernel').is_valid()
2170 False
2171 '''
2172
2173 SEP = ':'
2174
2175 def __init__(self, text='', tag=None, build=None,
2176 package=None, subpackages=[]):
2177 '''
2178 Instantiates a new KojiPkgSpec object
2179
2180 @type text: string
2181 @param text: a textual representation of a package on Koji that
2182 will be parsed
2183 @type tag: string
2184 @param tag: a koji tag, example: Fedora-14-RELEASE
2185 (see U{http://fedoraproject.org/wiki/Koji#Tags_and_Targets})
2186 @type build: number
2187 @param build: a koji build, example: 1001
2188 (see U{http://fedoraproject.org/wiki/Koji#Koji_Architecture})
2189 @type package: string
2190 @param package: a koji package, example: python
2191 (see U{http://fedoraproject.org/wiki/Koji#Koji_Architecture})
2192 @type subpackages: list of strings
2193 @param subpackages: a list of package names, usually a subset of
2194 the RPM packages generated by a given build
2195 '''
2196
2197 # Set to None to indicate 'not set' (and be able to use 'is')
2198 self.tag = None
2199 self.build = None
2200 self.package = None
2201 self.subpackages = []
2202
2203 self.default_tag = None
2204
2205 # Textual representation takes precedence (most common use case)
2206 if text:
2207 self.parse(text)
2208 else:
2209 self.tag = tag
2210 self.build = build
2211 self.package = package
2212 self.subpackages = subpackages
2213
2214 # Set the default tag, if set, as a fallback
2215 if not self.build and not self.tag:
2216 default_tag = get_default_koji_tag()
2217 if default_tag is not None:
2218 self.tag = default_tag
2219
2220
2221 def parse(self, text):
2222 '''
2223 Parses a textual representation of a package specification
2224
2225 @type text: string
2226 @param text: textual representation of a package in koji
2227 '''
2228 parts = text.count(self.SEP) + 1
2229 if parts == 1:
2230 if text.isdigit():
2231 self.build = text
Eric Lie0493a42010-11-15 13:05:43 -08002232 else:
Eric Lid656d562011-04-20 11:48:29 -07002233 self.package = text
2234 elif parts == 2:
2235 part1, part2 = text.split(self.SEP)
2236 if part1.isdigit():
2237 self.build = part1
2238 self.subpackages = part2.split(',')
2239 else:
2240 self.tag = part1
2241 self.package = part2
2242 elif parts >= 3:
2243 # Instead of erroring on more arguments, we simply ignore them
2244 # This makes the parser suitable for future syntax additions, such
2245 # as specifying the package architecture
2246 part1, part2, part3 = text.split(self.SEP)[0:3]
2247 self.tag = part1
2248 self.package = part2
2249 self.subpackages = part3.split(',')
Eric Lie0493a42010-11-15 13:05:43 -08002250
Eric Lie0493a42010-11-15 13:05:43 -08002251
Eric Lid656d562011-04-20 11:48:29 -07002252 def _is_invalid_neither_tag_or_build(self):
2253 '''
2254 Checks if this package is invalid due to not having either a valid
2255 tag or build set, that is, both are empty.
2256
2257 @returns: True if this is invalid and False if it's valid
2258 '''
2259 return (self.tag is None and self.build is None)
2260
2261
2262 def _is_invalid_package_but_no_tag(self):
2263 '''
2264 Checks if this package is invalid due to having a package name set
2265 but tag or build set, that is, both are empty.
2266
2267 @returns: True if this is invalid and False if it's valid
2268 '''
2269 return (self.package and not self.tag)
2270
2271
2272 def _is_invalid_subpackages_but_no_main_package(self):
2273 '''
2274 Checks if this package is invalid due to having a tag set (this is Ok)
2275 but specifying subpackage names without specifying the main package
2276 name.
2277
2278 Specifying subpackages without a main package name is only valid when
2279 a build is used instead of a tag.
2280
2281 @returns: True if this is invalid and False if it's valid
2282 '''
2283 return (self.tag and self.subpackages and not self.package)
2284
2285
2286 def is_valid(self):
2287 '''
2288 Checks if this package specification is valid.
2289
2290 Being valid means that it has enough and not conflicting information.
2291 It does not validate that the packages specified actually existe on
2292 the Koji server.
2293
2294 @returns: True or False
2295 '''
2296 if self._is_invalid_neither_tag_or_build():
2297 return False
2298 elif self._is_invalid_package_but_no_tag():
2299 return False
2300 elif self._is_invalid_subpackages_but_no_main_package():
2301 return False
2302
2303 return True
2304
2305
2306 def describe_invalid(self):
2307 '''
2308 Describes why this is not valid, in a human friendly way
2309 '''
2310 if self._is_invalid_neither_tag_or_build():
2311 return 'neither a tag or build are set, and of them should be set'
2312 elif self._is_invalid_package_but_no_tag():
2313 return 'package name specified but no tag is set'
2314 elif self._is_invalid_subpackages_but_no_main_package():
2315 return 'subpackages specified but no main package is set'
2316
2317 return 'unkwown reason, seems to be valid'
2318
2319
2320 def describe(self):
2321 '''
2322 Describe this package specification, in a human friendly way
2323
2324 @returns: package specification description
2325 '''
2326 if self.is_valid():
2327 description = ''
2328 if not self.subpackages:
2329 description += 'all subpackages from %s ' % self.package
2330 else:
2331 description += ('only subpackage(s) %s from package %s ' %
2332 (', '.join(self.subpackages), self.package))
2333
2334 if self.build:
2335 description += 'from build %s' % self.build
2336 elif self.tag:
2337 description += 'tagged with %s' % self.tag
2338 else:
2339 raise ValueError, 'neither build or tag is set'
2340
2341 return description
2342 else:
2343 return ('Invalid package specification: %s' %
2344 self.describe_invalid())
2345
2346
2347 def __repr__(self):
2348 return ("<KojiPkgSpec tag=%s build=%s pkg=%s subpkgs=%s>" %
2349 (self.tag, self.build, self.package,
2350 ", ".join(self.subpackages)))
Eric Li861b2d52011-02-04 14:50:35 -08002351
2352
2353def umount(src, mount_point, type):
2354 """
2355 Umount the src mounted in mount_point.
2356
2357 @src: mount source
2358 @mount_point: mount point
2359 @type: file system type
2360 """
2361
2362 mount_string = "%s %s %s" % (src, mount_point, type)
2363 if mount_string in file("/etc/mtab").read():
2364 umount_cmd = "umount %s" % mount_point
2365 try:
2366 utils.system(umount_cmd)
2367 return True
2368 except error.CmdError:
2369 return False
2370 else:
2371 logging.debug("%s is not mounted under %s", src, mount_point)
2372 return True
2373
2374
2375def mount(src, mount_point, type, perm="rw"):
2376 """
2377 Mount the src into mount_point of the host.
2378
2379 @src: mount source
2380 @mount_point: mount point
2381 @type: file system type
2382 @perm: mount premission
2383 """
2384 umount(src, mount_point, type)
2385 mount_string = "%s %s %s %s" % (src, mount_point, type, perm)
2386
2387 if mount_string in file("/etc/mtab").read():
2388 logging.debug("%s is already mounted in %s with %s",
2389 src, mount_point, perm)
2390 return True
2391
2392 mount_cmd = "mount -t %s %s %s -o %s" % (type, src, mount_point, perm)
2393 try:
2394 utils.system(mount_cmd)
2395 except error.CmdError:
2396 return False
2397
2398 logging.debug("Verify the mount through /etc/mtab")
2399 if mount_string in file("/etc/mtab").read():
2400 logging.debug("%s is successfully mounted", src)
2401 return True
2402 else:
2403 logging.error("Can't find mounted NFS share - /etc/mtab contents \n%s",
2404 file("/etc/mtab").read())
2405 return False
Eric Lid656d562011-04-20 11:48:29 -07002406
2407
Dale Curtis456d3c12011-07-19 11:42:51 -07002408class GitRepoHelper(object):
2409 '''
2410 Helps to deal with git repos, mostly fetching content from a repo
2411 '''
2412 def __init__(self, uri, branch, destination_dir, commit=None, lbranch=None):
2413 '''
2414 Instantiates a new GitRepoHelper
2415
2416 @type uri: string
2417 @param uri: git repository url
2418 @type branch: string
2419 @param branch: git remote branch
2420 @type destination_dir: string
2421 @param destination_dir: path of a dir where to save downloaded code
2422 @type commit: string
2423 @param commit: specific commit to download
2424 @type lbranch: string
2425 @param lbranch: git local branch name, if different from remote
2426 '''
2427 self.uri = uri
2428 self.branch = branch
2429 self.destination_dir = destination_dir
2430 self.commit = commit
2431 if lbranch is None:
2432 self.lbranch = branch
2433
2434
2435 def init(self):
2436 '''
2437 Initializes a directory for receiving a verbatim copy of git repo
2438
2439 This creates a directory if necessary, and either resets or inits
2440 the repo
2441 '''
2442 if not os.path.exists(self.destination_dir):
2443 logging.debug('Creating directory %s for git repo %s',
2444 self.destination_dir, self.uri)
2445 os.makedirs(self.destination_dir)
2446
2447 os.chdir(self.destination_dir)
2448
2449 if os.path.exists('.git'):
2450 logging.debug('Resetting previously existing git repo at %s for '
2451 'receiving git repo %s',
2452 self.destination_dir, self.uri)
2453 utils.system('git reset --hard')
2454 else:
2455 logging.debug('Initializing new git repo at %s for receiving '
2456 'git repo %s',
2457 self.destination_dir, self.uri)
2458 utils.system('git init')
2459
2460
2461 def fetch(self):
2462 '''
2463 Performs a git fetch from the remote repo
2464 '''
2465 logging.info("Fetching git [REP '%s' BRANCH '%s'] -> %s",
2466 self.uri, self.branch, self.destination_dir)
2467 os.chdir(self.destination_dir)
2468 utils.system("git fetch -q -f -u -t %s %s:%s" % (self.uri,
2469 self.branch,
2470 self.lbranch))
2471
2472
2473 def checkout(self):
2474 '''
2475 Performs a git checkout for a given branch and start point (commit)
2476 '''
2477 os.chdir(self.destination_dir)
2478
2479 logging.debug('Checking out local branch %s', self.lbranch)
2480 utils.system("git checkout %s" % self.lbranch)
2481
2482 if self.commit is not None:
2483 logging.debug('Checking out commit %s', self.commit)
2484 utils.system("git checkout %s" % self.commit)
2485
2486 h = utils.system_output('git log --pretty=format:"%H" -1').strip()
2487 try:
2488 desc = "tag %s" % utils.system_output("git describe")
2489 except error.CmdError:
2490 desc = "no tag found"
2491
2492 logging.info("Commit hash for %s is %s (%s)", self.name, h, desc)
2493
2494
2495 def execute(self):
2496 '''
2497 Performs all steps necessary to initialize and download a git repo
2498
2499 This includes the init, fetch and checkout steps in one single
2500 utility method.
2501 '''
2502 self.init()
2503 self.fetch()
2504 self.checkout()
2505
2506
2507class GitRepoParamHelper(GitRepoHelper):
2508 '''
2509 Helps to deal with git repos specified in cartersian config files
2510
2511 This class attempts to make it simple to manage a git repo, by using a
2512 naming standard that follows this basic syntax:
2513
2514 <prefix>_name_<suffix>
2515
2516 <prefix> is always 'git_repo' and <suffix> sets options for this git repo.
2517 Example for repo named foo:
2518
2519 git_repo_foo_uri = git://git.foo.org/foo.git
2520 git_repo_foo_branch = master
2521 git_repo_foo_lbranch = master
2522 git_repo_foo_commit = bb5fb8e678aabe286e74c4f2993dc2a9e550b627
2523 '''
2524 def __init__(self, params, name, destination_dir):
2525 '''
2526 Instantiates a new GitRepoParamHelper
2527 '''
2528 self.params = params
2529 self.name = name
2530 self.destination_dir = destination_dir
2531 self._parse_params()
2532
2533
2534 def _parse_params(self):
2535 '''
2536 Parses the params items for entries related to this repo
2537
2538 This method currently does everything that the parent class __init__()
2539 method does, that is, sets all instance variables needed by other
2540 methods. That means it's not strictly necessary to call parent's
2541 __init__().
2542 '''
2543 config_prefix = 'git_repo_%s' % self.name
2544 logging.debug('Parsing parameters for git repo %s, configuration '
2545 'prefix is %s' % (self.name, config_prefix))
2546
2547 self.uri = self.params.get('%s_uri' % config_prefix)
2548 logging.debug('Git repo %s uri: %s' % (self.name, self.uri))
2549
2550 self.branch = self.params.get('%s_branch' % config_prefix, 'master')
2551 logging.debug('Git repo %s branch: %s' % (self.name, self.branch))
2552
2553 self.lbranch = self.params.get('%s_lbranch' % config_prefix)
2554 if self.lbranch is None:
2555 self.lbranch = self.branch
2556 logging.debug('Git repo %s lbranch: %s' % (self.name, self.lbranch))
2557
2558 self.commit = self.params.get('%s_commit' % config_prefix)
2559 if self.commit is None:
2560 logging.debug('Git repo %s commit is not set' % self.name)
2561 else:
2562 logging.debug('Git repo %s commit: %s' % (self.name, self.commit))
2563
2564
2565class LocalSourceDirHelper(object):
2566 '''
2567 Helper class to deal with source code sitting somewhere in the filesystem
2568 '''
2569 def __init__(self, source_dir, destination_dir):
2570 '''
2571 @param source_dir:
2572 @param destination_dir:
2573 @return: new LocalSourceDirHelper instance
2574 '''
2575 self.source = source_dir
2576 self.destination = destination_dir
2577
2578
2579 def execute(self):
2580 '''
2581 Copies the source directory to the destination directory
2582 '''
2583 if os.path.isdir(self.destination):
2584 shutil.rmtree(self.destination)
2585
2586 if os.path.isdir(self.source):
2587 shutil.copytree(self.source, self.destination)
2588
2589
2590class LocalSourceDirParamHelper(LocalSourceDirHelper):
2591 '''
2592 Helps to deal with source dirs specified in cartersian config files
2593
2594 This class attempts to make it simple to manage a source dir, by using a
2595 naming standard that follows this basic syntax:
2596
2597 <prefix>_name_<suffix>
2598
2599 <prefix> is always 'local_src' and <suffix> sets options for this source
2600 dir. Example for source dir named foo:
2601
2602 local_src_foo_path = /home/user/foo
2603 '''
2604 def __init__(self, params, name, destination_dir):
2605 '''
2606 Instantiate a new LocalSourceDirParamHelper
2607 '''
2608 self.params = params
2609 self.name = name
2610 self.destination_dir = destination_dir
2611 self._parse_params()
2612
2613
2614 def _parse_params(self):
2615 '''
2616 Parses the params items for entries related to source dir
2617 '''
2618 config_prefix = 'local_src_%s' % self.name
2619 logging.debug('Parsing parameters for local source %s, configuration '
2620 'prefix is %s' % (self.name, config_prefix))
2621
2622 self.path = self.params.get('%s_path' % config_prefix)
2623 logging.debug('Local source directory %s path: %s' % (self.name,
2624 self.path))
2625 self.source = self.path
2626 self.destination = self.destination_dir
2627
2628
2629class LocalTarHelper(object):
2630 '''
2631 Helper class to deal with source code in a local tarball
2632 '''
2633 def __init__(self, source, destination_dir):
2634 self.source = source
2635 self.destination = destination_dir
2636
2637
2638 def extract(self):
2639 '''
2640 Extracts the tarball into the destination directory
2641 '''
2642 if os.path.isdir(self.destination):
2643 shutil.rmtree(self.destination)
2644
2645 if os.path.isfile(self.source) and tarfile.is_tarfile(self.source):
2646
2647 name = os.path.basename(self.destination)
2648 temp_dir = os.path.join(os.path.dirname(self.destination),
2649 '%s.tmp' % name)
2650 logging.debug('Temporary directory for extracting tarball is %s' %
2651 temp_dir)
2652
2653 if not os.path.isdir(temp_dir):
2654 os.makedirs(temp_dir)
2655
2656 tarball = tarfile.open(self.source)
2657 tarball.extractall(temp_dir)
2658
2659 #
2660 # If there's a directory at the toplevel of the tarfile, assume
2661 # it's the root for the contents, usually source code
2662 #
2663 tarball_info = tarball.members[0]
2664 if tarball_info.isdir():
2665 content_path = os.path.join(temp_dir,
2666 tarball_info.name)
2667 else:
2668 content_path = temp_dir
2669
2670 #
2671 # Now move the content directory to the final destination
2672 #
2673 shutil.move(content_path, self.destination)
2674
2675 else:
2676 raise OSError("%s is not a file or tar file" % self.source)
2677
2678
2679 def execute(self):
2680 '''
2681 Executes all action this helper is suposed to perform
2682
2683 This is the main entry point method for this class, and all other
2684 helper classes.
2685 '''
2686 self.extract()
2687
2688
2689class LocalTarParamHelper(LocalTarHelper):
2690 '''
2691 Helps to deal with source tarballs specified in cartersian config files
2692
2693 This class attempts to make it simple to manage a tarball with source code,
2694 by using a naming standard that follows this basic syntax:
2695
2696 <prefix>_name_<suffix>
2697
2698 <prefix> is always 'local_tar' and <suffix> sets options for this source
2699 tarball. Example for source tarball named foo:
2700
2701 local_tar_foo_path = /tmp/foo-1.0.tar.gz
2702 '''
2703 def __init__(self, params, name, destination_dir):
2704 '''
2705 Instantiates a new LocalTarParamHelper
2706 '''
2707 self.params = params
2708 self.name = name
2709 self.destination_dir = destination_dir
2710 self._parse_params()
2711
2712
2713 def _parse_params(self):
2714 '''
2715 Parses the params items for entries related to this local tar helper
2716 '''
2717 config_prefix = 'local_tar_%s' % self.name
2718 logging.debug('Parsing parameters for local tar %s, configuration '
2719 'prefix is %s' % (self.name, config_prefix))
2720
2721 self.path = self.params.get('%s_path' % config_prefix)
2722 logging.debug('Local source tar %s path: %s' % (self.name,
2723 self.path))
2724 self.source = self.path
2725 self.destination = self.destination_dir
2726
2727
2728class RemoteTarHelper(LocalTarHelper):
2729 '''
2730 Helper that fetches a tarball and extracts it locally
2731 '''
2732 def __init__(self, source_uri, destination_dir):
2733 self.source = source_uri
2734 self.destination = destination_dir
2735
2736
2737 def execute(self):
2738 '''
2739 Executes all action this helper class is suposed to perform
2740
2741 This is the main entry point method for this class, and all other
2742 helper classes.
2743
2744 This implementation fetches the remote tar file and then extracts
2745 it using the functionality present in the parent class.
2746 '''
2747 name = os.path.basename(self.source)
2748 base_dest = os.path.dirname(self.destination_dir)
2749 dest = os.path.join(base_dest, name)
2750 utils.get_file(self.source, dest)
2751 self.source = dest
2752 self.extract()
2753
2754
2755class RemoteTarParamHelper(RemoteTarHelper):
2756 '''
2757 Helps to deal with remote source tarballs specified in cartersian config
2758
2759 This class attempts to make it simple to manage a tarball with source code,
2760 by using a naming standard that follows this basic syntax:
2761
2762 <prefix>_name_<suffix>
2763
2764 <prefix> is always 'local_tar' and <suffix> sets options for this source
2765 tarball. Example for source tarball named foo:
2766
2767 remote_tar_foo_uri = http://foo.org/foo-1.0.tar.gz
2768 '''
2769 def __init__(self, params, name, destination_dir):
2770 '''
2771 Instantiates a new RemoteTarParamHelper instance
2772 '''
2773 self.params = params
2774 self.name = name
2775 self.destination_dir = destination_dir
2776 self._parse_params()
2777
2778
2779 def _parse_params(self):
2780 '''
2781 Parses the params items for entries related to this remote tar helper
2782 '''
2783 config_prefix = 'remote_tar_%s' % self.name
2784 logging.debug('Parsing parameters for remote tar %s, configuration '
2785 'prefix is %s' % (self.name, config_prefix))
2786
2787 self.uri = self.params.get('%s_uri' % config_prefix)
2788 logging.debug('Remote source tar %s uri: %s' % (self.name,
2789 self.uri))
2790 self.source = self.uri
2791 self.destination = self.destination_dir
2792
2793
2794class PatchHelper(object):
2795 '''
2796 Helper that encapsulates the patching of source code with patch files
2797 '''
2798 def __init__(self, source_dir, patches):
2799 '''
2800 Initializes a new PatchHelper
2801 '''
2802 self.source_dir = source_dir
2803 self.patches = patches
2804
2805
2806 def download(self):
2807 '''
2808 Copies patch files from remote locations to the source directory
2809 '''
2810 for patch in self.patches:
2811 utils.get_file(patch, os.path.join(self.source_dir,
2812 os.path.basename(patch)))
2813
2814
2815 def patch(self):
2816 '''
2817 Patches the source dir with all patch files
2818 '''
2819 os.chdir(self.source_dir)
2820 for patch in self.patches:
2821 patch_file = os.path.join(self.source_dir,
2822 os.path.basename(patch))
2823 utils.system('patch -p1 < %s' % os.path.basename(patch))
2824
2825
2826 def execute(self):
2827 '''
2828 Performs all steps necessary to download patches and apply them
2829 '''
2830 self.download()
2831 self.patch()
2832
2833
2834class PatchParamHelper(PatchHelper):
2835 '''
2836 Helps to deal with patches specified in cartersian config files
2837
2838 This class attempts to make it simple to patch source coude, by using a
2839 naming standard that follows this basic syntax:
2840
2841 [<git_repo>|<local_src>|<local_tar>|<remote_tar>]_<name>_patches
2842
2843 <prefix> is either a 'local_src' or 'git_repo', that, together with <name>
2844 specify a directory containing source code to receive the patches. That is,
2845 for source code coming from git repo foo, patches would be specified as:
2846
2847 git_repo_foo_patches = ['http://foo/bar.patch', 'http://foo/baz.patch']
2848
2849 And for for patches to be applied on local source code named also foo:
2850
2851 local_src_foo_patches = ['http://foo/bar.patch', 'http://foo/baz.patch']
2852 '''
2853 def __init__(self, params, prefix, source_dir):
2854 '''
2855 Initializes a new PatchParamHelper instance
2856 '''
2857 self.params = params
2858 self.prefix = prefix
2859 self.source_dir = source_dir
2860 self._parse_params()
2861
2862
2863 def _parse_params(self):
2864 '''
2865 Parses the params items for entries related to this set of patches
2866
2867 This method currently does everything that the parent class __init__()
2868 method does, that is, sets all instance variables needed by other
2869 methods. That means it's not strictly necessary to call parent's
2870 __init__().
2871 '''
2872 logging.debug('Parsing patch parameters for prefix %s' % self.prefix)
2873 patches_param_key = '%s_patches' % self.prefix
2874
2875 self.patches_str = self.params.get(patches_param_key, '[]')
2876 logging.debug('Patches config for prefix %s: %s' % (self.prefix,
2877 self.patches_str))
2878
2879 self.patches = eval(self.patches_str)
2880 logging.debug('Patches for prefix %s: %s' % (self.prefix,
2881 ", ".join(self.patches)))
2882
2883
2884class GnuSourceBuildInvalidSource(Exception):
2885 '''
2886 Exception raised when build source dir/file is not valid
2887 '''
2888 pass
2889
2890
2891class GnuSourceBuildHelper(object):
2892 '''
2893 Handles software installation of GNU-like source code
2894
2895 This basically means that the build will go though the classic GNU
2896 autotools steps: ./configure, make, make install
2897 '''
2898 def __init__(self, source, build_dir, prefix,
2899 configure_options=[]):
2900 '''
2901 @type source: string
2902 @param source: source directory or tarball
2903 @type prefix: string
2904 @param prefix: installation prefix
2905 @type build_dir: string
2906 @param build_dir: temporary directory used for building the source code
2907 @type configure_options: list
2908 @param configure_options: options to pass to configure
2909 @throws: GnuSourceBuildInvalidSource
2910 '''
2911 self.source = source
2912 self.build_dir = build_dir
2913 self.prefix = prefix
2914 self.configure_options = configure_options
2915 self.include_pkg_config_path()
2916
2917
2918 def include_pkg_config_path(self):
2919 '''
2920 Adds the current prefix to the list of paths that pkg-config searches
2921
2922 This is currently not optional as there is no observed adverse side
2923 effects of enabling this. As the "prefix" is usually only valid during
2924 a test run, we believe that having other pkg-config files (*.pc) in
2925 either '<prefix>/share/pkgconfig' or '<prefix>/lib/pkgconfig' is
2926 exactly for the purpose of using them.
2927
2928 @returns: None
2929 '''
2930 env_var = 'PKG_CONFIG_PATH'
2931
2932 include_paths = [os.path.join(self.prefix, 'share', 'pkgconfig'),
2933 os.path.join(self.prefix, 'lib', 'pkgconfig')]
2934
2935 if os.environ.has_key(env_var):
2936 paths = os.environ[env_var].split(':')
2937 for include_path in include_paths:
2938 if include_path not in paths:
2939 paths.append(include_path)
2940 os.environ[env_var] = ':'.join(paths)
2941 else:
2942 os.environ[env_var] = ':'.join(include_paths)
2943
2944 logging.debug('PKG_CONFIG_PATH is: %s' % os.environ['PKG_CONFIG_PATH'])
2945
2946
2947 def get_configure_path(self):
2948 '''
2949 Checks if 'configure' exists, if not, return 'autogen.sh' as a fallback
2950 '''
2951 configure_path = os.path.abspath(os.path.join(self.source,
2952 "configure"))
2953 autogen_path = os.path.abspath(os.path.join(self.source,
2954 "autogen.sh"))
2955 if os.path.exists(configure_path):
2956 return configure_path
2957 elif os.path.exists(autogen_path):
2958 return autogen_path
2959 else:
2960 raise GnuSourceBuildInvalidSource('configure script does not exist')
2961
2962
2963 def get_available_configure_options(self):
2964 '''
2965 Return the list of available options of a GNU like configure script
2966
2967 This will run the "configure" script at the source directory
2968
2969 @returns: list of options accepted by configure script
2970 '''
2971 help_raw = utils.system_output('%s --help' % self.get_configure_path(),
2972 ignore_status=True)
2973 help_output = help_raw.split("\n")
2974 option_list = []
2975 for line in help_output:
2976 cleaned_line = line.lstrip()
2977 if cleaned_line.startswith("--"):
2978 option = cleaned_line.split()[0]
2979 option = option.split("=")[0]
2980 option_list.append(option)
2981
2982 return option_list
2983
2984
2985 def enable_debug_symbols(self):
2986 '''
2987 Enables option that leaves debug symbols on compiled software
2988
2989 This makes debugging a lot easier.
2990 '''
2991 enable_debug_option = "--disable-strip"
2992 if enable_debug_option in self.get_available_configure_options():
2993 self.configure_options.append(enable_debug_option)
2994 logging.debug('Enabling debug symbols with option: %s' %
2995 enable_debug_option)
2996
2997
2998 def get_configure_command(self):
2999 '''
3000 Formats configure script with all options set
3001
3002 @returns: string with all configure options, including prefix
3003 '''
3004 prefix_option = "--prefix=%s" % self.prefix
3005 options = self.configure_options
3006 options.append(prefix_option)
3007 return "%s %s" % (self.get_configure_path(),
3008 " ".join(options))
3009
3010
3011 def configure(self):
3012 '''
3013 Runs the "configure" script passing apropriate command line options
3014 '''
3015 configure_command = self.get_configure_command()
3016 logging.info('Running configure on build dir')
3017 os.chdir(self.build_dir)
3018 utils.system(configure_command)
3019
3020
3021 def make(self):
3022 '''
3023 Runs "make" using the correct number of parallel jobs
3024 '''
3025 parallel_make_jobs = utils.count_cpus()
3026 make_command = "make -j %s" % parallel_make_jobs
3027 logging.info("Running make on build dir")
3028 os.chdir(self.build_dir)
3029 utils.system(make_command)
3030
3031
3032 def make_install(self):
3033 '''
3034 Runs "make install"
3035 '''
3036 os.chdir(self.build_dir)
3037 utils.system("make install")
3038
3039
3040 install = make_install
3041
3042
3043 def execute(self):
3044 '''
3045 Runs appropriate steps for *building* this source code tree
3046 '''
3047 self.configure()
3048 self.make()
3049
3050
3051class GnuSourceBuildParamHelper(GnuSourceBuildHelper):
3052 '''
3053 Helps to deal with gnu_autotools build helper in cartersian config files
3054
3055 This class attempts to make it simple to build source coude, by using a
3056 naming standard that follows this basic syntax:
3057
3058 [<git_repo>|<local_src>]_<name>_<option> = value
3059
3060 To pass extra options to the configure script, while building foo from a
3061 git repo, set the following variable:
3062
3063 git_repo_foo_configure_options = --enable-feature
3064 '''
3065 def __init__(self, params, name, destination_dir, install_prefix):
3066 '''
3067 Instantiates a new GnuSourceBuildParamHelper
3068 '''
3069 self.params = params
3070 self.name = name
3071 self.destination_dir = destination_dir
3072 self.install_prefix = install_prefix
3073 self._parse_params()
3074
3075
3076 def _parse_params(self):
3077 '''
3078 Parses the params items for entries related to source directory
3079
3080 This method currently does everything that the parent class __init__()
3081 method does, that is, sets all instance variables needed by other
3082 methods. That means it's not strictly necessary to call parent's
3083 __init__().
3084 '''
3085 logging.debug('Parsing gnu_autotools build parameters for %s' %
3086 self.name)
3087
3088 configure_opt_key = '%s_configure_options' % self.name
3089 configure_options = self.params.get(configure_opt_key, '').split()
3090 logging.debug('Configure options for %s: %s' % (self.name,
3091 configure_options))
3092
3093 self.source = self.destination_dir
3094 self.build_dir = self.destination_dir
3095 self.prefix = self.install_prefix
3096 self.configure_options = configure_options
3097 self.include_pkg_config_path()
3098
3099
Eric Lid656d562011-04-20 11:48:29 -07003100def install_host_kernel(job, params):
3101 """
3102 Install a host kernel, given the appropriate params.
3103
3104 @param job: Job object.
3105 @param params: Dict with host kernel install params.
3106 """
3107 install_type = params.get('host_kernel_install_type')
3108
3109 rpm_url = params.get('host_kernel_rpm_url')
3110
3111 koji_cmd = params.get('host_kernel_koji_cmd')
3112 koji_build = params.get('host_kernel_koji_build')
3113 koji_tag = params.get('host_kernel_koji_tag')
3114
3115 git_repo = params.get('host_kernel_git_repo')
3116 git_branch = params.get('host_kernel_git_branch')
3117 git_commit = params.get('host_kernel_git_commit')
3118 patch_list = params.get('host_kernel_patch_list')
3119 if patch_list:
3120 patch_list = patch_list.split()
3121 kernel_config = params.get('host_kernel_config')
3122
3123 if install_type == 'rpm':
3124 logging.info('Installing host kernel through rpm')
3125 dst = os.path.join("/tmp", os.path.basename(rpm_url))
3126 k = utils.get_file(rpm_url, dst)
3127 host_kernel = job.kernel(k)
3128 host_kernel.install(install_vmlinux=False)
3129 host_kernel.boot()
3130
3131 elif install_type in ['koji', 'brew']:
3132 k_deps = KojiPkgSpec(tag=koji_tag, package='kernel',
3133 subpackages=['kernel-devel', 'kernel-firmware'])
3134 k = KojiPkgSpec(tag=koji_tag, package='kernel',
3135 subpackages=['kernel'])
3136
3137 c = KojiClient(koji_cmd)
3138 logging.info('Fetching kernel dependencies (-devel, -firmware)')
3139 c.get_pkgs(k_deps, job.tmpdir)
3140 logging.info('Installing kernel dependencies (-devel, -firmware) '
3141 'through %s', install_type)
3142 k_deps_rpm_file_names = [os.path.join(job.tmpdir, rpm_file_name) for
3143 rpm_file_name in c.get_pkg_rpm_file_names(k_deps)]
3144 utils.run('rpm -U --force %s' % " ".join(k_deps_rpm_file_names))
3145
3146 c.get_pkgs(k, job.tmpdir)
3147 k_rpm = os.path.join(job.tmpdir,
3148 c.get_pkg_rpm_file_names(k)[0])
3149 host_kernel = job.kernel(k_rpm)
3150 host_kernel.install(install_vmlinux=False)
3151 host_kernel.boot()
3152
3153 elif install_type == 'git':
3154 logging.info('Chose to install host kernel through git, proceeding')
3155 repodir = os.path.join("/tmp", 'kernel_src')
3156 r = get_git_branch(git_repo, git_branch, repodir, git_commit)
3157 host_kernel = job.kernel(r)
3158 if patch_list:
3159 host_kernel.patch(patch_list)
3160 host_kernel.config(kernel_config)
3161 host_kernel.build()
3162 host_kernel.install()
3163 host_kernel.boot()
3164
3165 else:
3166 logging.info('Chose %s, using the current kernel for the host',
3167 install_type)
Dale Curtis74a314b2011-06-23 14:55:46 -07003168
3169
3170def if_nametoindex(ifname):
3171 """
3172 Map an interface name into its corresponding index.
3173 Returns 0 on error, as 0 is not a valid index
3174
3175 @param ifname: interface name
3176 """
3177 index = 0
3178 ctrl_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
3179 ifr = struct.pack("16si", ifname, 0)
3180 r = fcntl.ioctl(ctrl_sock, SIOCGIFINDEX, ifr)
3181 index = struct.unpack("16si", r)[1]
3182 ctrl_sock.close()
3183 return index
3184
3185
3186def vnet_hdr_probe(tapfd):
3187 """
3188 Check if the IFF_VNET_HDR is support by tun.
3189
3190 @param tapfd: the file descriptor of /dev/net/tun
3191 """
3192 u = struct.pack("I", 0)
3193 try:
3194 r = fcntl.ioctl(tapfd, TUNGETFEATURES, u)
3195 except OverflowError:
3196 return False
3197 flags = struct.unpack("I", r)[0]
3198 if flags & IFF_VNET_HDR:
3199 return True
3200 else:
3201 return False
3202
3203
3204def open_tap(devname, ifname, vnet_hdr=True):
3205 """
3206 Open a tap device and returns its file descriptor which is used by
3207 fd=<fd> parameter of qemu-kvm.
3208
3209 @param ifname: TAP interface name
3210 @param vnet_hdr: Whether enable the vnet header
3211 """
3212 try:
3213 tapfd = os.open(devname, os.O_RDWR)
3214 except OSError, e:
3215 raise TAPModuleError(devname, "open", e)
3216 flags = IFF_TAP | IFF_NO_PI
3217 if vnet_hdr and vnet_hdr_probe(tapfd):
3218 flags |= IFF_VNET_HDR
3219
3220 ifr = struct.pack("16sh", ifname, flags)
3221 try:
3222 r = fcntl.ioctl(tapfd, TUNSETIFF, ifr)
3223 except IOError, details:
3224 raise TAPCreationError(ifname, details)
3225 ifname = struct.unpack("16sh", r)[0].strip("\x00")
3226 return tapfd
3227
3228
3229def add_to_bridge(ifname, brname):
3230 """
3231 Add a TAP device to bridge
3232
3233 @param ifname: Name of TAP device
3234 @param brname: Name of the bridge
3235 """
3236 ctrl_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
3237 index = if_nametoindex(ifname)
3238 if index == 0:
3239 raise TAPNotExistError(ifname)
3240 ifr = struct.pack("16si", brname, index)
3241 try:
3242 r = fcntl.ioctl(ctrl_sock, SIOCBRADDIF, ifr)
3243 except IOError, details:
3244 raise BRAddIfError(ifname, brname, details)
3245 ctrl_sock.close()
3246
3247
3248def bring_up_ifname(ifname):
3249 """
3250 Bring up an interface
3251
3252 @param ifname: Name of the interface
3253 """
3254 ctrl_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
3255 ifr = struct.pack("16si", ifname, IFF_UP)
3256 try:
3257 fcntl.ioctl(ctrl_sock, SIOCSIFFLAGS, ifr)
3258 except IOError:
3259 raise TAPBringUpError(ifname)
3260 ctrl_sock.close()
3261
3262
3263def if_set_macaddress(ifname, mac):
3264 """
3265 Set the mac address for an interface
3266
3267 @param ifname: Name of the interface
3268 @mac: Mac address
3269 """
3270 ctrl_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
3271
3272 ifr = struct.pack("256s", ifname)
3273 try:
3274 mac_dev = fcntl.ioctl(ctrl_sock, SIOCGIFHWADDR, ifr)[18:24]
3275 mac_dev = ":".join(["%02x" % ord(m) for m in mac_dev])
3276 except IOError, e:
3277 raise HwAddrGetError(ifname)
3278
3279 if mac_dev.lower() == mac.lower():
3280 return
3281
3282 ifr = struct.pack("16sH14s", ifname, 1,
3283 "".join([chr(int(m, 16)) for m in mac.split(":")]))
3284 try:
3285 fcntl.ioctl(ctrl_sock, SIOCSIFHWADDR, ifr)
3286 except IOError, e:
3287 logging.info(e)
3288 raise HwAddrSetError(ifname, mac)
3289 ctrl_sock.close()