get rid of the 'plain' thing altogether
[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_cleanlog', SEP,
166         'qemu_start', 'qemu_timestamp', 'qemu_nodefamily', SEP,
167         'sfa_install_all', 'sfa_configure', 'cross_sfa_configure', 'sfa_start', 'sfa_import', SEPSFA,
168         'sfi_configure@1', 'sfa_register_site@1','sfa_register_pi@1', SEPSFA,
169         'sfa_register_user@1', 'sfa_update_user@1', 'sfa_register_slice@1', 'sfa_renew_slice@1', SEPSFA,
170         'sfa_remove_user_from_slice@1','sfi_show_slice_researchers@1', 
171         'sfa_insert_user_in_slice@1','sfi_show_slice_researchers@1', SEPSFA,
172         'sfa_discover@1', 'sfa_rspec@1', SEPSFA,
173         'sfa_allocate@1', 'sfa_provision@1', 'sfa_describe@1', SEPSFA,
174         'sfa_check_slice_plc@1', 'sfa_update_slice@1', SEPSFA,
175         'sfi_list@1', 'sfi_show_site@1', 'sfa_utest@1', SEPSFA,
176         # we used to run plcsh_stress_test, and then ssh_node_debug and ssh_node_boot
177         # but as the stress test might take a while, we sometimes missed the debug mode..
178         'probe_kvm_iptables',
179         'ping_node', 'ssh_node_debug', 'plcsh_stress_test@1', SEP,
180         'ssh_node_boot', 'node_bmlogs', 'ssh_slice', 'ssh_slice_basics', 'check_initscripts', SEP,
181         'ssh_slice_sfa@1', SEPSFA, 
182         'sfa_rspec_empty@1', 'sfa_allocate_empty@1', 'sfa_provision_empty@1','sfa_check_slice_plc_empty@1', SEPSFA,
183         'sfa_delete_slice@1', 'sfa_delete_user@1', SEPSFA,
184         'cross_check_tcp@1', 'check_system_slice', SEP,
185         # for inspecting the slice while it runs the first time
186         #'fail',
187         # check slices are turned off properly
188         'empty_slices', 'ssh_slice_off', 'slice_fs_deleted_ignore', SEP,
189         # check they are properly re-created with the same name
190         'fill_slices', 'ssh_slice_again', SEP,
191         'gather_logs_force', SEP,
192         ]
193     other_steps = [ 
194         'export', 'show_boxes', 'super_speed_up_slices', SEP,
195         'check_hooks', 'plc_stop', 'plcvm_start', 'plcvm_stop', SEP,
196         'delete_initscripts', 'delete_nodegroups','delete_all_sites', SEP,
197         'delete_sites', 'delete_nodes', 'delete_slices', 'keys_clean', SEP,
198         'delete_leases', 'list_leases', SEP,
199         'populate', SEP,
200         'nodestate_show','nodestate_safeboot','nodestate_boot', 'nodestate_upgrade', SEP,
201         'nodeflavour_show','nodedistro_f14','nodedistro_f18', SEP,
202         'nodedistro_f20', 'nodedistro_f21','nodedistro_f22', 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
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_string=" ".join(pkgs_list)
731         return self.yum_install(pkgs_list)
732
733     def install_syslinux6(self):
734         """
735         install syslinux6 from the fedora21 release
736         """
737         key = 'http://mirror.onelab.eu/keys/RPM-GPG-KEY-fedora-21-primary'
738
739         rpms = [ 
740             'http://mirror.onelab.eu/fedora/releases/21/Everything/x86_64/os/Packages/s/syslinux-6.03-1.fc21.x86_64.rpm',
741             'http://mirror.onelab.eu/fedora/releases/21/Everything/x86_64/os/Packages/s/syslinux-nonlinux-6.03-1.fc21.noarch.rpm',
742             'http://mirror.onelab.eu/fedora/releases/21/Everything/x86_64/os/Packages/s/syslinux-perl-6.03-1.fc21.x86_64.rpm',
743         ]
744         # this can be done several times
745         self.run_in_guest("rpm --import {key}".format(**locals()))
746         return self.run_in_guest("yum -y localinstall {}".format(" ".join(rpms))) == 0
747
748     def bonding_builds(self):
749         """
750         list /etc/yum.repos.d on the myplc side
751         """
752         self.run_in_guest("ls /etc/yum.repos.d/*partial.repo")
753         return True
754
755     def bonding_nodes(self):
756         """
757         List nodes known to the myplc together with their nodefamiliy
758         """
759         print("---------------------------------------- nodes")
760         for node in self.apiserver.GetNodes(self.auth_root()):
761             print("{} -> {}".format(node['hostname'],
762                                     self.apiserver.GetNodeFlavour(self.auth_root(),node['hostname'])['nodefamily']))
763         print("---------------------------------------- nodes")
764             
765     
766     ###
767     def mod_python(self):
768         """yum install mod_python, useful on f18 and above so as to avoid broken wsgi"""
769         return self.yum_install( ['mod_python'] )
770
771     ### 
772     def plc_configure(self):
773         "run plc-config-tty"
774         tmpname = '{}.plc-config-tty'.format(self.name())
775         with open(tmpname,'w') as fileconf:
776             for (var,value) in self.plc_spec['settings'].items():
777                 fileconf.write('e {}\n{}\n'.format(var, value))
778             fileconf.write('w\n')
779             fileconf.write('q\n')
780         utils.system('cat {}'.format(tmpname))
781         self.run_in_guest_piped('cat {}'.format(tmpname), 'plc-config-tty')
782         utils.system('rm {}'.format(tmpname))
783         return True
784
785 # f14 is a bit odd in this respect, although this worked fine in guests up to f18
786 # however using a vplc guest under f20 requires this trick
787 # the symptom is this: service plc start
788 # Starting plc (via systemctl):  Failed to get D-Bus connection: \
789 #    Failed to connect to socket /org/freedesktop/systemd1/private: Connection refused
790 # weird thing is the doc says f14 uses upstart by default and not systemd
791 # so this sounds kind of harmless
792     def start_service(self, service):
793         return self.start_stop_service(service, 'start')
794     def stop_service(self, service):
795         return self.start_stop_service(service, 'stop')
796
797     def start_stop_service(self, service, start_or_stop):
798         "utility to start/stop a service with the special trick for f14"
799         if self.options.fcdistro != 'f14':
800             return self.run_in_guest("service {} {}".format(service, start_or_stop)) == 0
801         else:
802             # patch /sbin/service so it does not reset environment
803             self.run_in_guest('sed -i -e \\"s,env -i,env,\\" /sbin/service')
804             # this is because our own scripts in turn call service 
805             return self.run_in_guest("SYSTEMCTL_SKIP_REDIRECT=true service {} {}"\
806                                      .format(service, start_or_stop)) == 0
807
808     def plc_start(self):
809         "service plc start"
810         return self.start_service('plc')
811
812     def plc_stop(self):
813         "service plc stop"
814         return self.stop_service('plc')
815
816     def plcvm_start(self):
817         "start the PLC vserver"
818         self.start_guest()
819         return True
820
821     def plcvm_stop(self):
822         "stop the PLC vserver"
823         self.stop_guest()
824         return True
825
826     # stores the keys from the config for further use
827     def keys_store(self):
828         "stores test users ssh keys in keys/"
829         for key_spec in self.plc_spec['keys']:
830                 TestKey(self,key_spec).store_key()
831         return True
832
833     def keys_clean(self):
834         "removes keys cached in keys/"
835         utils.system("rm -rf ./keys")
836         return True
837
838     # fetches the ssh keys in the plc's /etc/planetlab and stores them in keys/
839     # for later direct access to the nodes
840     def keys_fetch(self):
841         "gets ssh keys in /etc/planetlab/ and stores them locally in keys/"
842         dir="./keys"
843         if not os.path.isdir(dir):
844             os.mkdir(dir)
845         vservername = self.vservername
846         vm_root = self.vm_root_in_host()
847         overall = True
848         prefix = 'debug_ssh_key'
849         for ext in ['pub', 'rsa'] :
850             src = "{vm_root}/etc/planetlab/{prefix}.{ext}".format(**locals())
851             dst = "keys/{vservername}-debug.{ext}".format(**locals())
852             if self.test_ssh.fetch(src, dst) != 0:
853                 overall=False
854         return overall
855
856     def sites(self):
857         "create sites with PLCAPI"
858         return self.do_sites()
859     
860     def delete_sites(self):
861         "delete sites with PLCAPI"
862         return self.do_sites(action="delete")
863     
864     def do_sites(self, action="add"):
865         for site_spec in self.plc_spec['sites']:
866             test_site = TestSite(self,site_spec)
867             if (action != "add"):
868                 utils.header("Deleting site {} in {}".format(test_site.name(), self.name()))
869                 test_site.delete_site()
870                 # deleted with the site
871                 #test_site.delete_users()
872                 continue
873             else:
874                 utils.header("Creating site {} & users in {}".format(test_site.name(), self.name()))
875                 test_site.create_site()
876                 test_site.create_users()
877         return True
878
879     def delete_all_sites(self):
880         "Delete all sites in PLC, and related objects"
881         print('auth_root', self.auth_root())
882         sites = self.apiserver.GetSites(self.auth_root(), {}, ['site_id','login_base'])
883         for site in sites:
884             # keep automatic site - otherwise we shoot in our own foot, root_auth is not valid anymore
885             if site['login_base'] == self.plc_spec['settings']['PLC_SLICE_PREFIX']:
886                 continue
887             site_id = site['site_id']
888             print('Deleting site_id', site_id)
889             self.apiserver.DeleteSite(self.auth_root(), site_id)
890         return True
891
892     def nodes(self):
893         "create nodes with PLCAPI"
894         return self.do_nodes()
895     def delete_nodes(self):
896         "delete nodes with PLCAPI"
897         return self.do_nodes(action="delete")
898
899     def do_nodes(self, action="add"):
900         for site_spec in self.plc_spec['sites']:
901             test_site = TestSite(self, site_spec)
902             if action != "add":
903                 utils.header("Deleting nodes in site {}".format(test_site.name()))
904                 for node_spec in site_spec['nodes']:
905                     test_node = TestNode(self, test_site, node_spec)
906                     utils.header("Deleting {}".format(test_node.name()))
907                     test_node.delete_node()
908             else:
909                 utils.header("Creating nodes for site {} in {}".format(test_site.name(), self.name()))
910                 for node_spec in site_spec['nodes']:
911                     utils.pprint('Creating node {}'.format(node_spec), node_spec)
912                     test_node = TestNode(self, test_site, node_spec)
913                     test_node.create_node()
914         return True
915
916     def nodegroups(self):
917         "create nodegroups with PLCAPI"
918         return self.do_nodegroups("add")
919     def delete_nodegroups(self):
920         "delete nodegroups with PLCAPI"
921         return self.do_nodegroups("delete")
922
923     YEAR = 365*24*3600
924     @staticmethod
925     def translate_timestamp(start, grain, timestamp):
926         if timestamp < TestPlc.YEAR:
927             return start + timestamp*grain
928         else:
929             return timestamp
930
931     @staticmethod
932     def timestamp_printable(timestamp):
933         return time.strftime('%m-%d %H:%M:%S UTC', time.gmtime(timestamp))
934
935     def leases(self):
936         "create leases (on reservable nodes only, use e.g. run -c default -c resa)"
937         now = int(time.time())
938         grain = self.apiserver.GetLeaseGranularity(self.auth_root())
939         print('API answered grain=', grain)
940         start = (now//grain)*grain
941         start += grain
942         # find out all nodes that are reservable
943         nodes = self.all_reservable_nodenames()
944         if not nodes: 
945             utils.header("No reservable node found - proceeding without leases")
946             return True
947         ok = True
948         # attach them to the leases as specified in plc_specs
949         # this is where the 'leases' field gets interpreted as relative of absolute
950         for lease_spec in self.plc_spec['leases']:
951             # skip the ones that come with a null slice id
952             if not lease_spec['slice']:
953                 continue
954             lease_spec['t_from']  = TestPlc.translate_timestamp(start, grain, lease_spec['t_from'])
955             lease_spec['t_until'] = TestPlc.translate_timestamp(start, grain, lease_spec['t_until'])
956             lease_addition = self.apiserver.AddLeases(self.auth_root(), nodes, lease_spec['slice'],
957                                                       lease_spec['t_from'], lease_spec['t_until'])
958             if lease_addition['errors']:
959                 utils.header("Cannot create leases, {}".format(lease_addition['errors']))
960                 ok = False
961             else:
962                 utils.header('Leases on nodes {} for {} from {:d} ({}) until {:d} ({})'\
963                              .format(nodes, lease_spec['slice'],
964                                      lease_spec['t_from'],  TestPlc.timestamp_printable(lease_spec['t_from']),
965                                      lease_spec['t_until'], TestPlc.timestamp_printable(lease_spec['t_until'])))
966                 
967         return ok
968
969     def delete_leases(self):
970         "remove all leases in the myplc side"
971         lease_ids = [ l['lease_id'] for l in self.apiserver.GetLeases(self.auth_root())]
972         utils.header("Cleaning leases {}".format(lease_ids))
973         self.apiserver.DeleteLeases(self.auth_root(), lease_ids)
974         return True
975
976     def list_leases(self):
977         "list all leases known to the myplc"
978         leases = self.apiserver.GetLeases(self.auth_root())
979         now = int(time.time())
980         for l in leases:
981             current = l['t_until'] >= now
982             if self.options.verbose or current:
983                 utils.header("{} {} from {} until {}"\
984                              .format(l['hostname'], l['name'],
985                                      TestPlc.timestamp_printable(l['t_from']), 
986                                      TestPlc.timestamp_printable(l['t_until'])))
987         return True
988
989     # create nodegroups if needed, and populate
990     def do_nodegroups(self, action="add"):
991         # 1st pass to scan contents
992         groups_dict = {}
993         for site_spec in self.plc_spec['sites']:
994             test_site = TestSite(self,site_spec)
995             for node_spec in site_spec['nodes']:
996                 test_node = TestNode(self, test_site, node_spec)
997                 if 'nodegroups' in node_spec:
998                     nodegroupnames = node_spec['nodegroups']
999                     if isinstance(nodegroupnames, str):
1000                         nodegroupnames = [ nodegroupnames ]
1001                     for nodegroupname in nodegroupnames:
1002                         if nodegroupname not in groups_dict:
1003                             groups_dict[nodegroupname] = []
1004                         groups_dict[nodegroupname].append(test_node.name())
1005         auth = self.auth_root()
1006         overall = True
1007         for (nodegroupname,group_nodes) in groups_dict.items():
1008             if action == "add":
1009                 print('nodegroups:', 'dealing with nodegroup',\
1010                     nodegroupname, 'on nodes', group_nodes)
1011                 # first, check if the nodetagtype is here
1012                 tag_types = self.apiserver.GetTagTypes(auth, {'tagname':nodegroupname})
1013                 if tag_types:
1014                     tag_type_id = tag_types[0]['tag_type_id']
1015                 else:
1016                     tag_type_id = self.apiserver.AddTagType(auth,
1017                                                             {'tagname' : nodegroupname,
1018                                                              'description' : 'for nodegroup {}'.format(nodegroupname),
1019                                                              'category' : 'test'})
1020                 print('located tag (type)', nodegroupname, 'as', tag_type_id)
1021                 # create nodegroup
1022                 nodegroups = self.apiserver.GetNodeGroups(auth, {'groupname' : nodegroupname})
1023                 if not nodegroups:
1024                     self.apiserver.AddNodeGroup(auth, nodegroupname, tag_type_id, 'yes')
1025                     print('created nodegroup', nodegroupname, \
1026                         'from tagname', nodegroupname, 'and value', 'yes')
1027                 # set node tag on all nodes, value='yes'
1028                 for nodename in group_nodes:
1029                     try:
1030                         self.apiserver.AddNodeTag(auth, nodename, nodegroupname, "yes")
1031                     except:
1032                         traceback.print_exc()
1033                         print('node', nodename, 'seems to already have tag', nodegroupname)
1034                     # check anyway
1035                     try:
1036                         expect_yes = self.apiserver.GetNodeTags(auth,
1037                                                                 {'hostname' : nodename,
1038                                                                  'tagname'  : nodegroupname},
1039                                                                 ['value'])[0]['value']
1040                         if expect_yes != "yes":
1041                             print('Mismatch node tag on node',nodename,'got',expect_yes)
1042                             overall = False
1043                     except:
1044                         if not self.options.dry_run:
1045                             print('Cannot find tag', nodegroupname, 'on node', nodename)
1046                             overall = False
1047             else:
1048                 try:
1049                     print('cleaning nodegroup', nodegroupname)
1050                     self.apiserver.DeleteNodeGroup(auth, nodegroupname)
1051                 except:
1052                     traceback.print_exc()
1053                     overall = False
1054         return overall
1055
1056     # a list of TestNode objs
1057     def all_nodes(self):
1058         nodes=[]
1059         for site_spec in self.plc_spec['sites']:
1060             test_site = TestSite(self,site_spec)
1061             for node_spec in site_spec['nodes']:
1062                 nodes.append(TestNode(self, test_site, node_spec))
1063         return nodes
1064
1065     # return a list of tuples (nodename,qemuname)
1066     def all_node_infos(self) :
1067         node_infos = []
1068         for site_spec in self.plc_spec['sites']:
1069             node_infos += [ (node_spec['node_fields']['hostname'], node_spec['host_box']) \
1070                                 for node_spec in site_spec['nodes'] ]
1071         return node_infos
1072     
1073     def all_nodenames(self):
1074         return [ x[0] for x in self.all_node_infos() ]
1075     def all_reservable_nodenames(self): 
1076         res = []
1077         for site_spec in self.plc_spec['sites']:
1078             for node_spec in site_spec['nodes']:
1079                 node_fields = node_spec['node_fields']
1080                 if 'node_type' in node_fields and node_fields['node_type'] == 'reservable':
1081                     res.append(node_fields['hostname'])
1082         return res
1083
1084     # silent_minutes : during the first <silent_minutes> minutes nothing gets printed
1085     def nodes_check_boot_state(self, target_boot_state, timeout_minutes,
1086                                silent_minutes, period_seconds = 15):
1087         if self.options.dry_run:
1088             print('dry_run')
1089             return True
1090
1091         class CompleterTaskBootState(CompleterTask):
1092             def __init__(self, test_plc, hostname):
1093                 self.test_plc = test_plc
1094                 self.hostname = hostname
1095                 self.last_boot_state = 'undef'
1096             def actual_run(self):
1097                 try:
1098                     node = self.test_plc.apiserver.GetNodes(self.test_plc.auth_root(),
1099                                                             [ self.hostname ],
1100                                                             ['boot_state'])[0]
1101                     self.last_boot_state = node['boot_state'] 
1102                     return self.last_boot_state == target_boot_state
1103                 except:
1104                     return False
1105             def message(self):
1106                 return "CompleterTaskBootState with node {}".format(self.hostname)
1107             def failure_epilogue(self):
1108                 print("node {} in state {} - expected {}"\
1109                     .format(self.hostname, self.last_boot_state, target_boot_state))
1110                 
1111         timeout = timedelta(minutes=timeout_minutes)
1112         graceout = timedelta(minutes=silent_minutes)
1113         period   = timedelta(seconds=period_seconds)
1114         # the nodes that haven't checked yet - start with a full list and shrink over time
1115         utils.header("checking nodes boot state (expected {})".format(target_boot_state))
1116         tasks = [ CompleterTaskBootState(self,hostname) \
1117                       for (hostname,_) in self.all_node_infos() ]
1118         message = 'check_boot_state={}'.format(target_boot_state)
1119         return Completer(tasks, message=message).run(timeout, graceout, period)
1120
1121     def nodes_booted(self):
1122         return self.nodes_check_boot_state('boot', timeout_minutes=30, silent_minutes=28)
1123
1124     def probe_kvm_iptables(self):
1125         (_,kvmbox) = self.all_node_infos()[0]
1126         TestSsh(kvmbox).run("iptables-save")
1127         return True
1128
1129     # probing nodes
1130     def check_nodes_ping(self, timeout_seconds=60, period_seconds=10):
1131         class CompleterTaskPingNode(CompleterTask):
1132             def __init__(self, hostname):
1133                 self.hostname = hostname
1134             def run(self, silent):
1135                 command="ping -c 1 -w 1 {} >& /dev/null".format(self.hostname)
1136                 return utils.system(command, silent=silent) == 0
1137             def failure_epilogue(self):
1138                 print("Cannot ping node with name {}".format(self.hostname))
1139         timeout = timedelta(seconds = timeout_seconds)
1140         graceout = timeout
1141         period = timedelta(seconds = period_seconds)
1142         node_infos = self.all_node_infos()
1143         tasks = [ CompleterTaskPingNode(h) for (h,_) in node_infos ]
1144         return Completer(tasks, message='ping_node').run(timeout, graceout, period)
1145
1146     # ping node before we try to reach ssh, helpful for troubleshooting failing bootCDs
1147     def ping_node(self):
1148         "Ping nodes"
1149         return self.check_nodes_ping()
1150
1151     def check_nodes_ssh(self, debug, timeout_minutes, silent_minutes, period_seconds=15):
1152         # various delays 
1153         timeout  = timedelta(minutes=timeout_minutes)
1154         graceout = timedelta(minutes=silent_minutes)
1155         period   = timedelta(seconds=period_seconds)
1156         vservername = self.vservername
1157         if debug: 
1158             message = "debug"
1159             completer_message = 'ssh_node_debug'
1160             local_key = "keys/{vservername}-debug.rsa".format(**locals())
1161         else: 
1162             message = "boot"
1163             completer_message = 'ssh_node_boot'
1164             local_key = "keys/key_admin.rsa"
1165         utils.header("checking ssh access to nodes (expected in {} mode)".format(message))
1166         node_infos = self.all_node_infos()
1167         tasks = [ CompleterTaskNodeSsh(nodename, qemuname, local_key,
1168                                         boot_state=message, dry_run=self.options.dry_run) \
1169                       for (nodename, qemuname) in node_infos ]
1170         return Completer(tasks, message=completer_message).run(timeout, graceout, period)
1171         
1172     def ssh_node_debug(self):
1173         "Tries to ssh into nodes in debug mode with the debug ssh key"
1174         return self.check_nodes_ssh(debug = True,
1175                                     timeout_minutes = self.ssh_node_debug_timeout,
1176                                     silent_minutes = self.ssh_node_debug_silent)
1177     
1178     def ssh_node_boot(self):
1179         "Tries to ssh into nodes in production mode with the root ssh key"
1180         return self.check_nodes_ssh(debug = False,
1181                                     timeout_minutes = self.ssh_node_boot_timeout,
1182                                     silent_minutes = self.ssh_node_boot_silent)
1183
1184     def node_bmlogs(self):
1185         "Checks that there's a non-empty dir. /var/log/bm/raw"
1186         return utils.system(self.actual_command_in_guest("ls /var/log/bm/raw")) == 0
1187     
1188     @node_mapper
1189     def qemu_local_init(self): pass
1190     @node_mapper
1191     def bootcd(self): pass
1192     @node_mapper
1193     def qemu_local_config(self): pass
1194     @node_mapper
1195     def qemu_export(self): pass
1196     @node_mapper
1197     def qemu_cleanlog(self): pass
1198     @node_mapper
1199     def nodestate_reinstall(self): pass
1200     @node_mapper
1201     def nodestate_upgrade(self): pass
1202     @node_mapper
1203     def nodestate_safeboot(self): pass
1204     @node_mapper
1205     def nodestate_boot(self): pass
1206     @node_mapper
1207     def nodestate_show(self): pass
1208     @node_mapper
1209     def nodedistro_f14(self): pass
1210     @node_mapper
1211     def nodedistro_f18(self): pass
1212     @node_mapper
1213     def nodedistro_f20(self): pass
1214     @node_mapper
1215     def nodedistro_f21(self): pass
1216     @node_mapper
1217     def nodedistro_f22(self): pass
1218     @node_mapper
1219     def nodeflavour_show(self): pass
1220         
1221     ### check hooks : invoke scripts from hooks/{node,slice}
1222     def check_hooks_node(self): 
1223         return self.locate_first_node().check_hooks()
1224     def check_hooks_sliver(self) : 
1225         return self.locate_first_sliver().check_hooks()
1226     
1227     def check_hooks(self):
1228         "runs unit tests in the node and slice contexts - see hooks/{node,slice}"
1229         return self.check_hooks_node() and self.check_hooks_sliver()
1230
1231     ### initscripts
1232     def do_check_initscripts(self):
1233         class CompleterTaskInitscript(CompleterTask):
1234             def __init__(self, test_sliver, stamp):
1235                 self.test_sliver = test_sliver
1236                 self.stamp = stamp
1237             def actual_run(self):
1238                 return self.test_sliver.check_initscript_stamp(self.stamp)
1239             def message(self):
1240                 return "initscript checker for {}".format(self.test_sliver.name())
1241             def failure_epilogue(self):
1242                 print("initscript stamp {} not found in sliver {}"\
1243                     .format(self.stamp, self.test_sliver.name()))
1244             
1245         tasks = []
1246         for slice_spec in self.plc_spec['slices']:
1247             if 'initscriptstamp' not in slice_spec:
1248                 continue
1249             stamp = slice_spec['initscriptstamp']
1250             slicename = slice_spec['slice_fields']['name']
1251             for nodename in slice_spec['nodenames']:
1252                 print('nodename', nodename, 'slicename', slicename, 'stamp', stamp)
1253                 site,node = self.locate_node(nodename)
1254                 # xxx - passing the wrong site - probably harmless
1255                 test_site = TestSite(self, site)
1256                 test_slice = TestSlice(self, test_site, slice_spec)
1257                 test_node = TestNode(self, test_site, node)
1258                 test_sliver = TestSliver(self, test_node, test_slice)
1259                 tasks.append(CompleterTaskInitscript(test_sliver, stamp))
1260         return Completer(tasks, message='check_initscripts').\
1261             run (timedelta(minutes=5), timedelta(minutes=4), timedelta(seconds=10))
1262             
1263     def check_initscripts(self):
1264         "check that the initscripts have triggered"
1265         return self.do_check_initscripts()
1266     
1267     def initscripts(self):
1268         "create initscripts with PLCAPI"
1269         for initscript in self.plc_spec['initscripts']:
1270             utils.pprint('Adding Initscript in plc {}'.format(self.plc_spec['name']), initscript)
1271             self.apiserver.AddInitScript(self.auth_root(), initscript['initscript_fields'])
1272         return True
1273
1274     def delete_initscripts(self):
1275         "delete initscripts with PLCAPI"
1276         for initscript in self.plc_spec['initscripts']:
1277             initscript_name = initscript['initscript_fields']['name']
1278             print(('Attempting to delete {} in plc {}'.format(initscript_name, self.plc_spec['name'])))
1279             try:
1280                 self.apiserver.DeleteInitScript(self.auth_root(), initscript_name)
1281                 print(initscript_name, 'deleted')
1282             except:
1283                 print('deletion went wrong - probably did not exist')
1284         return True
1285
1286     ### manage slices
1287     def slices(self):
1288         "create slices with PLCAPI"
1289         return self.do_slices(action="add")
1290
1291     def delete_slices(self):
1292         "delete slices with PLCAPI"
1293         return self.do_slices(action="delete")
1294
1295     def fill_slices(self):
1296         "add nodes in slices with PLCAPI"
1297         return self.do_slices(action="fill")
1298
1299     def empty_slices(self):
1300         "remove nodes from slices with PLCAPI"
1301         return self.do_slices(action="empty")
1302
1303     def do_slices(self,  action="add"):
1304         for slice in self.plc_spec['slices']:
1305             site_spec = self.locate_site(slice['sitename'])
1306             test_site = TestSite(self,site_spec)
1307             test_slice=TestSlice(self,test_site,slice)
1308             if action == "delete":
1309                 test_slice.delete_slice()
1310             elif action == "fill":
1311                 test_slice.add_nodes()
1312             elif action == "empty":
1313                 test_slice.delete_nodes()
1314             else:
1315                 test_slice.create_slice()
1316         return True
1317         
1318     @slice_mapper__tasks(20, 10, 15)
1319     def ssh_slice(self): pass
1320     @slice_mapper__tasks(20, 19, 15)
1321     def ssh_slice_off(self): pass
1322     @slice_mapper__tasks(1, 1, 15)
1323     def slice_fs_present(self): pass
1324     @slice_mapper__tasks(1, 1, 15)
1325     def slice_fs_deleted(self): pass
1326
1327     # use another name so we can exclude/ignore it from the tests on the nightly command line
1328     def ssh_slice_again(self): return self.ssh_slice()
1329     # note that simply doing ssh_slice_again=ssh_slice would kind of work too
1330     # but for some reason the ignore-wrapping thing would not
1331
1332     @slice_mapper
1333     def ssh_slice_basics(self): pass
1334     @slice_mapper
1335     def check_vsys_defaults(self): pass
1336
1337     @node_mapper
1338     def keys_clear_known_hosts(self): pass
1339     
1340     def plcapi_urls(self):
1341         """
1342         attempts to reach the PLCAPI with various forms for the URL
1343         """
1344         return PlcapiUrlScanner(self.auth_root(), ip=self.vserverip).scan()
1345
1346     def speed_up_slices(self):
1347         "tweak nodemanager cycle (wait time) to 30+/-10 s"
1348         return self._speed_up_slices (30, 10)
1349     def super_speed_up_slices(self):
1350         "dev mode: tweak nodemanager cycle (wait time) to 5+/-1 s"
1351         return self._speed_up_slices(5, 1)
1352
1353     def _speed_up_slices(self, p, r):
1354         # create the template on the server-side 
1355         template = "{}.nodemanager".format(self.name())
1356         with open(template,"w") as template_file:
1357             template_file.write('OPTIONS="-p {} -r {} -d"\n'.format(p, r))
1358         in_vm = "/var/www/html/PlanetLabConf/nodemanager"
1359         remote = "{}/{}".format(self.vm_root_in_host(), in_vm)
1360         self.test_ssh.copy_abs(template, remote)
1361         # Add a conf file
1362         if not self.apiserver.GetConfFiles(self.auth_root(),
1363                                            {'dest' : '/etc/sysconfig/nodemanager'}):
1364             self.apiserver.AddConfFile(self.auth_root(),
1365                                         {'dest' : '/etc/sysconfig/nodemanager',
1366                                          'source' : 'PlanetLabConf/nodemanager',
1367                                          'postinstall_cmd' : 'service nm restart',})
1368         return True
1369
1370     def debug_nodemanager(self):
1371         "sets verbose mode for nodemanager, and speeds up cycle even more (needs speed_up_slices first)"
1372         template = "{}.nodemanager".format(self.name())
1373         with open(template,"w") as template_file:
1374             template_file.write('OPTIONS="-p 10 -r 6 -v -d"\n')
1375         in_vm = "/var/www/html/PlanetLabConf/nodemanager"
1376         remote = "{}/{}".format(self.vm_root_in_host(), in_vm)
1377         self.test_ssh.copy_abs(template, remote)
1378         return True
1379
1380     @node_mapper
1381     def qemu_start(self) : pass
1382
1383     @node_mapper
1384     def qemu_timestamp(self) : pass
1385
1386     @node_mapper
1387     def qemu_nodefamily(self): pass
1388
1389     # when a spec refers to a node possibly on another plc
1390     def locate_sliver_obj_cross(self, nodename, slicename, other_plcs):
1391         for plc in [ self ] + other_plcs:
1392             try:
1393                 return plc.locate_sliver_obj(nodename, slicename)
1394             except:
1395                 pass
1396         raise Exception("Cannot locate sliver {}@{} among all PLCs".format(nodename, slicename))
1397
1398     # implement this one as a cross step so that we can take advantage of different nodes
1399     # in multi-plcs mode
1400     def cross_check_tcp(self, other_plcs):
1401         "check TCP connectivity between 2 slices (or in loopback if only one is defined)"
1402         if 'tcp_specs' not in self.plc_spec or not self.plc_spec['tcp_specs']: 
1403             utils.header("check_tcp: no/empty config found")
1404             return True
1405         specs = self.plc_spec['tcp_specs']
1406         overall = True
1407
1408         # first wait for the network to be up and ready from the slices
1409         class CompleterTaskNetworkReadyInSliver(CompleterTask):
1410             def __init__(self, test_sliver):
1411                 self.test_sliver = test_sliver
1412             def actual_run(self):
1413                 return self.test_sliver.check_tcp_ready(port = 9999)
1414             def message(self):
1415                 return "network ready checker for {}".format(self.test_sliver.name())
1416             def failure_epilogue(self):
1417                 print("could not bind port from sliver {}".format(self.test_sliver.name()))
1418
1419         sliver_specs = {}
1420         tasks = []
1421         managed_sliver_names = set()
1422         for spec in specs:
1423             # locate the TestSliver instances involved, and cache them in the spec instance
1424             spec['s_sliver'] = self.locate_sliver_obj_cross(spec['server_node'], spec['server_slice'], other_plcs)
1425             spec['c_sliver'] = self.locate_sliver_obj_cross(spec['client_node'], spec['client_slice'], other_plcs)
1426             message = "Will check TCP between s={} and c={}"\
1427                       .format(spec['s_sliver'].name(), spec['c_sliver'].name())
1428             if 'client_connect' in spec:
1429                 message += " (using {})".format(spec['client_connect'])
1430             utils.header(message)
1431             # we need to check network presence in both slivers, but also
1432             # avoid to insert a sliver several times
1433             for sliver in [ spec['s_sliver'], spec['c_sliver'] ]:
1434                 if sliver.name() not in managed_sliver_names:
1435                     tasks.append(CompleterTaskNetworkReadyInSliver(sliver))
1436                     # add this sliver's name in the set
1437                     managed_sliver_names .update( {sliver.name()} )
1438
1439         # wait for the netork to be OK in all server sides
1440         if not Completer(tasks, message='check for network readiness in slivers').\
1441            run(timedelta(seconds=30), timedelta(seconds=24), period=timedelta(seconds=5)):
1442             return False
1443             
1444         # run server and client
1445         for spec in specs:
1446             port = spec['port']
1447             # server side
1448             # the issue here is that we have the server run in background
1449             # and so we have no clue if it took off properly or not
1450             # looks like in some cases it does not
1451             if not spec['s_sliver'].run_tcp_server(port, timeout=20):
1452                 overall = False
1453                 break
1454
1455             # idem for the client side
1456             # use nodename from located sliver, unless 'client_connect' is set
1457             if 'client_connect' in spec:
1458                 destination = spec['client_connect']
1459             else:
1460                 destination = spec['s_sliver'].test_node.name()
1461             if not spec['c_sliver'].run_tcp_client(destination, port):
1462                 overall = False
1463         return overall
1464
1465     # painfully enough, we need to allow for some time as netflow might show up last
1466     def check_system_slice(self): 
1467         "all nodes: check that a system slice is alive"
1468         # netflow currently not working in the lxc distro
1469         # drl not built at all in the wtx distro
1470         # if we find either of them we're happy
1471         return self.check_netflow() or self.check_drl()
1472     
1473     # expose these
1474     def check_netflow(self): return self._check_system_slice('netflow')
1475     def check_drl(self): return self._check_system_slice('drl')
1476
1477     # we have the slices up already here, so it should not take too long
1478     def _check_system_slice(self, slicename, timeout_minutes=5, period_seconds=15):
1479         class CompleterTaskSystemSlice(CompleterTask):
1480             def __init__(self, test_node, dry_run): 
1481                 self.test_node = test_node
1482                 self.dry_run = dry_run
1483             def actual_run(self): 
1484                 return self.test_node._check_system_slice(slicename, dry_run=self.dry_run)
1485             def message(self): 
1486                 return "System slice {} @ {}".format(slicename, self.test_node.name())
1487             def failure_epilogue(self): 
1488                 print("COULD not find system slice {} @ {}".format(slicename, self.test_node.name()))
1489         timeout = timedelta(minutes=timeout_minutes)
1490         silent  = timedelta(0)
1491         period  = timedelta(seconds=period_seconds)
1492         tasks = [ CompleterTaskSystemSlice(test_node, self.options.dry_run) \
1493                       for test_node in self.all_nodes() ]
1494         return Completer(tasks, message='_check_system_slice').run(timeout, silent, period)
1495
1496     def plcsh_stress_test(self):
1497         "runs PLCAPI stress test, that checks Add/Update/Delete on all types - preserves contents"
1498         # install the stress-test in the plc image
1499         location = "/usr/share/plc_api/plcsh_stress_test.py"
1500         remote = "{}/{}".format(self.vm_root_in_host(), location)
1501         self.test_ssh.copy_abs("plcsh_stress_test.py", remote)
1502         command = location
1503         command += " -- --check"
1504         if self.options.size == 1:
1505             command +=  " --tiny"
1506         return self.run_in_guest(command) == 0
1507
1508     # populate runs the same utility without slightly different options
1509     # in particular runs with --preserve (dont cleanup) and without --check
1510     # also it gets run twice, once with the --foreign option for creating fake foreign entries
1511
1512     def sfa_install_all(self):
1513         "yum install sfa sfa-plc sfa-sfatables sfa-client"
1514         return self.yum_install("sfa sfa-plc sfa-sfatables sfa-client")
1515
1516     def sfa_install_core(self):
1517         "yum install sfa"
1518         return self.yum_install("sfa")
1519         
1520     def sfa_install_plc(self):
1521         "yum install sfa-plc"
1522         return self.yum_install("sfa-plc")
1523         
1524     def sfa_install_sfatables(self):
1525         "yum install sfa-sfatables"
1526         return self.yum_install("sfa-sfatables")
1527
1528     # for some very odd reason, this sometimes fails with the following symptom
1529     # # yum install sfa-client
1530     # Setting up Install Process
1531     # ...
1532     # Downloading Packages:
1533     # Running rpm_check_debug
1534     # Running Transaction Test
1535     # Transaction Test Succeeded
1536     # Running Transaction
1537     # Transaction couldn't start:
1538     # installing package sfa-client-2.1-7.onelab.2012.05.23.i686 needs 68KB on the / filesystem
1539     # [('installing package sfa-client-2.1-7.onelab.2012.05.23.i686 needs 68KB on the / filesystem', (9, '/', 69632L))]
1540     # even though in the same context I have
1541     # [2012.05.23--f14-32-sfastd1-1-vplc07] / # df -h 
1542     # Filesystem            Size  Used Avail Use% Mounted on
1543     # /dev/hdv1             806G  264G  501G  35% /
1544     # none                   16M   36K   16M   1% /tmp
1545     #
1546     # so as a workaround, we first try yum install, and then invoke rpm on the cached rpm...
1547     def sfa_install_client(self):
1548         "yum install sfa-client"
1549         first_try = self.yum_install("sfa-client")
1550         if first_try:
1551             return True
1552         utils.header("********** Regular yum failed - special workaround in place, 2nd chance")
1553         code, cached_rpm_path = \
1554                 utils.output_of(self.actual_command_in_guest('find /var/cache/yum -name sfa-client\*.rpm'))
1555         utils.header("rpm_path=<<{}>>".format(rpm_path))
1556         # just for checking 
1557         self.run_in_guest("rpm -i {}".format(cached_rpm_path))
1558         return self.yum_check_installed("sfa-client")
1559
1560     def sfa_dbclean(self):
1561         "thoroughly wipes off the SFA database"
1562         return self.run_in_guest("sfaadmin reg nuke") == 0 or \
1563             self.run_in_guest("sfa-nuke.py") == 0 or \
1564             self.run_in_guest("sfa-nuke-plc.py") == 0 or \
1565             self.run_in_guest("sfaadmin registry nuke") == 0             
1566
1567     def sfa_fsclean(self):
1568         "cleanup /etc/sfa/trusted_roots and /var/lib/sfa"
1569         self.run_in_guest("rm -rf /etc/sfa/trusted_roots /var/lib/sfa/authorities")
1570         return True
1571
1572     def sfa_plcclean(self):
1573         "cleans the PLC entries that were created as a side effect of running the script"
1574         # ignore result 
1575         sfa_spec = self.plc_spec['sfa']
1576
1577         for auth_sfa_spec in sfa_spec['auth_sfa_specs']:
1578             login_base = auth_sfa_spec['login_base']
1579             try:
1580                 self.apiserver.DeleteSite(self.auth_root(),login_base)
1581             except:
1582                 print("Site {} already absent from PLC db".format(login_base))
1583
1584             for spec_name in ['pi_spec','user_spec']:
1585                 user_spec = auth_sfa_spec[spec_name]
1586                 username = user_spec['email']
1587                 try:
1588                     self.apiserver.DeletePerson(self.auth_root(),username)
1589                 except: 
1590                     # this in fact is expected as sites delete their members
1591                     #print "User {} already absent from PLC db".format(username)
1592                     pass
1593
1594         print("REMEMBER TO RUN sfa_import AGAIN")
1595         return True
1596
1597     def sfa_uninstall(self):
1598         "uses rpm to uninstall sfa - ignore result"
1599         self.run_in_guest("rpm -e sfa sfa-sfatables sfa-client sfa-plc")
1600         self.run_in_guest("rm -rf /var/lib/sfa")
1601         self.run_in_guest("rm -rf /etc/sfa")
1602         self.run_in_guest("rm -rf /var/log/sfa_access.log /var/log/sfa_import_plc.log /var/log/sfa.daemon")
1603         # xxx tmp 
1604         self.run_in_guest("rpm -e --noscripts sfa-plc")
1605         return True
1606
1607     ### run unit tests for SFA
1608     # NOTE: for some reason on f14/i386, yum install sfa-tests fails for no reason
1609     # Running Transaction
1610     # Transaction couldn't start:
1611     # installing package sfa-tests-1.0-21.onelab.i686 needs 204KB on the / filesystem
1612     # [('installing package sfa-tests-1.0-21.onelab.i686 needs 204KB on the / filesystem', (9, '/', 208896L))]
1613     # no matter how many Gbs are available on the testplc
1614     # could not figure out what's wrong, so...
1615     # if the yum install phase fails, consider the test is successful
1616     # other combinations will eventually run it hopefully
1617     def sfa_utest(self):
1618         "yum install sfa-tests and run SFA unittests"
1619         self.run_in_guest("yum -y install sfa-tests")
1620         # failed to install - forget it
1621         if self.run_in_guest("rpm -q sfa-tests") != 0: 
1622             utils.header("WARNING: SFA unit tests failed to install, ignoring")
1623             return True
1624         return self.run_in_guest("/usr/share/sfa/tests/testAll.py") == 0
1625
1626     ###
1627     def confdir(self):
1628         dirname = "conf.{}".format(self.plc_spec['name'])
1629         if not os.path.isdir(dirname):
1630             utils.system("mkdir -p {}".format(dirname))
1631         if not os.path.isdir(dirname):
1632             raise Exception("Cannot create config dir for plc {}".format(self.name()))
1633         return dirname
1634
1635     def conffile(self, filename):
1636         return "{}/{}".format(self.confdir(), filename)
1637     def confsubdir(self, dirname, clean, dry_run=False):
1638         subdirname = "{}/{}".format(self.confdir(), dirname)
1639         if clean:
1640             utils.system("rm -rf {}".format(subdirname))
1641         if not os.path.isdir(subdirname): 
1642             utils.system("mkdir -p {}".format(subdirname))
1643         if not dry_run and not os.path.isdir(subdirname):
1644             raise "Cannot create config subdir {} for plc {}".format(dirname, self.name())
1645         return subdirname
1646         
1647     def conffile_clean(self, filename):
1648         filename=self.conffile(filename)
1649         return utils.system("rm -rf {}".format(filename))==0
1650     
1651     ###
1652     def sfa_configure(self):
1653         "run sfa-config-tty"
1654         tmpname = self.conffile("sfa-config-tty")
1655         with open(tmpname,'w') as fileconf:
1656             for (var,value) in self.plc_spec['sfa']['settings'].items():
1657                 fileconf.write('e {}\n{}\n'.format(var, value))
1658             fileconf.write('w\n')
1659             fileconf.write('R\n')
1660             fileconf.write('q\n')
1661         utils.system('cat {}'.format(tmpname))
1662         self.run_in_guest_piped('cat {}'.format(tmpname), 'sfa-config-tty')
1663         return True
1664
1665     def aggregate_xml_line(self):
1666         port = self.plc_spec['sfa']['neighbours-port']
1667         return '<aggregate addr="{}" hrn="{}" port="{}"/>'\
1668             .format(self.vserverip, self.plc_spec['sfa']['settings']['SFA_REGISTRY_ROOT_AUTH'], port)
1669
1670     def registry_xml_line(self):
1671         return '<registry addr="{}" hrn="{}" port="12345"/>'\
1672             .format(self.vserverip, self.plc_spec['sfa']['settings']['SFA_REGISTRY_ROOT_AUTH'])
1673
1674
1675     # a cross step that takes all other plcs in argument
1676     def cross_sfa_configure(self, other_plcs):
1677         "writes aggregates.xml and registries.xml that point to all other PLCs in the test"
1678         # of course with a single plc, other_plcs is an empty list
1679         if not other_plcs:
1680             return True
1681         agg_fname = self.conffile("agg.xml")
1682         with open(agg_fname,"w") as out:
1683             out.write("<aggregates>{}</aggregates>\n"\
1684                       .format(" ".join([ plc.aggregate_xml_line() for plc in other_plcs ])))
1685         utils.header("(Over)wrote {}".format(agg_fname))
1686         reg_fname=self.conffile("reg.xml")
1687         with open(reg_fname,"w") as out:
1688             out.write("<registries>{}</registries>\n"\
1689                       .format(" ".join([ plc.registry_xml_line() for plc in other_plcs ])))
1690         utils.header("(Over)wrote {}".format(reg_fname))
1691         return self.test_ssh.copy_abs(agg_fname,
1692                                       '/{}/etc/sfa/aggregates.xml'.format(self.vm_root_in_host())) == 0 \
1693            and self.test_ssh.copy_abs(reg_fname,
1694                                       '/{}/etc/sfa/registries.xml'.format(self.vm_root_in_host())) == 0
1695
1696     def sfa_import(self):
1697         "use sfaadmin to import from plc"
1698         auth = self.plc_spec['sfa']['settings']['SFA_REGISTRY_ROOT_AUTH']
1699         return self.run_in_guest('sfaadmin reg import_registry') == 0 
1700
1701     def sfa_start(self):
1702         "service sfa start"
1703         return self.start_service('sfa')
1704
1705
1706     def sfi_configure(self):
1707         "Create /root/sfi on the plc side for sfi client configuration"
1708         if self.options.dry_run: 
1709             utils.header("DRY RUN - skipping step")
1710             return True
1711         sfa_spec = self.plc_spec['sfa']
1712         # cannot use auth_sfa_mapper to pass dir_name
1713         for slice_spec in self.plc_spec['sfa']['auth_sfa_specs']:
1714             test_slice = TestAuthSfa(self, slice_spec)
1715             dir_basename = os.path.basename(test_slice.sfi_path())
1716             dir_name = self.confsubdir("dot-sfi/{}".format(dir_basename),
1717                                        clean=True, dry_run=self.options.dry_run)
1718             test_slice.sfi_configure(dir_name)
1719             # push into the remote /root/sfi area
1720             location = test_slice.sfi_path()
1721             remote = "{}/{}".format(self.vm_root_in_host(), location)
1722             self.test_ssh.mkdir(remote, abs=True)
1723             # need to strip last level or remote otherwise we get an extra dir level
1724             self.test_ssh.copy_abs(dir_name, os.path.dirname(remote), recursive=True)
1725
1726         return True
1727
1728     def sfi_clean(self):
1729         "clean up /root/sfi on the plc side"
1730         self.run_in_guest("rm -rf /root/sfi")
1731         return True
1732
1733     def sfa_rspec_empty(self):
1734         "expose a static empty rspec (ships with the tests module) in the sfi directory"
1735         filename = "empty-rspec.xml"
1736         overall = True
1737         for slice_spec in self.plc_spec['sfa']['auth_sfa_specs']:
1738             test_slice = TestAuthSfa(self, slice_spec)
1739             in_vm = test_slice.sfi_path()
1740             remote = "{}/{}".format(self.vm_root_in_host(), in_vm)
1741             if self.test_ssh.copy_abs(filename, remote) !=0:
1742                 overall = False
1743         return overall
1744
1745     @auth_sfa_mapper
1746     def sfa_register_site(self): pass
1747     @auth_sfa_mapper
1748     def sfa_register_pi(self): pass
1749     @auth_sfa_mapper
1750     def sfa_register_user(self): pass
1751     @auth_sfa_mapper
1752     def sfa_update_user(self): pass
1753     @auth_sfa_mapper
1754     def sfa_register_slice(self): pass
1755     @auth_sfa_mapper
1756     def sfa_renew_slice(self): pass
1757     @auth_sfa_mapper
1758     def sfa_get_expires(self): pass
1759     @auth_sfa_mapper
1760     def sfa_discover(self): pass
1761     @auth_sfa_mapper
1762     def sfa_rspec(self): pass
1763     @auth_sfa_mapper
1764     def sfa_allocate(self): pass
1765     @auth_sfa_mapper
1766     def sfa_allocate_empty(self): pass
1767     @auth_sfa_mapper
1768     def sfa_provision(self): pass
1769     @auth_sfa_mapper
1770     def sfa_provision_empty(self): pass
1771     @auth_sfa_mapper
1772     def sfa_describe(self): pass
1773     @auth_sfa_mapper
1774     def sfa_check_slice_plc(self): pass
1775     @auth_sfa_mapper
1776     def sfa_check_slice_plc_empty(self): pass
1777     @auth_sfa_mapper
1778     def sfa_update_slice(self): pass
1779     @auth_sfa_mapper
1780     def sfa_remove_user_from_slice(self): pass
1781     @auth_sfa_mapper
1782     def sfa_insert_user_in_slice(self): pass
1783     @auth_sfa_mapper
1784     def sfi_list(self): pass
1785     @auth_sfa_mapper
1786     def sfi_show_site(self): pass
1787     @auth_sfa_mapper
1788     def sfi_show_slice(self): pass
1789     @auth_sfa_mapper
1790     def sfi_show_slice_researchers(self): pass
1791     @auth_sfa_mapper
1792     def ssh_slice_sfa(self): pass
1793     @auth_sfa_mapper
1794     def sfa_delete_user(self): pass
1795     @auth_sfa_mapper
1796     def sfa_delete_slice(self): pass
1797
1798     def sfa_stop(self):
1799         "service sfa stop"
1800         return self.stop_service('sfa')
1801
1802     def populate(self):
1803         "creates random entries in the PLCAPI"
1804         # install the stress-test in the plc image
1805         location = "/usr/share/plc_api/plcsh_stress_test.py"
1806         remote = "{}/{}".format(self.vm_root_in_host(), location)
1807         self.test_ssh.copy_abs("plcsh_stress_test.py", remote)
1808         command = location
1809         command += " -- --preserve --short-names"
1810         local = (self.run_in_guest(command) == 0);
1811         # second run with --foreign
1812         command += ' --foreign'
1813         remote = (self.run_in_guest(command) == 0);
1814         return local and remote
1815
1816
1817     ####################
1818     @bonding_redirector
1819     def bonding_init_partial(self): pass
1820
1821     @bonding_redirector
1822     def bonding_add_yum(self): pass
1823
1824     @bonding_redirector
1825     def bonding_install_rpms(self): pass
1826
1827     ####################
1828
1829     def gather_logs(self):
1830         "gets all possible logs from plc's/qemu node's/slice's for future reference"
1831         # (1.a) get the plc's /var/log/ and store it locally in logs/myplc.var-log.<plcname>/*
1832         # (1.b) get the plc's  /var/lib/pgsql/data/pg_log/ -> logs/myplc.pgsql-log.<plcname>/*
1833         # (1.c) get the plc's /root/sfi -> logs/sfi.<plcname>/
1834         # (2) get all the nodes qemu log and store it as logs/node.qemu.<node>.log
1835         # (3) get the nodes /var/log and store is as logs/node.var-log.<node>/*
1836         # (4) as far as possible get the slice's /var/log as logs/sliver.var-log.<sliver>/*
1837         # (1.a)
1838         print("-------------------- TestPlc.gather_logs : PLC's /var/log")
1839         self.gather_var_logs()
1840         # (1.b)
1841         print("-------------------- TestPlc.gather_logs : PLC's /var/lib/psql/data/pg_log/")
1842         self.gather_pgsql_logs()
1843         # (1.c)
1844         print("-------------------- TestPlc.gather_logs : PLC's /root/sfi/")
1845         self.gather_root_sfi()
1846         # (2) 
1847         print("-------------------- TestPlc.gather_logs : nodes's QEMU logs")
1848         for site_spec in self.plc_spec['sites']:
1849             test_site = TestSite(self,site_spec)
1850             for node_spec in site_spec['nodes']:
1851                 test_node = TestNode(self, test_site, node_spec)
1852                 test_node.gather_qemu_logs()
1853         # (3)
1854         print("-------------------- TestPlc.gather_logs : nodes's /var/log")
1855         self.gather_nodes_var_logs()
1856         # (4)
1857         print("-------------------- TestPlc.gather_logs : sample sliver's /var/log")
1858         self.gather_slivers_var_logs()
1859         return True
1860
1861     def gather_slivers_var_logs(self):
1862         for test_sliver in self.all_sliver_objs():
1863             remote = test_sliver.tar_var_logs()
1864             utils.system("mkdir -p logs/sliver.var-log.{}".format(test_sliver.name()))
1865             command = remote + " | tar -C logs/sliver.var-log.{} -xf -".format(test_sliver.name())
1866             utils.system(command)
1867         return True
1868
1869     def gather_var_logs(self):
1870         utils.system("mkdir -p logs/myplc.var-log.{}".format(self.name()))
1871         to_plc = self.actual_command_in_guest("tar -C /var/log/ -cf - .")        
1872         command = to_plc + "| tar -C logs/myplc.var-log.{} -xf -".format(self.name())
1873         utils.system(command)
1874         command = "chmod a+r,a+x logs/myplc.var-log.{}/httpd".format(self.name())
1875         utils.system(command)
1876
1877     def gather_pgsql_logs(self):
1878         utils.system("mkdir -p logs/myplc.pgsql-log.{}".format(self.name()))
1879         to_plc = self.actual_command_in_guest("tar -C /var/lib/pgsql/data/pg_log/ -cf - .")        
1880         command = to_plc + "| tar -C logs/myplc.pgsql-log.{} -xf -".format(self.name())
1881         utils.system(command)
1882
1883     def gather_root_sfi(self):
1884         utils.system("mkdir -p logs/sfi.{}".format(self.name()))
1885         to_plc = self.actual_command_in_guest("tar -C /root/sfi/ -cf - .")        
1886         command = to_plc + "| tar -C logs/sfi.{} -xf -".format(self.name())
1887         utils.system(command)
1888
1889     def gather_nodes_var_logs(self):
1890         for site_spec in self.plc_spec['sites']:
1891             test_site = TestSite(self, site_spec)
1892             for node_spec in site_spec['nodes']:
1893                 test_node = TestNode(self, test_site, node_spec)
1894                 test_ssh = TestSsh(test_node.name(), key="keys/key_admin.rsa")
1895                 command = test_ssh.actual_command("tar -C /var/log -cf - .")
1896                 command = command + "| tar -C logs/node.var-log.{} -xf -".format(test_node.name())
1897                 utils.system("mkdir -p logs/node.var-log.{}".format(test_node.name()))
1898                 utils.system(command)
1899
1900
1901     # returns the filename to use for sql dump/restore, using options.dbname if set
1902     def dbfile(self, database):
1903         # uses options.dbname if it is found
1904         try:
1905             name = self.options.dbname
1906             if not isinstance(name, str):
1907                 raise Exception
1908         except:
1909             t = datetime.now()
1910             d = t.date()
1911             name = str(d)
1912         return "/root/{}-{}.sql".format(database, name)
1913
1914     def plc_db_dump(self):
1915         'dump the planetlab5 DB in /root in the PLC - filename has time'
1916         dump=self.dbfile("planetab5")
1917         self.run_in_guest('pg_dump -U pgsqluser planetlab5 -f '+ dump)
1918         utils.header('Dumped planetlab5 database in {}'.format(dump))
1919         return True
1920
1921     def plc_db_restore(self):
1922         'restore the planetlab5 DB - looks broken, but run -n might help'
1923         dump = self.dbfile("planetab5")
1924         ##stop httpd service
1925         self.run_in_guest('service httpd stop')
1926         # xxx - need another wrapper
1927         self.run_in_guest_piped('echo drop database planetlab5', 'psql --user=pgsqluser template1')
1928         self.run_in_guest('createdb -U postgres --encoding=UNICODE --owner=pgsqluser planetlab5')
1929         self.run_in_guest('psql -U pgsqluser planetlab5 -f ' + dump)
1930         ##starting httpd service
1931         self.run_in_guest('service httpd start')
1932
1933         utils.header('Database restored from ' + dump)
1934
1935     @staticmethod
1936     def create_ignore_steps():
1937         for step in TestPlc.default_steps + TestPlc.other_steps:
1938             # default step can have a plc qualifier
1939             if '@' in step:
1940                 step, qualifier = step.split('@')
1941             # or be defined as forced or ignored by default
1942             for keyword in ['_ignore','_force']:
1943                 if step.endswith(keyword):
1944                     step=step.replace(keyword,'')
1945             if step == SEP or step == SEPSFA :
1946                 continue
1947             method = getattr(TestPlc,step)
1948             name = step + '_ignore'
1949             wrapped = ignore_result(method)
1950 #            wrapped.__doc__ = method.__doc__ + " (run in ignore-result mode)"
1951             setattr(TestPlc, name, wrapped)
1952             
1953 #    @ignore_result
1954 #    def ssh_slice_again_ignore (self): pass
1955 #    @ignore_result
1956 #    def check_initscripts_ignore (self): pass
1957     
1958     def standby_1_through_20(self):
1959         """convenience function to wait for a specified number of minutes"""
1960         pass
1961     @standby_generic 
1962     def standby_1(): pass
1963     @standby_generic 
1964     def standby_2(): pass
1965     @standby_generic 
1966     def standby_3(): pass
1967     @standby_generic 
1968     def standby_4(): pass
1969     @standby_generic 
1970     def standby_5(): pass
1971     @standby_generic 
1972     def standby_6(): pass
1973     @standby_generic 
1974     def standby_7(): pass
1975     @standby_generic 
1976     def standby_8(): pass
1977     @standby_generic 
1978     def standby_9(): pass
1979     @standby_generic 
1980     def standby_10(): pass
1981     @standby_generic 
1982     def standby_11(): pass
1983     @standby_generic 
1984     def standby_12(): pass
1985     @standby_generic 
1986     def standby_13(): pass
1987     @standby_generic 
1988     def standby_14(): pass
1989     @standby_generic 
1990     def standby_15(): pass
1991     @standby_generic 
1992     def standby_16(): pass
1993     @standby_generic 
1994     def standby_17(): pass
1995     @standby_generic 
1996     def standby_18(): pass
1997     @standby_generic 
1998     def standby_19(): pass
1999     @standby_generic 
2000     def standby_20(): pass
2001
2002     # convenience for debugging the test logic
2003     def yes(self): return True
2004     def no(self): return False
2005     def fail(self): return False