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