65d05de5570a8a79ababcf5be91c127542fd7d4c
[tests.git] / system / TestPlc.py
1 # Thierry Parmentelat <thierry.parmentelat@inria.fr>
2 # Copyright (C) 2010 INRIA 
3 #
4 import sys
5 import time
6 import os, os.path
7 import traceback
8 import socket
9 from datetime import datetime, timedelta
10
11 import utils
12 from Completer import Completer, CompleterTask
13 from TestSite import TestSite
14 from TestNode import TestNode, CompleterTaskNodeSsh
15 from TestUser import TestUser
16 from TestKey import TestKey
17 from TestSlice import TestSlice
18 from TestSliver import TestSliver
19 from TestBoxQemu import TestBoxQemu
20 from TestSsh import TestSsh
21 from TestApiserver import TestApiserver
22 from TestAuthSfa import TestAuthSfa
23 from PlcapiUrlScanner import PlcapiUrlScanner
24
25 from TestBonding import TestBonding
26
27 has_sfa_cache_filename="sfa-cache"
28
29 # step methods must take (self) and return a boolean (options is a member of the class)
30
31 def standby(minutes, dry_run):
32     utils.header('Entering StandBy for {:d} mn'.format(minutes))
33     if dry_run:
34         print('dry_run')
35     else:
36         time.sleep(60*minutes)
37     return True
38
39 def standby_generic(func):
40     def actual(self):
41         minutes = int(func.__name__.split("_")[1])
42         return standby(minutes, self.options.dry_run)
43     return actual
44
45 def node_mapper(method):
46     def map_on_nodes(self, *args, **kwds):
47         overall = True
48         node_method = TestNode.__dict__[method.__name__]
49         for test_node in self.all_nodes():
50             if not node_method(test_node, *args, **kwds):
51                 overall=False
52         return overall
53     # maintain __name__ for ignore_result
54     map_on_nodes.__name__ = method.__name__
55     # restore the doc text
56     map_on_nodes.__doc__ = TestNode.__dict__[method.__name__].__doc__
57     return map_on_nodes
58
59 def slice_mapper(method):
60     def map_on_slices(self):
61         overall = True
62         slice_method = TestSlice.__dict__[method.__name__]
63         for slice_spec in self.plc_spec['slices']:
64             site_spec = self.locate_site (slice_spec['sitename'])
65             test_site = TestSite(self,site_spec)
66             test_slice = TestSlice(self,test_site,slice_spec)
67             if not slice_method(test_slice, self.options):
68                 overall=False
69         return overall
70     # maintain __name__ for ignore_result
71     map_on_slices.__name__ = method.__name__
72     # restore the doc text
73     map_on_slices.__doc__ = TestSlice.__dict__[method.__name__].__doc__
74     return map_on_slices
75
76 def bonding_redirector(method):
77     bonding_name = method.__name__.replace('bonding_', '')
78     def redirect(self):
79         bonding_method = TestBonding.__dict__[bonding_name]
80         return bonding_method(self.test_bonding)
81     # maintain __name__ for ignore_result
82     redirect.__name__ = method.__name__
83     # restore the doc text
84     redirect.__doc__ = TestBonding.__dict__[bonding_name].__doc__
85     return redirect
86
87 # run a step but return True so that we can go on
88 def ignore_result(method):
89     def ignoring(self):
90         # ssh_slice_ignore->ssh_slice
91         ref_name = method.__name__.replace('_ignore', '').replace('force_', '')
92         ref_method = TestPlc.__dict__[ref_name]
93         result = ref_method(self)
94         print("Actual (but ignored) result for {ref_name} is {result}".format(**locals()))
95         return Ignored(result)
96     name = method.__name__.replace('_ignore', '').replace('force_', '')
97     ignoring.__name__ = name
98     ignoring.__doc__ = "ignored version of " + name
99     return ignoring
100
101 # a variant that expects the TestSlice method to return a list of CompleterTasks that
102 # are then merged into a single Completer run to avoid wating for all the slices
103 # esp. useful when a test fails of course
104 # because we need to pass arguments we use a class instead..
105 class slice_mapper__tasks(object):
106     # could not get this to work with named arguments
107     def __init__(self, timeout_minutes, silent_minutes, period_seconds):
108         self.timeout = timedelta(minutes = timeout_minutes)
109         self.silent = timedelta(minutes = silent_minutes)
110         self.period = timedelta(seconds = period_seconds)
111     def __call__(self, method):
112         decorator_self=self
113         # compute augmented method name
114         method_name = method.__name__ + "__tasks"
115         # locate in TestSlice
116         slice_method = TestSlice.__dict__[ method_name ]
117         def wrappee(self):
118             tasks=[]
119             for slice_spec in self.plc_spec['slices']:
120                 site_spec = self.locate_site (slice_spec['sitename'])
121                 test_site = TestSite(self, site_spec)
122                 test_slice = TestSlice(self, test_site, slice_spec)
123                 tasks += slice_method (test_slice, self.options)
124             return Completer (tasks, message=method.__name__).\
125                 run(decorator_self.timeout, decorator_self.silent, decorator_self.period)
126         # restore the doc text from the TestSlice method even if a bit odd
127         wrappee.__name__ = method.__name__
128         wrappee.__doc__ = slice_method.__doc__
129         return wrappee
130
131 def auth_sfa_mapper(method):
132     def actual(self):
133         overall = True
134         auth_method = TestAuthSfa.__dict__[method.__name__]
135         for auth_spec in self.plc_spec['sfa']['auth_sfa_specs']:
136             test_auth = TestAuthSfa(self, auth_spec)
137             if not auth_method(test_auth, self.options):
138                 overall=False
139         return overall
140     # restore the doc text
141     actual.__doc__ = TestAuthSfa.__dict__[method.__name__].__doc__
142     return actual
143
144 class Ignored:
145     def __init__(self, result):
146         self.result = result
147
148 SEP = '<sep>'
149 SEPSFA = '<sep_sfa>'
150
151 class TestPlc:
152
153     default_steps = [
154         'show', SEP,
155         'plcvm_delete','plcvm_timestamp','plcvm_create', SEP,
156         'plc_install', 'plc_configure', 'plc_start', SEP,
157         'keys_fetch', 'keys_store', 'keys_clear_known_hosts', SEP,
158         'plcapi_urls','speed_up_slices', SEP,
159         'initscripts', 'sites', 'nodes', 'slices', 'nodegroups', 'leases', SEP,
160 # slices created under plcsh interactively seem to be fine but these ones don't have the tags
161 # keep this our of the way for now
162         'check_vsys_defaults_ignore', SEP,
163 # run this first off so it's easier to re-run on another qemu box        
164         'qemu_kill_mine', 'nodestate_reinstall', 'qemu_local_init','bootcd', 'qemu_local_config', SEP,
165         'qemu_clean_mine', 'qemu_export', 'qemu_start', 'qemu_timestamp', 'qemu_nodefamily', SEP,
166         'sfa_install_all', 'sfa_configure', 'cross_sfa_configure', 'sfa_start', 'sfa_import', SEPSFA,
167         'sfi_configure@1', 'sfa_register_site@1','sfa_register_pi@1', SEPSFA,
168         'sfa_register_user@1', 'sfa_update_user@1', 'sfa_register_slice@1', 'sfa_renew_slice@1', SEPSFA,
169         'sfa_remove_user_from_slice@1','sfi_show_slice_researchers@1', 
170         'sfa_insert_user_in_slice@1','sfi_show_slice_researchers@1', SEPSFA,
171         'sfa_discover@1', 'sfa_rspec@1', SEPSFA,
172         'sfa_allocate@1', 'sfa_provision@1', 'sfa_describe@1', SEPSFA,
173         'sfa_check_slice_plc@1', 'sfa_update_slice@1', SEPSFA,
174         'sfi_list@1', 'sfi_show_site@1', 'sfa_utest@1', SEPSFA,
175         # we used to run plcsh_stress_test, and then ssh_node_debug and ssh_node_boot
176         # but as the stress test might take a while, we sometimes missed the debug mode..
177         'probe_kvm_iptables',
178         'ping_node', 'ssh_node_debug', 'plcsh_stress_test@1', SEP,
179         'ssh_node_boot', 'node_bmlogs', 'ssh_slice', 'ssh_slice_basics', 'check_initscripts', SEP,
180         'ssh_slice_sfa@1', SEPSFA, 
181         'sfa_rspec_empty@1', 'sfa_allocate_empty@1', 'sfa_provision_empty@1','sfa_check_slice_plc_empty@1', SEPSFA,
182         'sfa_delete_slice@1', 'sfa_delete_user@1', SEPSFA,
183         'cross_check_tcp@1', 'check_system_slice', SEP,
184         # for inspecting the slice while it runs the first time
185         #'fail',
186         # check slices are turned off properly
187         'empty_slices', 'ssh_slice_off', 'slice_fs_deleted_ignore', SEP,
188         # check they are properly re-created with the same name
189         'fill_slices', 'ssh_slice_again', SEP,
190         'gather_logs_force', SEP,
191         ]
192     other_steps = [ 
193         'export', 'show_boxes', 'super_speed_up_slices', SEP,
194         'check_hooks', 'plc_stop', 'plcvm_start', 'plcvm_stop', SEP,
195         'delete_initscripts', 'delete_nodegroups','delete_all_sites', SEP,
196         'delete_sites', 'delete_nodes', 'delete_slices', 'keys_clean', SEP,
197         'delete_leases', 'list_leases', SEP,
198         'populate', SEP,
199         'nodestate_show','nodestate_safeboot','nodestate_boot', 'nodestate_upgrade', SEP,
200         'nodeflavour_show','nodedistro_f14','nodedistro_f18', SEP,
201         'nodedistro_f20', 'nodedistro_f21','nodedistro_f22', SEP,
202         'nodeplain_on','nodeplain_off','nodeplain_show', SEP,
203         'qemu_list_all', 'qemu_list_mine', 'qemu_kill_all', SEP,
204         'sfa_install_core', 'sfa_install_sfatables', 'sfa_install_plc', 'sfa_install_client', SEPSFA,
205         'sfa_plcclean', 'sfa_dbclean', 'sfa_stop','sfa_uninstall', 'sfi_clean', SEPSFA,
206         'sfa_get_expires', SEPSFA,
207         'plc_db_dump' , 'plc_db_restore', SEP,
208         'check_netflow','check_drl', SEP,
209         'debug_nodemanager', 'slice_fs_present', SEP,
210         'standby_1_through_20','yes','no',SEP,
211         'install_syslinux6', 'bonding_builds', 'bonding_nodes', SEP,
212         ]
213     default_bonding_steps = [
214         'bonding_init_partial',
215         'bonding_add_yum',
216         'bonding_install_rpms', SEP,
217         ]
218
219     @staticmethod
220     def printable_steps(list):
221         single_line = " ".join(list) + " "
222         return single_line.replace(" "+SEP+" ", " \\\n").replace(" "+SEPSFA+" ", " \\\n")
223     @staticmethod
224     def valid_step(step):
225         return step != SEP and step != SEPSFA
226
227     # turn off the sfa-related steps when build has skipped SFA
228     # this was originally for centos5 but is still valid
229     # for up to f12 as recent SFAs with sqlalchemy won't build before f14
230     @staticmethod
231     def _has_sfa_cached(rpms_url):
232         if os.path.isfile(has_sfa_cache_filename):
233             with open(has_sfa_cache_filename) as cache:
234                 cached = cache.read() == "yes"
235             utils.header("build provides SFA (cached):{}".format(cached))
236             return cached
237         # warning, we're now building 'sface' so let's be a bit more picky
238         # full builds are expected to return with 0 here
239         utils.header("Checking if build provides SFA package...")
240         retcod = utils.system("curl --silent {}/ | grep -q sfa-".format(rpms_url)) == 0
241         encoded = 'yes' if retcod else 'no'
242         with open(has_sfa_cache_filename,'w') as cache:
243             cache.write(encoded)
244         return retcod
245         
246     @staticmethod
247     def check_whether_build_has_sfa(rpms_url):
248         has_sfa = TestPlc._has_sfa_cached(rpms_url)
249         if has_sfa:
250             utils.header("build does provide SFA")
251         else:
252             # move all steps containing 'sfa' from default_steps to other_steps
253             utils.header("SFA package not found - removing steps with sfa or sfi")
254             sfa_steps = [ step for step in TestPlc.default_steps
255                           if step.find('sfa') >= 0 or step.find("sfi") >= 0 ]
256             TestPlc.other_steps += sfa_steps
257             for step in sfa_steps:
258                 TestPlc.default_steps.remove(step)
259
260     def __init__(self, plc_spec, options):
261         self.plc_spec = plc_spec
262         self.options = options
263         self.test_ssh = TestSsh(self.plc_spec['host_box'], self.options.buildname)
264         self.vserverip = plc_spec['vserverip']
265         self.vservername = plc_spec['vservername']
266         self.vplchostname = self.vservername.split('-')[-1]
267         self.url = "https://{}:443/PLCAPI/".format(plc_spec['vserverip'])
268         self.apiserver = TestApiserver(self.url, options.dry_run)
269         (self.ssh_node_boot_timeout, self.ssh_node_boot_silent) = plc_spec['ssh_node_boot_timers']
270         (self.ssh_node_debug_timeout, self.ssh_node_debug_silent) = plc_spec['ssh_node_debug_timers']
271         
272     def has_addresses_api(self):
273         return self.apiserver.has_method('AddIpAddress')
274
275     def name(self):
276         name = self.plc_spec['name']
277         return "{}.{}".format(name,self.vservername)
278
279     def hostname(self):
280         return self.plc_spec['host_box']
281
282     def is_local(self):
283         return self.test_ssh.is_local()
284
285     # define the API methods on this object through xmlrpc
286     # would help, but not strictly necessary
287     def connect(self):
288         pass
289
290     def actual_command_in_guest(self,command, backslash=False):
291         raw1 = self.host_to_guest(command)
292         raw2 = self.test_ssh.actual_command(raw1, dry_run=self.options.dry_run, backslash=backslash)
293         return raw2
294     
295     def start_guest(self):
296       return utils.system(self.test_ssh.actual_command(self.start_guest_in_host(),
297                                                        dry_run=self.options.dry_run))
298     
299     def stop_guest(self):
300       return utils.system(self.test_ssh.actual_command(self.stop_guest_in_host(),
301                                                        dry_run=self.options.dry_run))
302     
303     def run_in_guest(self, command, backslash=False):
304         raw = self.actual_command_in_guest(command, backslash)
305         return utils.system(raw)
306     
307     def run_in_host(self,command):
308         return self.test_ssh.run_in_buildname(command, dry_run=self.options.dry_run)
309
310     # backslashing turned out so awful at some point that I've turned off auto-backslashing
311     # see e.g. plc_start esp. the version for f14
312     #command gets run in the plc's vm
313     def host_to_guest(self, command):
314         ssh_leg = TestSsh(self.vplchostname)
315         return ssh_leg.actual_command(command, keep_stdin=True)
316     
317     # this /vservers thing is legacy...
318     def vm_root_in_host(self):
319         return "/vservers/{}/".format(self.vservername)
320
321     def vm_timestamp_path(self):
322         return "/vservers/{}/{}.timestamp".format(self.vservername, self.vservername)
323
324     #start/stop the vserver
325     def start_guest_in_host(self):
326         return "virsh -c lxc:/// start {}".format(self.vservername)
327     
328     def stop_guest_in_host(self):
329         return "virsh -c lxc:/// destroy {}".format(self.vservername)
330     
331     # xxx quick n dirty
332     def run_in_guest_piped(self,local,remote):
333         return utils.system(local+" | "+self.test_ssh.actual_command(self.host_to_guest(remote),
334                                                                      keep_stdin = True))
335
336     def yum_check_installed(self, rpms):
337         if isinstance(rpms, list): 
338             rpms=" ".join(rpms)
339         return self.run_in_guest("rpm -q {}".format(rpms)) == 0
340         
341     # does a yum install in the vs, ignore yum retcod, check with rpm
342     def yum_install(self, rpms):
343         if isinstance(rpms, list): 
344             rpms=" ".join(rpms)
345         self.run_in_guest("yum -y install {}".format(rpms))
346         # yum-complete-transaction comes with yum-utils, that is in vtest.pkgs
347         self.run_in_guest("yum-complete-transaction -y")
348         return self.yum_check_installed(rpms)
349
350     def auth_root(self):
351         return {'Username'   : self.plc_spec['settings']['PLC_ROOT_USER'],
352                 'AuthMethod' : 'password',
353                 'AuthString' : self.plc_spec['settings']['PLC_ROOT_PASSWORD'],
354                 'Role'       : self.plc_spec['role'],
355                 }
356     
357     def locate_site(self,sitename):
358         for site in self.plc_spec['sites']:
359             if site['site_fields']['name'] == sitename:
360                 return site
361             if site['site_fields']['login_base'] == sitename:
362                 return site
363         raise Exception("Cannot locate site {}".format(sitename))
364         
365     def locate_node(self, nodename):
366         for site in self.plc_spec['sites']:
367             for node in site['nodes']:
368                 if node['name'] == nodename:
369                     return site, node
370         raise Exception("Cannot locate node {}".format(nodename))
371         
372     def locate_hostname(self, hostname):
373         for site in self.plc_spec['sites']:
374             for node in site['nodes']:
375                 if node['node_fields']['hostname'] == hostname:
376                     return(site, node)
377         raise Exception("Cannot locate hostname {}".format(hostname))
378         
379     def locate_key(self, key_name):
380         for key in self.plc_spec['keys']:
381             if key['key_name'] == key_name:
382                 return key
383         raise Exception("Cannot locate key {}".format(key_name))
384
385     def locate_private_key_from_key_names(self, key_names):
386         # locate the first avail. key
387         found = False
388         for key_name in key_names:
389             key_spec = self.locate_key(key_name)
390             test_key = TestKey(self,key_spec)
391             publickey = test_key.publicpath()
392             privatekey = test_key.privatepath()
393             if os.path.isfile(publickey) and os.path.isfile(privatekey):
394                 found = True
395         if found:
396             return privatekey
397         else:
398             return None
399
400     def locate_slice(self, slicename):
401         for slice in self.plc_spec['slices']:
402             if slice['slice_fields']['name'] == slicename:
403                 return slice
404         raise Exception("Cannot locate slice {}".format(slicename))
405
406     def all_sliver_objs(self):
407         result = []
408         for slice_spec in self.plc_spec['slices']:
409             slicename = slice_spec['slice_fields']['name']
410             for nodename in slice_spec['nodenames']:
411                 result.append(self.locate_sliver_obj(nodename, slicename))
412         return result
413
414     def locate_sliver_obj(self, nodename, slicename):
415         site,node = self.locate_node(nodename)
416         slice = self.locate_slice(slicename)
417         # build objects
418         test_site = TestSite(self, site)
419         test_node = TestNode(self, test_site, node)
420         # xxx the slice site is assumed to be the node site - mhh - probably harmless
421         test_slice = TestSlice(self, test_site, slice)
422         return TestSliver(self, test_node, test_slice)
423
424     def locate_first_node(self):
425         nodename = self.plc_spec['slices'][0]['nodenames'][0]
426         site,node = self.locate_node(nodename)
427         test_site = TestSite(self, site)
428         test_node = TestNode(self, test_site, node)
429         return test_node
430
431     def locate_first_sliver(self):
432         slice_spec = self.plc_spec['slices'][0]
433         slicename = slice_spec['slice_fields']['name']
434         nodename = slice_spec['nodenames'][0]
435         return self.locate_sliver_obj(nodename,slicename)
436
437     # all different hostboxes used in this plc
438     def get_BoxNodes(self):
439         # maps on sites and nodes, return [ (host_box,test_node) ]
440         tuples = []
441         for site_spec in self.plc_spec['sites']:
442             test_site = TestSite(self,site_spec)
443             for node_spec in site_spec['nodes']:
444                 test_node = TestNode(self, test_site, node_spec)
445                 if not test_node.is_real():
446                     tuples.append( (test_node.host_box(),test_node) )
447         # transform into a dict { 'host_box' -> [ test_node .. ] }
448         result = {}
449         for (box,node) in tuples:
450             if box not in result:
451                 result[box] = [node]
452             else:
453                 result[box].append(node)
454         return result
455                     
456     # a step for checking this stuff
457     def show_boxes(self):
458         'print summary of nodes location'
459         for box,nodes in self.get_BoxNodes().items():
460             print(box,":"," + ".join( [ node.name() for node in nodes ] ))
461         return True
462
463     # make this a valid step
464     def qemu_kill_all(self):
465         'kill all qemu instances on the qemu boxes involved by this setup'
466         # this is the brute force version, kill all qemus on that host box
467         for (box,nodes) in self.get_BoxNodes().items():
468             # pass the first nodename, as we don't push template-qemu on testboxes
469             nodedir = nodes[0].nodedir()
470             TestBoxQemu(box, self.options.buildname).qemu_kill_all(nodedir)
471         return True
472
473     # make this a valid step
474     def qemu_list_all(self):
475         'list all qemu instances on the qemu boxes involved by this setup'
476         for box,nodes in self.get_BoxNodes().items():
477             # this is the brute force version, kill all qemus on that host box
478             TestBoxQemu(box, self.options.buildname).qemu_list_all()
479         return True
480
481     # kill only the qemus related to this test
482     def qemu_list_mine(self):
483         'list qemu instances for our nodes'
484         for (box,nodes) in self.get_BoxNodes().items():
485             # the fine-grain version
486             for node in nodes:
487                 node.list_qemu()
488         return True
489
490     # kill only the qemus related to this test
491     def qemu_clean_mine(self):
492         'cleanup (rm -rf) qemu instances for our nodes'
493         for box,nodes in self.get_BoxNodes().items():
494             # the fine-grain version
495             for node in nodes:
496                 node.qemu_clean()
497         return True
498
499     # kill only the right qemus
500     def qemu_kill_mine(self):
501         'kill the qemu instances for our nodes'
502         for box,nodes in self.get_BoxNodes().items():
503             # the fine-grain version
504             for node in nodes:
505                 node.kill_qemu()
506         return True
507
508     #################### display config
509     def show(self):
510         "show test configuration after localization"
511         self.show_pass(1)
512         self.show_pass(2)
513         return True
514
515     # uggly hack to make sure 'run export' only reports about the 1st plc 
516     # to avoid confusion - also we use 'inri_slice1' in various aliases..
517     exported_id = 1
518     def export(self):
519         "print cut'n paste-able stuff to export env variables to your shell"
520         # guess local domain from hostname
521         if TestPlc.exported_id > 1: 
522             print("export GUESTHOSTNAME{:d}={}".format(TestPlc.exported_id, self.plc_spec['vservername']))
523             return True
524         TestPlc.exported_id += 1
525         domain = socket.gethostname().split('.',1)[1]
526         fqdn   = "{}.{}".format(self.plc_spec['host_box'], domain)
527         print("export BUILD={}".format(self.options.buildname))
528         print("export PLCHOSTLXC={}".format(fqdn))
529         print("export GUESTNAME={}".format(self.vservername))
530         print("export GUESTHOSTNAME={}.{}".format(self.vplchostname, domain))
531         # find hostname of first node
532         hostname, qemubox = self.all_node_infos()[0]
533         print("export KVMHOST={}.{}".format(qemubox, domain))
534         print("export NODE={}".format(hostname))
535         return True
536
537     # entry point
538     always_display_keys=['PLC_WWW_HOST', 'nodes', 'sites']
539     def show_pass(self, passno):
540         for (key,val) in self.plc_spec.items():
541             if not self.options.verbose and key not in TestPlc.always_display_keys:
542                 continue
543             if passno == 2:
544                 if key == 'sites':
545                     for site in val:
546                         self.display_site_spec(site)
547                         for node in site['nodes']:
548                             self.display_node_spec(node)
549                 elif key == 'initscripts':
550                     for initscript in val:
551                         self.display_initscript_spec(initscript)
552                 elif key == 'slices':
553                     for slice in val:
554                         self.display_slice_spec(slice)
555                 elif key == 'keys':
556                     for key in val:
557                         self.display_key_spec(key)
558             elif passno == 1:
559                 if key not in ['sites', 'initscripts', 'slices', 'keys']:
560                     print('+   ', key, ':', val)
561
562     def display_site_spec(self, site):
563         print('+ ======== site', site['site_fields']['name'])
564         for k,v in site.items():
565             if not self.options.verbose and k not in TestPlc.always_display_keys:
566                 continue
567             if k == 'nodes':
568                 if v: 
569                     print('+       ','nodes : ', end=' ')
570                     for node in v:  
571                         print(node['node_fields']['hostname'],'', end=' ')
572                     print('')
573             elif k == 'users':
574                 if v: 
575                     print('+       users : ', end=' ')
576                     for user in v:  
577                         print(user['name'],'', end=' ')
578                     print('')
579             elif k == 'site_fields':
580                 print('+       login_base', ':', v['login_base'])
581             elif k == 'address_fields':
582                 pass
583             else:
584                 print('+       ', end=' ')
585                 utils.pprint(k, v)
586         
587     def display_initscript_spec(self, initscript):
588         print('+ ======== initscript', initscript['initscript_fields']['name'])
589
590     def display_key_spec(self, key):
591         print('+ ======== key', key['key_name'])
592
593     def display_slice_spec(self, slice):
594         print('+ ======== slice', slice['slice_fields']['name'])
595         for k,v in slice.items():
596             if k == 'nodenames':
597                 if v: 
598                     print('+       nodes : ', end=' ')
599                     for nodename in v:  
600                         print(nodename,'', end=' ')
601                     print('')
602             elif k == 'usernames':
603                 if v: 
604                     print('+       users : ', end=' ')
605                     for username in v:  
606                         print(username,'', end=' ')
607                     print('')
608             elif k == 'slice_fields':
609                 print('+       fields',':', end=' ')
610                 print('max_nodes=',v['max_nodes'], end=' ')
611                 print('')
612             else:
613                 print('+       ',k,v)
614
615     def display_node_spec(self, node):
616         print("+           node={} host_box={}".format(node['name'], node['host_box']), end=' ')
617         print("hostname=", node['node_fields']['hostname'], end=' ')
618         print("ip=", node['interface_fields']['ip'])
619         if self.options.verbose:
620             utils.pprint("node details", node, depth=3)
621
622     # another entry point for just showing the boxes involved
623     def display_mapping(self):
624         TestPlc.display_mapping_plc(self.plc_spec)
625         return True
626
627     @staticmethod
628     def display_mapping_plc(plc_spec):
629         print('+ MyPLC',plc_spec['name'])
630         # WARNING this would not be right for lxc-based PLC's - should be harmless though
631         print('+\tvserver address = root@{}:/vservers/{}'.format(plc_spec['host_box'], plc_spec['vservername']))
632         print('+\tIP = {}/{}'.format(plc_spec['settings']['PLC_API_HOST'], plc_spec['vserverip']))
633         for site_spec in plc_spec['sites']:
634             for node_spec in site_spec['nodes']:
635                 TestPlc.display_mapping_node(node_spec)
636
637     @staticmethod
638     def display_mapping_node(node_spec):
639         print('+   NODE {}'.format(node_spec['name']))
640         print('+\tqemu box {}'.format(node_spec['host_box']))
641         print('+\thostname={}'.format(node_spec['node_fields']['hostname']))
642
643     # write a timestamp in /vservers/<>.timestamp
644     # cannot be inside the vserver, that causes vserver .. build to cough
645     def plcvm_timestamp(self):
646         "Create a timestamp to remember creation date for this plc"
647         now = int(time.time())
648         # TODO-lxc check this one
649         # a first approx. is to store the timestamp close to the VM root like vs does
650         stamp_path = self.vm_timestamp_path()
651         stamp_dir = os.path.dirname(stamp_path)
652         utils.system(self.test_ssh.actual_command("mkdir -p {}".format(stamp_dir)))
653         return utils.system(self.test_ssh.actual_command("echo {:d} > {}".format(now, stamp_path))) == 0
654         
655     # this is called inconditionnally at the beginning of the test sequence 
656     # just in case this is a rerun, so if the vm is not running it's fine
657     def plcvm_delete(self):
658         "vserver delete the test myplc"
659         stamp_path = self.vm_timestamp_path()
660         self.run_in_host("rm -f {}".format(stamp_path))
661         self.run_in_host("virsh -c lxc:// destroy {}".format(self.vservername))
662         self.run_in_host("virsh -c lxc:// undefine {}".format(self.vservername))
663         self.run_in_host("rm -fr /vservers/{}".format(self.vservername))
664         return True
665
666     ### install
667     # historically the build was being fetched by the tests
668     # now the build pushes itself as a subdir of the tests workdir
669     # so that the tests do not have to worry about extracting the build (svn, git, or whatever)
670     def plcvm_create(self):
671         "vserver creation (no install done)"
672         # push the local build/ dir to the testplc box 
673         if self.is_local():
674             # a full path for the local calls
675             build_dir = os.path.dirname(sys.argv[0])
676             # sometimes this is empty - set to "." in such a case
677             if not build_dir:
678                 build_dir="."
679             build_dir += "/build"
680         else:
681             # use a standard name - will be relative to remote buildname
682             build_dir = "build"
683             # remove for safety; do *not* mkdir first, otherwise we end up with build/build/
684             self.test_ssh.rmdir(build_dir)
685             self.test_ssh.copy(build_dir, recursive=True)
686         # the repo url is taken from arch-rpms-url 
687         # with the last step (i386) removed
688         repo_url = self.options.arch_rpms_url
689         for level in [ 'arch' ]:
690             repo_url = os.path.dirname(repo_url)
691
692         # invoke initvm (drop support for vs)
693         script = "lbuild-initvm.sh"
694         script_options = ""
695         # pass the vbuild-nightly options to [lv]test-initvm
696         script_options += " -p {}".format(self.options.personality)
697         script_options += " -d {}".format(self.options.pldistro)
698         script_options += " -f {}".format(self.options.fcdistro)
699         script_options += " -r {}".format(repo_url)
700         vserver_name = self.vservername
701         try:
702             vserver_hostname = socket.gethostbyaddr(self.vserverip)[0]
703             script_options += " -n {}".format(vserver_hostname)
704         except:
705             print("Cannot reverse lookup {}".format(self.vserverip))
706             print("This is considered fatal, as this might pollute the test results")
707             return False
708         create_vserver="{build_dir}/{script} {script_options} {vserver_name}".format(**locals())
709         return self.run_in_host(create_vserver) == 0
710
711     ### install_rpm 
712     def plc_install(self):
713         """
714         yum install myplc, noderepo + plain bootstrapfs as well
715         """
716
717         # compute nodefamily
718         if self.options.personality == "linux32":
719             arch = "i386"
720         elif self.options.personality == "linux64":
721             arch = "x86_64"
722         else:
723             raise Exception("Unsupported personality {}".format(self.options.personality))
724         nodefamily = "{}-{}-{}".format(self.options.pldistro, self.options.fcdistro, arch)
725
726         pkgs_list=[]
727         pkgs_list.append("slicerepo-{}".format(nodefamily))
728         pkgs_list.append("myplc")
729         pkgs_list.append("noderepo-{}".format(nodefamily))
730         pkgs_list.append("nodeimage-{}-plain".format(nodefamily))
731         pkgs_string=" ".join(pkgs_list)
732         return self.yum_install(pkgs_list)
733
734     def install_syslinux6(self):
735         """
736         install syslinux6 from the fedora21 release
737         """
738         key = 'http://mirror.onelab.eu/keys/RPM-GPG-KEY-fedora-21-primary'
739
740         rpms = [ 
741             'http://mirror.onelab.eu/fedora/releases/21/Everything/x86_64/os/Packages/s/syslinux-6.03-1.fc21.x86_64.rpm',
742             'http://mirror.onelab.eu/fedora/releases/21/Everything/x86_64/os/Packages/s/syslinux-nonlinux-6.03-1.fc21.noarch.rpm',
743             'http://mirror.onelab.eu/fedora/releases/21/Everything/x86_64/os/Packages/s/syslinux-perl-6.03-1.fc21.x86_64.rpm',
744         ]
745         # this can be done several times
746         self.run_in_guest("rpm --import {key}".format(**locals()))
747         return self.run_in_guest("yum -y localinstall {}".format(" ".join(rpms))) == 0
748
749     def bonding_builds(self):
750         """
751         list /etc/yum.repos.d on the myplc side
752         """
753         self.run_in_guest("ls /etc/yum.repos.d/*partial.repo")
754         return True
755
756     def bonding_nodes(self):
757         """
758         List nodes known to the myplc together with their nodefamiliy
759         """
760         print("---------------------------------------- nodes")
761         for node in self.apiserver.GetNodes(self.auth_root()):
762             print("{} -> {}".format(node['hostname'],
763                                     self.apiserver.GetNodeFlavour(self.auth_root(),node['hostname'])['nodefamily']))
764         print("---------------------------------------- nodes")
765             
766     
767     ###
768     def mod_python(self):
769         """yum install mod_python, useful on f18 and above so as to avoid broken wsgi"""
770         return self.yum_install( ['mod_python'] )
771
772     ### 
773     def plc_configure(self):
774         "run plc-config-tty"
775         tmpname = '{}.plc-config-tty'.format(self.name())
776         with open(tmpname,'w') as fileconf:
777             for (var,value) in self.plc_spec['settings'].items():
778                 fileconf.write('e {}\n{}\n'.format(var, value))
779             fileconf.write('w\n')
780             fileconf.write('q\n')
781         utils.system('cat {}'.format(tmpname))
782         self.run_in_guest_piped('cat {}'.format(tmpname), 'plc-config-tty')
783         utils.system('rm {}'.format(tmpname))
784         return True
785
786 # f14 is a bit odd in this respect, although this worked fine in guests up to f18
787 # however using a vplc guest under f20 requires this trick
788 # the symptom is this: service plc start
789 # Starting plc (via systemctl):  Failed to get D-Bus connection: \
790 #    Failed to connect to socket /org/freedesktop/systemd1/private: Connection refused
791 # weird thing is the doc says f14 uses upstart by default and not systemd
792 # so this sounds kind of harmless
793     def start_service(self, service):
794         return self.start_stop_service(service, 'start')
795     def stop_service(self, service):
796         return self.start_stop_service(service, 'stop')
797
798     def start_stop_service(self, service, start_or_stop):
799         "utility to start/stop a service with the special trick for f14"
800         if self.options.fcdistro != 'f14':
801             return self.run_in_guest("service {} {}".format(service, start_or_stop)) == 0
802         else:
803             # patch /sbin/service so it does not reset environment
804             self.run_in_guest('sed -i -e \\"s,env -i,env,\\" /sbin/service')
805             # this is because our own scripts in turn call service 
806             return self.run_in_guest("SYSTEMCTL_SKIP_REDIRECT=true service {} {}"\
807                                      .format(service, start_or_stop)) == 0
808
809     def plc_start(self):
810         "service plc start"
811         return self.start_service('plc')
812
813     def plc_stop(self):
814         "service plc stop"
815         return self.stop_service('plc')
816
817     def plcvm_start(self):
818         "start the PLC vserver"
819         self.start_guest()
820         return True
821
822     def plcvm_stop(self):
823         "stop the PLC vserver"
824         self.stop_guest()
825         return True
826
827     # stores the keys from the config for further use
828     def keys_store(self):
829         "stores test users ssh keys in keys/"
830         for key_spec in self.plc_spec['keys']:
831                 TestKey(self,key_spec).store_key()
832         return True
833
834     def keys_clean(self):
835         "removes keys cached in keys/"
836         utils.system("rm -rf ./keys")
837         return True
838
839     # fetches the ssh keys in the plc's /etc/planetlab and stores them in keys/
840     # for later direct access to the nodes
841     def keys_fetch(self):
842         "gets ssh keys in /etc/planetlab/ and stores them locally in keys/"
843         dir="./keys"
844         if not os.path.isdir(dir):
845             os.mkdir(dir)
846         vservername = self.vservername
847         vm_root = self.vm_root_in_host()
848         overall = True
849         prefix = 'debug_ssh_key'
850         for ext in ['pub', 'rsa'] :
851             src = "{vm_root}/etc/planetlab/{prefix}.{ext}".format(**locals())
852             dst = "keys/{vservername}-debug.{ext}".format(**locals())
853             if self.test_ssh.fetch(src, dst) != 0:
854                 overall=False
855         return overall
856
857     def sites(self):
858         "create sites with PLCAPI"
859         return self.do_sites()
860     
861     def delete_sites(self):
862         "delete sites with PLCAPI"
863         return self.do_sites(action="delete")
864     
865     def do_sites(self, action="add"):
866         for site_spec in self.plc_spec['sites']:
867             test_site = TestSite(self,site_spec)
868             if (action != "add"):
869                 utils.header("Deleting site {} in {}".format(test_site.name(), self.name()))
870                 test_site.delete_site()
871                 # deleted with the site
872                 #test_site.delete_users()
873                 continue
874             else:
875                 utils.header("Creating site {} & users in {}".format(test_site.name(), self.name()))
876                 test_site.create_site()
877                 test_site.create_users()
878         return True
879
880     def delete_all_sites(self):
881         "Delete all sites in PLC, and related objects"
882         print('auth_root', self.auth_root())
883         sites = self.apiserver.GetSites(self.auth_root(), {}, ['site_id','login_base'])
884         for site in sites:
885             # keep automatic site - otherwise we shoot in our own foot, root_auth is not valid anymore
886             if site['login_base'] == self.plc_spec['settings']['PLC_SLICE_PREFIX']:
887                 continue
888             site_id = site['site_id']
889             print('Deleting site_id', site_id)
890             self.apiserver.DeleteSite(self.auth_root(), site_id)
891         return True
892
893     def nodes(self):
894         "create nodes with PLCAPI"
895         return self.do_nodes()
896     def delete_nodes(self):
897         "delete nodes with PLCAPI"
898         return self.do_nodes(action="delete")
899
900     def do_nodes(self, action="add"):
901         for site_spec in self.plc_spec['sites']:
902             test_site = TestSite(self, site_spec)
903             if action != "add":
904                 utils.header("Deleting nodes in site {}".format(test_site.name()))
905                 for node_spec in site_spec['nodes']:
906                     test_node = TestNode(self, test_site, node_spec)
907                     utils.header("Deleting {}".format(test_node.name()))
908                     test_node.delete_node()
909             else:
910                 utils.header("Creating nodes for site {} in {}".format(test_site.name(), self.name()))
911                 for node_spec in site_spec['nodes']:
912                     utils.pprint('Creating node {}'.format(node_spec), node_spec)
913                     test_node = TestNode(self, test_site, node_spec)
914                     test_node.create_node()
915         return True
916
917     def nodegroups(self):
918         "create nodegroups with PLCAPI"
919         return self.do_nodegroups("add")
920     def delete_nodegroups(self):
921         "delete nodegroups with PLCAPI"
922         return self.do_nodegroups("delete")
923
924     YEAR = 365*24*3600
925     @staticmethod
926     def translate_timestamp(start, grain, timestamp):
927         if timestamp < TestPlc.YEAR:
928             return start + timestamp*grain
929         else:
930             return timestamp
931
932     @staticmethod
933     def timestamp_printable(timestamp):
934         return time.strftime('%m-%d %H:%M:%S UTC', time.gmtime(timestamp))
935
936     def leases(self):
937         "create leases (on reservable nodes only, use e.g. run -c default -c resa)"
938         now = int(time.time())
939         grain = self.apiserver.GetLeaseGranularity(self.auth_root())
940         print('API answered grain=', grain)
941         start = (now//grain)*grain
942         start += grain
943         # find out all nodes that are reservable
944         nodes = self.all_reservable_nodenames()
945         if not nodes: 
946             utils.header("No reservable node found - proceeding without leases")
947             return True
948         ok = True
949         # attach them to the leases as specified in plc_specs
950         # this is where the 'leases' field gets interpreted as relative of absolute
951         for lease_spec in self.plc_spec['leases']:
952             # skip the ones that come with a null slice id
953             if not lease_spec['slice']:
954                 continue
955             lease_spec['t_from']  = TestPlc.translate_timestamp(start, grain, lease_spec['t_from'])
956             lease_spec['t_until'] = TestPlc.translate_timestamp(start, grain, lease_spec['t_until'])
957             lease_addition = self.apiserver.AddLeases(self.auth_root(), nodes, lease_spec['slice'],
958                                                       lease_spec['t_from'], lease_spec['t_until'])
959             if lease_addition['errors']:
960                 utils.header("Cannot create leases, {}".format(lease_addition['errors']))
961                 ok = False
962             else:
963                 utils.header('Leases on nodes {} for {} from {:d} ({}) until {:d} ({})'\
964                              .format(nodes, lease_spec['slice'],
965                                      lease_spec['t_from'],  TestPlc.timestamp_printable(lease_spec['t_from']),
966                                      lease_spec['t_until'], TestPlc.timestamp_printable(lease_spec['t_until'])))
967                 
968         return ok
969
970     def delete_leases(self):
971         "remove all leases in the myplc side"
972         lease_ids = [ l['lease_id'] for l in self.apiserver.GetLeases(self.auth_root())]
973         utils.header("Cleaning leases {}".format(lease_ids))
974         self.apiserver.DeleteLeases(self.auth_root(), lease_ids)
975         return True
976
977     def list_leases(self):
978         "list all leases known to the myplc"
979         leases = self.apiserver.GetLeases(self.auth_root())
980         now = int(time.time())
981         for l in leases:
982             current = l['t_until'] >= now
983             if self.options.verbose or current:
984                 utils.header("{} {} from {} until {}"\
985                              .format(l['hostname'], l['name'],
986                                      TestPlc.timestamp_printable(l['t_from']), 
987                                      TestPlc.timestamp_printable(l['t_until'])))
988         return True
989
990     # create nodegroups if needed, and populate
991     def do_nodegroups(self, action="add"):
992         # 1st pass to scan contents
993         groups_dict = {}
994         for site_spec in self.plc_spec['sites']:
995             test_site = TestSite(self,site_spec)
996             for node_spec in site_spec['nodes']:
997                 test_node = TestNode(self, test_site, node_spec)
998                 if 'nodegroups' in node_spec:
999                     nodegroupnames = node_spec['nodegroups']
1000                     if isinstance(nodegroupnames, str):
1001                         nodegroupnames = [ nodegroupnames ]
1002                     for nodegroupname in nodegroupnames:
1003                         if nodegroupname not in groups_dict:
1004                             groups_dict[nodegroupname] = []
1005                         groups_dict[nodegroupname].append(test_node.name())
1006         auth = self.auth_root()
1007         overall = True
1008         for (nodegroupname,group_nodes) in groups_dict.items():
1009             if action == "add":
1010                 print('nodegroups:', 'dealing with nodegroup',\
1011                     nodegroupname, 'on nodes', group_nodes)
1012                 # first, check if the nodetagtype is here
1013                 tag_types = self.apiserver.GetTagTypes(auth, {'tagname':nodegroupname})
1014                 if tag_types:
1015                     tag_type_id = tag_types[0]['tag_type_id']
1016                 else:
1017                     tag_type_id = self.apiserver.AddTagType(auth,
1018                                                             {'tagname' : nodegroupname,
1019                                                              'description' : 'for nodegroup {}'.format(nodegroupname),
1020                                                              'category' : 'test'})
1021                 print('located tag (type)', nodegroupname, 'as', tag_type_id)
1022                 # create nodegroup
1023                 nodegroups = self.apiserver.GetNodeGroups(auth, {'groupname' : nodegroupname})
1024                 if not nodegroups:
1025                     self.apiserver.AddNodeGroup(auth, nodegroupname, tag_type_id, 'yes')
1026                     print('created nodegroup', nodegroupname, \
1027                         'from tagname', nodegroupname, 'and value', 'yes')
1028                 # set node tag on all nodes, value='yes'
1029                 for nodename in group_nodes:
1030                     try:
1031                         self.apiserver.AddNodeTag(auth, nodename, nodegroupname, "yes")
1032                     except:
1033                         traceback.print_exc()
1034                         print('node', nodename, 'seems to already have tag', nodegroupname)
1035                     # check anyway
1036                     try:
1037                         expect_yes = self.apiserver.GetNodeTags(auth,
1038                                                                 {'hostname' : nodename,
1039                                                                  'tagname'  : nodegroupname},
1040                                                                 ['value'])[0]['value']
1041                         if expect_yes != "yes":
1042                             print('Mismatch node tag on node',nodename,'got',expect_yes)
1043                             overall = False
1044                     except:
1045                         if not self.options.dry_run:
1046                             print('Cannot find tag', nodegroupname, 'on node', nodename)
1047                             overall = False
1048             else:
1049                 try:
1050                     print('cleaning nodegroup', nodegroupname)
1051                     self.apiserver.DeleteNodeGroup(auth, nodegroupname)
1052                 except:
1053                     traceback.print_exc()
1054                     overall = False
1055         return overall
1056
1057     # a list of TestNode objs
1058     def all_nodes(self):
1059         nodes=[]
1060         for site_spec in self.plc_spec['sites']:
1061             test_site = TestSite(self,site_spec)
1062             for node_spec in site_spec['nodes']:
1063                 nodes.append(TestNode(self, test_site, node_spec))
1064         return nodes
1065
1066     # return a list of tuples (nodename,qemuname)
1067     def all_node_infos(self) :
1068         node_infos = []
1069         for site_spec in self.plc_spec['sites']:
1070             node_infos += [ (node_spec['node_fields']['hostname'], node_spec['host_box']) \
1071                                 for node_spec in site_spec['nodes'] ]
1072         return node_infos
1073     
1074     def all_nodenames(self):
1075         return [ x[0] for x in self.all_node_infos() ]
1076     def all_reservable_nodenames(self): 
1077         res = []
1078         for site_spec in self.plc_spec['sites']:
1079             for node_spec in site_spec['nodes']:
1080                 node_fields = node_spec['node_fields']
1081                 if 'node_type' in node_fields and node_fields['node_type'] == 'reservable':
1082                     res.append(node_fields['hostname'])
1083         return res
1084
1085     # silent_minutes : during the first <silent_minutes> minutes nothing gets printed
1086     def nodes_check_boot_state(self, target_boot_state, timeout_minutes,
1087                                silent_minutes, period_seconds = 15):
1088         if self.options.dry_run:
1089             print('dry_run')
1090             return True
1091
1092         class CompleterTaskBootState(CompleterTask):
1093             def __init__(self, test_plc, hostname):
1094                 self.test_plc = test_plc
1095                 self.hostname = hostname
1096                 self.last_boot_state = 'undef'
1097             def actual_run(self):
1098                 try:
1099                     node = self.test_plc.apiserver.GetNodes(self.test_plc.auth_root(),
1100                                                             [ self.hostname ],
1101                                                             ['boot_state'])[0]
1102                     self.last_boot_state = node['boot_state'] 
1103                     return self.last_boot_state == target_boot_state
1104                 except:
1105                     return False
1106             def message(self):
1107                 return "CompleterTaskBootState with node {}".format(self.hostname)
1108             def failure_epilogue(self):
1109                 print("node {} in state {} - expected {}"\
1110                     .format(self.hostname, self.last_boot_state, target_boot_state))
1111                 
1112         timeout = timedelta(minutes=timeout_minutes)
1113         graceout = timedelta(minutes=silent_minutes)
1114         period   = timedelta(seconds=period_seconds)
1115         # the nodes that haven't checked yet - start with a full list and shrink over time
1116         utils.header("checking nodes boot state (expected {})".format(target_boot_state))
1117         tasks = [ CompleterTaskBootState(self,hostname) \
1118                       for (hostname,_) in self.all_node_infos() ]
1119         message = 'check_boot_state={}'.format(target_boot_state)
1120         return Completer(tasks, message=message).run(timeout, graceout, period)
1121
1122     def nodes_booted(self):
1123         return self.nodes_check_boot_state('boot', timeout_minutes=30, silent_minutes=28)
1124
1125     def probe_kvm_iptables(self):
1126         (_,kvmbox) = self.all_node_infos()[0]
1127         TestSsh(kvmbox).run("iptables-save")
1128         return True
1129
1130     # probing nodes
1131     def check_nodes_ping(self, timeout_seconds=60, period_seconds=10):
1132         class CompleterTaskPingNode(CompleterTask):
1133             def __init__(self, hostname):
1134                 self.hostname = hostname
1135             def run(self, silent):
1136                 command="ping -c 1 -w 1 {} >& /dev/null".format(self.hostname)
1137                 return utils.system(command, silent=silent) == 0
1138             def failure_epilogue(self):
1139                 print("Cannot ping node with name {}".format(self.hostname))
1140         timeout = timedelta(seconds = timeout_seconds)
1141         graceout = timeout
1142         period = timedelta(seconds = period_seconds)
1143         node_infos = self.all_node_infos()
1144         tasks = [ CompleterTaskPingNode(h) for (h,_) in node_infos ]
1145         return Completer(tasks, message='ping_node').run(timeout, graceout, period)
1146
1147     # ping node before we try to reach ssh, helpful for troubleshooting failing bootCDs
1148     def ping_node(self):
1149         "Ping nodes"
1150         return self.check_nodes_ping()
1151
1152     def check_nodes_ssh(self, debug, timeout_minutes, silent_minutes, period_seconds=15):
1153         # various delays 
1154         timeout  = timedelta(minutes=timeout_minutes)
1155         graceout = timedelta(minutes=silent_minutes)
1156         period   = timedelta(seconds=period_seconds)
1157         vservername = self.vservername
1158         if debug: 
1159             message = "debug"
1160             completer_message = 'ssh_node_debug'
1161             local_key = "keys/{vservername}-debug.rsa".format(**locals())
1162         else: 
1163             message = "boot"
1164             completer_message = 'ssh_node_boot'
1165             local_key = "keys/key_admin.rsa"
1166         utils.header("checking ssh access to nodes (expected in {} mode)".format(message))
1167         node_infos = self.all_node_infos()
1168         tasks = [ CompleterTaskNodeSsh(nodename, qemuname, local_key,
1169                                         boot_state=message, dry_run=self.options.dry_run) \
1170                       for (nodename, qemuname) in node_infos ]
1171         return Completer(tasks, message=completer_message).run(timeout, graceout, period)
1172         
1173     def ssh_node_debug(self):
1174         "Tries to ssh into nodes in debug mode with the debug ssh key"
1175         return self.check_nodes_ssh(debug = True,
1176                                     timeout_minutes = self.ssh_node_debug_timeout,
1177                                     silent_minutes = self.ssh_node_debug_silent)
1178     
1179     def ssh_node_boot(self):
1180         "Tries to ssh into nodes in production mode with the root ssh key"
1181         return self.check_nodes_ssh(debug = False,
1182                                     timeout_minutes = self.ssh_node_boot_timeout,
1183                                     silent_minutes = self.ssh_node_boot_silent)
1184
1185     def node_bmlogs(self):
1186         "Checks that there's a non-empty dir. /var/log/bm/raw"
1187         return utils.system(self.actual_command_in_guest("ls /var/log/bm/raw")) == 0
1188     
1189     @node_mapper
1190     def qemu_local_init(self): pass
1191     @node_mapper
1192     def bootcd(self): pass
1193     @node_mapper
1194     def qemu_local_config(self): pass
1195     @node_mapper
1196     def qemu_export(self): pass
1197     @node_mapper
1198     def nodestate_reinstall(self): pass
1199     @node_mapper
1200     def nodestate_upgrade(self): pass
1201     @node_mapper
1202     def nodestate_safeboot(self): pass
1203     @node_mapper
1204     def nodestate_boot(self): pass
1205     @node_mapper
1206     def nodestate_show(self): pass
1207     @node_mapper
1208     def nodedistro_f14(self): pass
1209     @node_mapper
1210     def nodedistro_f18(self): pass
1211     @node_mapper
1212     def nodedistro_f20(self): pass
1213     @node_mapper
1214     def nodedistro_f21(self): pass
1215     @node_mapper
1216     def nodedistro_f22(self): pass
1217     @node_mapper
1218     def nodeflavour_show(self): pass
1219     @node_mapper
1220     def nodeplain_on(self): pass
1221     @node_mapper
1222     def nodeplain_off(self): pass
1223     @node_mapper
1224     def nodeplain_show(self): pass
1225         
1226     ### check hooks : invoke scripts from hooks/{node,slice}
1227     def check_hooks_node(self): 
1228         return self.locate_first_node().check_hooks()
1229     def check_hooks_sliver(self) : 
1230         return self.locate_first_sliver().check_hooks()
1231     
1232     def check_hooks(self):
1233         "runs unit tests in the node and slice contexts - see hooks/{node,slice}"
1234         return self.check_hooks_node() and self.check_hooks_sliver()
1235
1236     ### initscripts
1237     def do_check_initscripts(self):
1238         class CompleterTaskInitscript(CompleterTask):
1239             def __init__(self, test_sliver, stamp):
1240                 self.test_sliver = test_sliver
1241                 self.stamp = stamp
1242             def actual_run(self):
1243                 return self.test_sliver.check_initscript_stamp(self.stamp)
1244             def message(self):
1245                 return "initscript checker for {}".format(self.test_sliver.name())
1246             def failure_epilogue(self):
1247                 print("initscript stamp {} not found in sliver {}"\
1248                     .format(self.stamp, self.test_sliver.name()))
1249             
1250         tasks = []
1251         for slice_spec in self.plc_spec['slices']:
1252             if 'initscriptstamp' not in slice_spec:
1253                 continue
1254             stamp = slice_spec['initscriptstamp']
1255             slicename = slice_spec['slice_fields']['name']
1256             for nodename in slice_spec['nodenames']:
1257                 print('nodename', nodename, 'slicename', slicename, 'stamp', stamp)
1258                 site,node = self.locate_node(nodename)
1259                 # xxx - passing the wrong site - probably harmless
1260                 test_site = TestSite(self, site)
1261                 test_slice = TestSlice(self, test_site, slice_spec)
1262                 test_node = TestNode(self, test_site, node)
1263                 test_sliver = TestSliver(self, test_node, test_slice)
1264                 tasks.append(CompleterTaskInitscript(test_sliver, stamp))
1265         return Completer(tasks, message='check_initscripts').\
1266             run (timedelta(minutes=5), timedelta(minutes=4), timedelta(seconds=10))
1267             
1268     def check_initscripts(self):
1269         "check that the initscripts have triggered"
1270         return self.do_check_initscripts()
1271     
1272     def initscripts(self):
1273         "create initscripts with PLCAPI"
1274         for initscript in self.plc_spec['initscripts']:
1275             utils.pprint('Adding Initscript in plc {}'.format(self.plc_spec['name']), initscript)
1276             self.apiserver.AddInitScript(self.auth_root(), initscript['initscript_fields'])
1277         return True
1278
1279     def delete_initscripts(self):
1280         "delete initscripts with PLCAPI"
1281         for initscript in self.plc_spec['initscripts']:
1282             initscript_name = initscript['initscript_fields']['name']
1283             print(('Attempting to delete {} in plc {}'.format(initscript_name, self.plc_spec['name'])))
1284             try:
1285                 self.apiserver.DeleteInitScript(self.auth_root(), initscript_name)
1286                 print(initscript_name, 'deleted')
1287             except:
1288                 print('deletion went wrong - probably did not exist')
1289         return True
1290
1291     ### manage slices
1292     def slices(self):
1293         "create slices with PLCAPI"
1294         return self.do_slices(action="add")
1295
1296     def delete_slices(self):
1297         "delete slices with PLCAPI"
1298         return self.do_slices(action="delete")
1299
1300     def fill_slices(self):
1301         "add nodes in slices with PLCAPI"
1302         return self.do_slices(action="fill")
1303
1304     def empty_slices(self):
1305         "remove nodes from slices with PLCAPI"
1306         return self.do_slices(action="empty")
1307
1308     def do_slices(self,  action="add"):
1309         for slice in self.plc_spec['slices']:
1310             site_spec = self.locate_site(slice['sitename'])
1311             test_site = TestSite(self,site_spec)
1312             test_slice=TestSlice(self,test_site,slice)
1313             if action == "delete":
1314                 test_slice.delete_slice()
1315             elif action == "fill":
1316                 test_slice.add_nodes()
1317             elif action == "empty":
1318                 test_slice.delete_nodes()
1319             else:
1320                 test_slice.create_slice()
1321         return True
1322         
1323     @slice_mapper__tasks(20, 10, 15)
1324     def ssh_slice(self): pass
1325     @slice_mapper__tasks(20, 19, 15)
1326     def ssh_slice_off(self): pass
1327     @slice_mapper__tasks(1, 1, 15)
1328     def slice_fs_present(self): pass
1329     @slice_mapper__tasks(1, 1, 15)
1330     def slice_fs_deleted(self): pass
1331
1332     # use another name so we can exclude/ignore it from the tests on the nightly command line
1333     def ssh_slice_again(self): return self.ssh_slice()
1334     # note that simply doing ssh_slice_again=ssh_slice would kind of work too
1335     # but for some reason the ignore-wrapping thing would not
1336
1337     @slice_mapper
1338     def ssh_slice_basics(self): pass
1339     @slice_mapper
1340     def check_vsys_defaults(self): pass
1341
1342     @node_mapper
1343     def keys_clear_known_hosts(self): pass
1344     
1345     def plcapi_urls(self):
1346         """
1347         attempts to reach the PLCAPI with various forms for the URL
1348         """
1349         return PlcapiUrlScanner(self.auth_root(), ip=self.vserverip).scan()
1350
1351     def speed_up_slices(self):
1352         "tweak nodemanager cycle (wait time) to 30+/-10 s"
1353         return self._speed_up_slices (30, 10)
1354     def super_speed_up_slices(self):
1355         "dev mode: tweak nodemanager cycle (wait time) to 5+/-1 s"
1356         return self._speed_up_slices(5, 1)
1357
1358     def _speed_up_slices(self, p, r):
1359         # create the template on the server-side 
1360         template = "{}.nodemanager".format(self.name())
1361         with open(template,"w") as template_file:
1362             template_file.write('OPTIONS="-p {} -r {} -d"\n'.format(p, r))
1363         in_vm = "/var/www/html/PlanetLabConf/nodemanager"
1364         remote = "{}/{}".format(self.vm_root_in_host(), in_vm)
1365         self.test_ssh.copy_abs(template, remote)
1366         # Add a conf file
1367         if not self.apiserver.GetConfFiles(self.auth_root(),
1368                                            {'dest' : '/etc/sysconfig/nodemanager'}):
1369             self.apiserver.AddConfFile(self.auth_root(),
1370                                         {'dest' : '/etc/sysconfig/nodemanager',
1371                                          'source' : 'PlanetLabConf/nodemanager',
1372                                          'postinstall_cmd' : 'service nm restart',})
1373         return True
1374
1375     def debug_nodemanager(self):
1376         "sets verbose mode for nodemanager, and speeds up cycle even more (needs speed_up_slices first)"
1377         template = "{}.nodemanager".format(self.name())
1378         with open(template,"w") as template_file:
1379             template_file.write('OPTIONS="-p 10 -r 6 -v -d"\n')
1380         in_vm = "/var/www/html/PlanetLabConf/nodemanager"
1381         remote = "{}/{}".format(self.vm_root_in_host(), in_vm)
1382         self.test_ssh.copy_abs(template, remote)
1383         return True
1384
1385     @node_mapper
1386     def qemu_start(self) : pass
1387
1388     @node_mapper
1389     def qemu_timestamp(self) : pass
1390
1391     @node_mapper
1392     def qemu_nodefamily(self): pass
1393
1394     # when a spec refers to a node possibly on another plc
1395     def locate_sliver_obj_cross(self, nodename, slicename, other_plcs):
1396         for plc in [ self ] + other_plcs:
1397             try:
1398                 return plc.locate_sliver_obj(nodename, slicename)
1399             except:
1400                 pass
1401         raise Exception("Cannot locate sliver {}@{} among all PLCs".format(nodename, slicename))
1402
1403     # implement this one as a cross step so that we can take advantage of different nodes
1404     # in multi-plcs mode
1405     def cross_check_tcp(self, other_plcs):
1406         "check TCP connectivity between 2 slices (or in loopback if only one is defined)"
1407         if 'tcp_specs' not in self.plc_spec or not self.plc_spec['tcp_specs']: 
1408             utils.header("check_tcp: no/empty config found")
1409             return True
1410         specs = self.plc_spec['tcp_specs']
1411         overall = True
1412
1413         # first wait for the network to be up and ready from the slices
1414         class CompleterTaskNetworkReadyInSliver(CompleterTask):
1415             def __init__(self, test_sliver):
1416                 self.test_sliver = test_sliver
1417             def actual_run(self):
1418                 return self.test_sliver.check_tcp_ready(port = 9999)
1419             def message(self):
1420                 return "network ready checker for {}".format(self.test_sliver.name())
1421             def failure_epilogue(self):
1422                 print("could not bind port from sliver {}".format(self.test_sliver.name()))
1423
1424         sliver_specs = {}
1425         tasks = []
1426         managed_sliver_names = set()
1427         for spec in specs:
1428             # locate the TestSliver instances involved, and cache them in the spec instance
1429             spec['s_sliver'] = self.locate_sliver_obj_cross(spec['server_node'], spec['server_slice'], other_plcs)
1430             spec['c_sliver'] = self.locate_sliver_obj_cross(spec['client_node'], spec['client_slice'], other_plcs)
1431             message = "Will check TCP between s={} and c={}"\
1432                       .format(spec['s_sliver'].name(), spec['c_sliver'].name())
1433             if 'client_connect' in spec:
1434                 message += " (using {})".format(spec['client_connect'])
1435             utils.header(message)
1436             # we need to check network presence in both slivers, but also
1437             # avoid to insert a sliver several times
1438             for sliver in [ spec['s_sliver'], spec['c_sliver'] ]:
1439                 if sliver.name() not in managed_sliver_names:
1440                     tasks.append(CompleterTaskNetworkReadyInSliver(sliver))
1441                     # add this sliver's name in the set
1442                     managed_sliver_names .update( {sliver.name()} )
1443
1444         # wait for the netork to be OK in all server sides
1445         if not Completer(tasks, message='check for network readiness in slivers').\
1446            run(timedelta(seconds=30), timedelta(seconds=24), period=timedelta(seconds=5)):
1447             return False
1448             
1449         # run server and client
1450         for spec in specs:
1451             port = spec['port']
1452             # server side
1453             # the issue here is that we have the server run in background
1454             # and so we have no clue if it took off properly or not
1455             # looks like in some cases it does not
1456             if not spec['s_sliver'].run_tcp_server(port, timeout=20):
1457                 overall = False
1458                 break
1459
1460             # idem for the client side
1461             # use nodename from located sliver, unless 'client_connect' is set
1462             if 'client_connect' in spec:
1463                 destination = spec['client_connect']
1464             else:
1465                 destination = spec['s_sliver'].test_node.name()
1466             if not spec['c_sliver'].run_tcp_client(destination, port):
1467                 overall = False
1468         return overall
1469
1470     # painfully enough, we need to allow for some time as netflow might show up last
1471     def check_system_slice(self): 
1472         "all nodes: check that a system slice is alive"
1473         # netflow currently not working in the lxc distro
1474         # drl not built at all in the wtx distro
1475         # if we find either of them we're happy
1476         return self.check_netflow() or self.check_drl()
1477     
1478     # expose these
1479     def check_netflow(self): return self._check_system_slice('netflow')
1480     def check_drl(self): return self._check_system_slice('drl')
1481
1482     # we have the slices up already here, so it should not take too long
1483     def _check_system_slice(self, slicename, timeout_minutes=5, period_seconds=15):
1484         class CompleterTaskSystemSlice(CompleterTask):
1485             def __init__(self, test_node, dry_run): 
1486                 self.test_node = test_node
1487                 self.dry_run = dry_run
1488             def actual_run(self): 
1489                 return self.test_node._check_system_slice(slicename, dry_run=self.dry_run)
1490             def message(self): 
1491                 return "System slice {} @ {}".format(slicename, self.test_node.name())
1492             def failure_epilogue(self): 
1493                 print("COULD not find system slice {} @ {}".format(slicename, self.test_node.name()))
1494         timeout = timedelta(minutes=timeout_minutes)
1495         silent  = timedelta(0)
1496         period  = timedelta(seconds=period_seconds)
1497         tasks = [ CompleterTaskSystemSlice(test_node, self.options.dry_run) \
1498                       for test_node in self.all_nodes() ]
1499         return Completer(tasks, message='_check_system_slice').run(timeout, silent, period)
1500
1501     def plcsh_stress_test(self):
1502         "runs PLCAPI stress test, that checks Add/Update/Delete on all types - preserves contents"
1503         # install the stress-test in the plc image
1504         location = "/usr/share/plc_api/plcsh_stress_test.py"
1505         remote = "{}/{}".format(self.vm_root_in_host(), location)
1506         self.test_ssh.copy_abs("plcsh_stress_test.py", remote)
1507         command = location
1508         command += " -- --check"
1509         if self.options.size == 1:
1510             command +=  " --tiny"
1511         return self.run_in_guest(command) == 0
1512
1513     # populate runs the same utility without slightly different options
1514     # in particular runs with --preserve (dont cleanup) and without --check
1515     # also it gets run twice, once with the --foreign option for creating fake foreign entries
1516
1517     def sfa_install_all(self):
1518         "yum install sfa sfa-plc sfa-sfatables sfa-client"
1519         return self.yum_install("sfa sfa-plc sfa-sfatables sfa-client")
1520
1521     def sfa_install_core(self):
1522         "yum install sfa"
1523         return self.yum_install("sfa")
1524         
1525     def sfa_install_plc(self):
1526         "yum install sfa-plc"
1527         return self.yum_install("sfa-plc")
1528         
1529     def sfa_install_sfatables(self):
1530         "yum install sfa-sfatables"
1531         return self.yum_install("sfa-sfatables")
1532
1533     # for some very odd reason, this sometimes fails with the following symptom
1534     # # yum install sfa-client
1535     # Setting up Install Process
1536     # ...
1537     # Downloading Packages:
1538     # Running rpm_check_debug
1539     # Running Transaction Test
1540     # Transaction Test Succeeded
1541     # Running Transaction
1542     # Transaction couldn't start:
1543     # installing package sfa-client-2.1-7.onelab.2012.05.23.i686 needs 68KB on the / filesystem
1544     # [('installing package sfa-client-2.1-7.onelab.2012.05.23.i686 needs 68KB on the / filesystem', (9, '/', 69632L))]
1545     # even though in the same context I have
1546     # [2012.05.23--f14-32-sfastd1-1-vplc07] / # df -h 
1547     # Filesystem            Size  Used Avail Use% Mounted on
1548     # /dev/hdv1             806G  264G  501G  35% /
1549     # none                   16M   36K   16M   1% /tmp
1550     #
1551     # so as a workaround, we first try yum install, and then invoke rpm on the cached rpm...
1552     def sfa_install_client(self):
1553         "yum install sfa-client"
1554         first_try = self.yum_install("sfa-client")
1555         if first_try:
1556             return True
1557         utils.header("********** Regular yum failed - special workaround in place, 2nd chance")
1558         code, cached_rpm_path = \
1559                 utils.output_of(self.actual_command_in_guest('find /var/cache/yum -name sfa-client\*.rpm'))
1560         utils.header("rpm_path=<<{}>>".format(rpm_path))
1561         # just for checking 
1562         self.run_in_guest("rpm -i {}".format(cached_rpm_path))
1563         return self.yum_check_installed("sfa-client")
1564
1565     def sfa_dbclean(self):
1566         "thoroughly wipes off the SFA database"
1567         return self.run_in_guest("sfaadmin reg nuke") == 0 or \
1568             self.run_in_guest("sfa-nuke.py") == 0 or \
1569             self.run_in_guest("sfa-nuke-plc.py") == 0 or \
1570             self.run_in_guest("sfaadmin registry nuke") == 0             
1571
1572     def sfa_fsclean(self):
1573         "cleanup /etc/sfa/trusted_roots and /var/lib/sfa"
1574         self.run_in_guest("rm -rf /etc/sfa/trusted_roots /var/lib/sfa/authorities")
1575         return True
1576
1577     def sfa_plcclean(self):
1578         "cleans the PLC entries that were created as a side effect of running the script"
1579         # ignore result 
1580         sfa_spec = self.plc_spec['sfa']
1581
1582         for auth_sfa_spec in sfa_spec['auth_sfa_specs']:
1583             login_base = auth_sfa_spec['login_base']
1584             try:
1585                 self.apiserver.DeleteSite(self.auth_root(),login_base)
1586             except:
1587                 print("Site {} already absent from PLC db".format(login_base))
1588
1589             for spec_name in ['pi_spec','user_spec']:
1590                 user_spec = auth_sfa_spec[spec_name]
1591                 username = user_spec['email']
1592                 try:
1593                     self.apiserver.DeletePerson(self.auth_root(),username)
1594                 except: 
1595                     # this in fact is expected as sites delete their members
1596                     #print "User {} already absent from PLC db".format(username)
1597                     pass
1598
1599         print("REMEMBER TO RUN sfa_import AGAIN")
1600         return True
1601
1602     def sfa_uninstall(self):
1603         "uses rpm to uninstall sfa - ignore result"
1604         self.run_in_guest("rpm -e sfa sfa-sfatables sfa-client sfa-plc")
1605         self.run_in_guest("rm -rf /var/lib/sfa")
1606         self.run_in_guest("rm -rf /etc/sfa")
1607         self.run_in_guest("rm -rf /var/log/sfa_access.log /var/log/sfa_import_plc.log /var/log/sfa.daemon")
1608         # xxx tmp 
1609         self.run_in_guest("rpm -e --noscripts sfa-plc")
1610         return True
1611
1612     ### run unit tests for SFA
1613     # NOTE: for some reason on f14/i386, yum install sfa-tests fails for no reason
1614     # Running Transaction
1615     # Transaction couldn't start:
1616     # installing package sfa-tests-1.0-21.onelab.i686 needs 204KB on the / filesystem
1617     # [('installing package sfa-tests-1.0-21.onelab.i686 needs 204KB on the / filesystem', (9, '/', 208896L))]
1618     # no matter how many Gbs are available on the testplc
1619     # could not figure out what's wrong, so...
1620     # if the yum install phase fails, consider the test is successful
1621     # other combinations will eventually run it hopefully
1622     def sfa_utest(self):
1623         "yum install sfa-tests and run SFA unittests"
1624         self.run_in_guest("yum -y install sfa-tests")
1625         # failed to install - forget it
1626         if self.run_in_guest("rpm -q sfa-tests") != 0: 
1627             utils.header("WARNING: SFA unit tests failed to install, ignoring")
1628             return True
1629         return self.run_in_guest("/usr/share/sfa/tests/testAll.py") == 0
1630
1631     ###
1632     def confdir(self):
1633         dirname = "conf.{}".format(self.plc_spec['name'])
1634         if not os.path.isdir(dirname):
1635             utils.system("mkdir -p {}".format(dirname))
1636         if not os.path.isdir(dirname):
1637             raise Exception("Cannot create config dir for plc {}".format(self.name()))
1638         return dirname
1639
1640     def conffile(self, filename):
1641         return "{}/{}".format(self.confdir(), filename)
1642     def confsubdir(self, dirname, clean, dry_run=False):
1643         subdirname = "{}/{}".format(self.confdir(), dirname)
1644         if clean:
1645             utils.system("rm -rf {}".format(subdirname))
1646         if not os.path.isdir(subdirname): 
1647             utils.system("mkdir -p {}".format(subdirname))
1648         if not dry_run and not os.path.isdir(subdirname):
1649             raise "Cannot create config subdir {} for plc {}".format(dirname, self.name())
1650         return subdirname
1651         
1652     def conffile_clean(self, filename):
1653         filename=self.conffile(filename)
1654         return utils.system("rm -rf {}".format(filename))==0
1655     
1656     ###
1657     def sfa_configure(self):
1658         "run sfa-config-tty"
1659         tmpname = self.conffile("sfa-config-tty")
1660         with open(tmpname,'w') as fileconf:
1661             for (var,value) in self.plc_spec['sfa']['settings'].items():
1662                 fileconf.write('e {}\n{}\n'.format(var, value))
1663             fileconf.write('w\n')
1664             fileconf.write('R\n')
1665             fileconf.write('q\n')
1666         utils.system('cat {}'.format(tmpname))
1667         self.run_in_guest_piped('cat {}'.format(tmpname), 'sfa-config-tty')
1668         return True
1669
1670     def aggregate_xml_line(self):
1671         port = self.plc_spec['sfa']['neighbours-port']
1672         return '<aggregate addr="{}" hrn="{}" port="{}"/>'\
1673             .format(self.vserverip, self.plc_spec['sfa']['settings']['SFA_REGISTRY_ROOT_AUTH'], port)
1674
1675     def registry_xml_line(self):
1676         return '<registry addr="{}" hrn="{}" port="12345"/>'\
1677             .format(self.vserverip, self.plc_spec['sfa']['settings']['SFA_REGISTRY_ROOT_AUTH'])
1678
1679
1680     # a cross step that takes all other plcs in argument
1681     def cross_sfa_configure(self, other_plcs):
1682         "writes aggregates.xml and registries.xml that point to all other PLCs in the test"
1683         # of course with a single plc, other_plcs is an empty list
1684         if not other_plcs:
1685             return True
1686         agg_fname = self.conffile("agg.xml")
1687         with open(agg_fname,"w") as out:
1688             out.write("<aggregates>{}</aggregates>\n"\
1689                       .format(" ".join([ plc.aggregate_xml_line() for plc in other_plcs ])))
1690         utils.header("(Over)wrote {}".format(agg_fname))
1691         reg_fname=self.conffile("reg.xml")
1692         with open(reg_fname,"w") as out:
1693             out.write("<registries>{}</registries>\n"\
1694                       .format(" ".join([ plc.registry_xml_line() for plc in other_plcs ])))
1695         utils.header("(Over)wrote {}".format(reg_fname))
1696         return self.test_ssh.copy_abs(agg_fname,
1697                                       '/{}/etc/sfa/aggregates.xml'.format(self.vm_root_in_host())) == 0 \
1698            and self.test_ssh.copy_abs(reg_fname,
1699                                       '/{}/etc/sfa/registries.xml'.format(self.vm_root_in_host())) == 0
1700
1701     def sfa_import(self):
1702         "use sfaadmin to import from plc"
1703         auth = self.plc_spec['sfa']['settings']['SFA_REGISTRY_ROOT_AUTH']
1704         return self.run_in_guest('sfaadmin reg import_registry') == 0 
1705
1706     def sfa_start(self):
1707         "service sfa start"
1708         return self.start_service('sfa')
1709
1710
1711     def sfi_configure(self):
1712         "Create /root/sfi on the plc side for sfi client configuration"
1713         if self.options.dry_run: 
1714             utils.header("DRY RUN - skipping step")
1715             return True
1716         sfa_spec = self.plc_spec['sfa']
1717         # cannot use auth_sfa_mapper to pass dir_name
1718         for slice_spec in self.plc_spec['sfa']['auth_sfa_specs']:
1719             test_slice = TestAuthSfa(self, slice_spec)
1720             dir_basename = os.path.basename(test_slice.sfi_path())
1721             dir_name = self.confsubdir("dot-sfi/{}".format(dir_basename),
1722                                        clean=True, dry_run=self.options.dry_run)
1723             test_slice.sfi_configure(dir_name)
1724             # push into the remote /root/sfi area
1725             location = test_slice.sfi_path()
1726             remote = "{}/{}".format(self.vm_root_in_host(), location)
1727             self.test_ssh.mkdir(remote, abs=True)
1728             # need to strip last level or remote otherwise we get an extra dir level
1729             self.test_ssh.copy_abs(dir_name, os.path.dirname(remote), recursive=True)
1730
1731         return True
1732
1733     def sfi_clean(self):
1734         "clean up /root/sfi on the plc side"
1735         self.run_in_guest("rm -rf /root/sfi")
1736         return True
1737
1738     def sfa_rspec_empty(self):
1739         "expose a static empty rspec (ships with the tests module) in the sfi directory"
1740         filename = "empty-rspec.xml"
1741         overall = True
1742         for slice_spec in self.plc_spec['sfa']['auth_sfa_specs']:
1743             test_slice = TestAuthSfa(self, slice_spec)
1744             in_vm = test_slice.sfi_path()
1745             remote = "{}/{}".format(self.vm_root_in_host(), in_vm)
1746             if self.test_ssh.copy_abs(filename, remote) !=0:
1747                 overall = False
1748         return overall
1749
1750     @auth_sfa_mapper
1751     def sfa_register_site(self): pass
1752     @auth_sfa_mapper
1753     def sfa_register_pi(self): pass
1754     @auth_sfa_mapper
1755     def sfa_register_user(self): pass
1756     @auth_sfa_mapper
1757     def sfa_update_user(self): pass
1758     @auth_sfa_mapper
1759     def sfa_register_slice(self): pass
1760     @auth_sfa_mapper
1761     def sfa_renew_slice(self): pass
1762     @auth_sfa_mapper
1763     def sfa_get_expires(self): pass
1764     @auth_sfa_mapper
1765     def sfa_discover(self): pass
1766     @auth_sfa_mapper
1767     def sfa_rspec(self): pass
1768     @auth_sfa_mapper
1769     def sfa_allocate(self): pass
1770     @auth_sfa_mapper
1771     def sfa_allocate_empty(self): pass
1772     @auth_sfa_mapper
1773     def sfa_provision(self): pass
1774     @auth_sfa_mapper
1775     def sfa_provision_empty(self): pass
1776     @auth_sfa_mapper
1777     def sfa_describe(self): pass
1778     @auth_sfa_mapper
1779     def sfa_check_slice_plc(self): pass
1780     @auth_sfa_mapper
1781     def sfa_check_slice_plc_empty(self): pass
1782     @auth_sfa_mapper
1783     def sfa_update_slice(self): pass
1784     @auth_sfa_mapper
1785     def sfa_remove_user_from_slice(self): pass
1786     @auth_sfa_mapper
1787     def sfa_insert_user_in_slice(self): pass
1788     @auth_sfa_mapper
1789     def sfi_list(self): pass
1790     @auth_sfa_mapper
1791     def sfi_show_site(self): pass
1792     @auth_sfa_mapper
1793     def sfi_show_slice(self): pass
1794     @auth_sfa_mapper
1795     def sfi_show_slice_researchers(self): pass
1796     @auth_sfa_mapper
1797     def ssh_slice_sfa(self): pass
1798     @auth_sfa_mapper
1799     def sfa_delete_user(self): pass
1800     @auth_sfa_mapper
1801     def sfa_delete_slice(self): pass
1802
1803     def sfa_stop(self):
1804         "service sfa stop"
1805         return self.stop_service('sfa')
1806
1807     def populate(self):
1808         "creates random entries in the PLCAPI"
1809         # install the stress-test in the plc image
1810         location = "/usr/share/plc_api/plcsh_stress_test.py"
1811         remote = "{}/{}".format(self.vm_root_in_host(), location)
1812         self.test_ssh.copy_abs("plcsh_stress_test.py", remote)
1813         command = location
1814         command += " -- --preserve --short-names"
1815         local = (self.run_in_guest(command) == 0);
1816         # second run with --foreign
1817         command += ' --foreign'
1818         remote = (self.run_in_guest(command) == 0);
1819         return local and remote
1820
1821
1822     ####################
1823     @bonding_redirector
1824     def bonding_init_partial(self): pass
1825
1826     @bonding_redirector
1827     def bonding_add_yum(self): pass
1828
1829     @bonding_redirector
1830     def bonding_install_rpms(self): pass
1831
1832     ####################
1833
1834     def gather_logs(self):
1835         "gets all possible logs from plc's/qemu node's/slice's for future reference"
1836         # (1.a) get the plc's /var/log/ and store it locally in logs/myplc.var-log.<plcname>/*
1837         # (1.b) get the plc's  /var/lib/pgsql/data/pg_log/ -> logs/myplc.pgsql-log.<plcname>/*
1838         # (1.c) get the plc's /root/sfi -> logs/sfi.<plcname>/
1839         # (2) get all the nodes qemu log and store it as logs/node.qemu.<node>.log
1840         # (3) get the nodes /var/log and store is as logs/node.var-log.<node>/*
1841         # (4) as far as possible get the slice's /var/log as logs/sliver.var-log.<sliver>/*
1842         # (1.a)
1843         print("-------------------- TestPlc.gather_logs : PLC's /var/log")
1844         self.gather_var_logs()
1845         # (1.b)
1846         print("-------------------- TestPlc.gather_logs : PLC's /var/lib/psql/data/pg_log/")
1847         self.gather_pgsql_logs()
1848         # (1.c)
1849         print("-------------------- TestPlc.gather_logs : PLC's /root/sfi/")
1850         self.gather_root_sfi()
1851         # (2) 
1852         print("-------------------- TestPlc.gather_logs : nodes's QEMU logs")
1853         for site_spec in self.plc_spec['sites']:
1854             test_site = TestSite(self,site_spec)
1855             for node_spec in site_spec['nodes']:
1856                 test_node = TestNode(self, test_site, node_spec)
1857                 test_node.gather_qemu_logs()
1858         # (3)
1859         print("-------------------- TestPlc.gather_logs : nodes's /var/log")
1860         self.gather_nodes_var_logs()
1861         # (4)
1862         print("-------------------- TestPlc.gather_logs : sample sliver's /var/log")
1863         self.gather_slivers_var_logs()
1864         return True
1865
1866     def gather_slivers_var_logs(self):
1867         for test_sliver in self.all_sliver_objs():
1868             remote = test_sliver.tar_var_logs()
1869             utils.system("mkdir -p logs/sliver.var-log.{}".format(test_sliver.name()))
1870             command = remote + " | tar -C logs/sliver.var-log.{} -xf -".format(test_sliver.name())
1871             utils.system(command)
1872         return True
1873
1874     def gather_var_logs(self):
1875         utils.system("mkdir -p logs/myplc.var-log.{}".format(self.name()))
1876         to_plc = self.actual_command_in_guest("tar -C /var/log/ -cf - .")        
1877         command = to_plc + "| tar -C logs/myplc.var-log.{} -xf -".format(self.name())
1878         utils.system(command)
1879         command = "chmod a+r,a+x logs/myplc.var-log.{}/httpd".format(self.name())
1880         utils.system(command)
1881
1882     def gather_pgsql_logs(self):
1883         utils.system("mkdir -p logs/myplc.pgsql-log.{}".format(self.name()))
1884         to_plc = self.actual_command_in_guest("tar -C /var/lib/pgsql/data/pg_log/ -cf - .")        
1885         command = to_plc + "| tar -C logs/myplc.pgsql-log.{} -xf -".format(self.name())
1886         utils.system(command)
1887
1888     def gather_root_sfi(self):
1889         utils.system("mkdir -p logs/sfi.{}".format(self.name()))
1890         to_plc = self.actual_command_in_guest("tar -C /root/sfi/ -cf - .")        
1891         command = to_plc + "| tar -C logs/sfi.{} -xf -".format(self.name())
1892         utils.system(command)
1893
1894     def gather_nodes_var_logs(self):
1895         for site_spec in self.plc_spec['sites']:
1896             test_site = TestSite(self, site_spec)
1897             for node_spec in site_spec['nodes']:
1898                 test_node = TestNode(self, test_site, node_spec)
1899                 test_ssh = TestSsh(test_node.name(), key="keys/key_admin.rsa")
1900                 command = test_ssh.actual_command("tar -C /var/log -cf - .")
1901                 command = command + "| tar -C logs/node.var-log.{} -xf -".format(test_node.name())
1902                 utils.system("mkdir -p logs/node.var-log.{}".format(test_node.name()))
1903                 utils.system(command)
1904
1905
1906     # returns the filename to use for sql dump/restore, using options.dbname if set
1907     def dbfile(self, database):
1908         # uses options.dbname if it is found
1909         try:
1910             name = self.options.dbname
1911             if not isinstance(name, str):
1912                 raise Exception
1913         except:
1914             t = datetime.now()
1915             d = t.date()
1916             name = str(d)
1917         return "/root/{}-{}.sql".format(database, name)
1918
1919     def plc_db_dump(self):
1920         'dump the planetlab5 DB in /root in the PLC - filename has time'
1921         dump=self.dbfile("planetab5")
1922         self.run_in_guest('pg_dump -U pgsqluser planetlab5 -f '+ dump)
1923         utils.header('Dumped planetlab5 database in {}'.format(dump))
1924         return True
1925
1926     def plc_db_restore(self):
1927         'restore the planetlab5 DB - looks broken, but run -n might help'
1928         dump = self.dbfile("planetab5")
1929         ##stop httpd service
1930         self.run_in_guest('service httpd stop')
1931         # xxx - need another wrapper
1932         self.run_in_guest_piped('echo drop database planetlab5', 'psql --user=pgsqluser template1')
1933         self.run_in_guest('createdb -U postgres --encoding=UNICODE --owner=pgsqluser planetlab5')
1934         self.run_in_guest('psql -U pgsqluser planetlab5 -f ' + dump)
1935         ##starting httpd service
1936         self.run_in_guest('service httpd start')
1937
1938         utils.header('Database restored from ' + dump)
1939
1940     @staticmethod
1941     def create_ignore_steps():
1942         for step in TestPlc.default_steps + TestPlc.other_steps:
1943             # default step can have a plc qualifier
1944             if '@' in step:
1945                 step, qualifier = step.split('@')
1946             # or be defined as forced or ignored by default
1947             for keyword in ['_ignore','_force']:
1948                 if step.endswith(keyword):
1949                     step=step.replace(keyword,'')
1950             if step == SEP or step == SEPSFA :
1951                 continue
1952             method = getattr(TestPlc,step)
1953             name = step + '_ignore'
1954             wrapped = ignore_result(method)
1955 #            wrapped.__doc__ = method.__doc__ + " (run in ignore-result mode)"
1956             setattr(TestPlc, name, wrapped)
1957             
1958 #    @ignore_result
1959 #    def ssh_slice_again_ignore (self): pass
1960 #    @ignore_result
1961 #    def check_initscripts_ignore (self): pass
1962     
1963     def standby_1_through_20(self):
1964         """convenience function to wait for a specified number of minutes"""
1965         pass
1966     @standby_generic 
1967     def standby_1(): pass
1968     @standby_generic 
1969     def standby_2(): pass
1970     @standby_generic 
1971     def standby_3(): pass
1972     @standby_generic 
1973     def standby_4(): pass
1974     @standby_generic 
1975     def standby_5(): pass
1976     @standby_generic 
1977     def standby_6(): pass
1978     @standby_generic 
1979     def standby_7(): pass
1980     @standby_generic 
1981     def standby_8(): pass
1982     @standby_generic 
1983     def standby_9(): pass
1984     @standby_generic 
1985     def standby_10(): pass
1986     @standby_generic 
1987     def standby_11(): pass
1988     @standby_generic 
1989     def standby_12(): pass
1990     @standby_generic 
1991     def standby_13(): pass
1992     @standby_generic 
1993     def standby_14(): pass
1994     @standby_generic 
1995     def standby_15(): pass
1996     @standby_generic 
1997     def standby_16(): pass
1998     @standby_generic 
1999     def standby_17(): pass
2000     @standby_generic 
2001     def standby_18(): pass
2002     @standby_generic 
2003     def standby_19(): pass
2004     @standby_generic 
2005     def standby_20(): pass
2006
2007     # convenience for debugging the test logic
2008     def yes(self): return True
2009     def no(self): return False
2010     def fail(self): return False