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