run -g with bonding build can provision node (and locate free IP) and run most usual...
[tests.git] / system / TestPlc.py
1 # Thierry Parmentelat <thierry.parmentelat@inria.fr>
2 # Copyright (C) 2010 INRIA 
3 #
4 import sys
5 import time
6 import os, os.path
7 import traceback
8 import socket
9 from datetime import datetime, timedelta
10
11 import utils
12 from Completer import Completer, CompleterTask
13 from TestSite import TestSite
14 from TestNode import TestNode, CompleterTaskNodeSsh
15 from TestUser import TestUser
16 from TestKey import TestKey
17 from TestSlice import TestSlice
18 from TestSliver import TestSliver
19 from TestBoxQemu import TestBoxQemu
20 from TestSsh import TestSsh
21 from TestApiserver import TestApiserver
22 from TestAuthSfa import TestAuthSfa
23 from PlcapiUrlScanner import PlcapiUrlScanner
24
25 from TestBonding import TestBonding
26
27 has_sfa_cache_filename="sfa-cache"
28
29 # step methods must take (self) and return a boolean (options is a member of the class)
30
31 def standby(minutes, dry_run):
32     utils.header('Entering StandBy for {:d} mn'.format(minutes))
33     if dry_run:
34         print('dry_run')
35     else:
36         time.sleep(60*minutes)
37     return True
38
39 def standby_generic(func):
40     def actual(self):
41         minutes = int(func.__name__.split("_")[1])
42         return standby(minutes, self.options.dry_run)
43     return actual
44
45 def node_mapper(method):
46     def map_on_nodes(self, *args, **kwds):
47         overall = True
48         node_method = TestNode.__dict__[method.__name__]
49         for test_node in self.all_nodes():
50             if not node_method(test_node, *args, **kwds):
51                 overall=False
52         return overall
53     # maintain __name__ for ignore_result
54     map_on_nodes.__name__ = method.__name__
55     # restore the doc text
56     map_on_nodes.__doc__ = TestNode.__dict__[method.__name__].__doc__
57     return map_on_nodes
58
59 def slice_mapper(method):
60     def map_on_slices(self):
61         overall = True
62         slice_method = TestSlice.__dict__[method.__name__]
63         for slice_spec in self.plc_spec['slices']:
64             site_spec = self.locate_site (slice_spec['sitename'])
65             test_site = TestSite(self,site_spec)
66             test_slice = TestSlice(self,test_site,slice_spec)
67             if not slice_method(test_slice, self.options):
68                 overall=False
69         return overall
70     # maintain __name__ for ignore_result
71     map_on_slices.__name__ = method.__name__
72     # restore the doc text
73     map_on_slices.__doc__ = TestSlice.__dict__[method.__name__].__doc__
74     return map_on_slices
75
76 def bonding_redirector(method):
77     bonding_name = method.__name__.replace('bonding_', '')
78     def redirect(self):
79         bonding_method = TestBonding.__dict__[bonding_name]
80         return bonding_method(self.test_bonding)
81     # maintain __name__ for ignore_result
82     redirect.__name__ = method.__name__
83     # restore the doc text
84     redirect.__doc__ = TestBonding.__dict__[bonding_name].__doc__
85     return redirect
86
87 # run a step but return True so that we can go on
88 def ignore_result(method):
89     def ignoring(self):
90         # ssh_slice_ignore->ssh_slice
91         ref_name = method.__name__.replace('_ignore', '').replace('force_', '')
92         ref_method = TestPlc.__dict__[ref_name]
93         result = ref_method(self)
94         print("Actual (but ignored) result for {ref_name} is {result}".format(**locals()))
95         return Ignored(result)
96     name = method.__name__.replace('_ignore', '').replace('force_', '')
97     ignoring.__name__ = name
98     ignoring.__doc__ = "ignored version of " + name
99     return ignoring
100
101 # a variant that expects the TestSlice method to return a list of CompleterTasks that
102 # are then merged into a single Completer run to avoid wating for all the slices
103 # esp. useful when a test fails of course
104 # because we need to pass arguments we use a class instead..
105 class slice_mapper__tasks(object):
106     # could not get this to work with named arguments
107     def __init__(self, timeout_minutes, silent_minutes, period_seconds):
108         self.timeout = timedelta(minutes = timeout_minutes)
109         self.silent = timedelta(minutes = silent_minutes)
110         self.period = timedelta(seconds = period_seconds)
111     def __call__(self, method):
112         decorator_self=self
113         # compute augmented method name
114         method_name = method.__name__ + "__tasks"
115         # locate in TestSlice
116         slice_method = TestSlice.__dict__[ method_name ]
117         def wrappee(self):
118             tasks=[]
119             for slice_spec in self.plc_spec['slices']:
120                 site_spec = self.locate_site (slice_spec['sitename'])
121                 test_site = TestSite(self, site_spec)
122                 test_slice = TestSlice(self, test_site, slice_spec)
123                 tasks += slice_method (test_slice, self.options)
124             return Completer (tasks, message=method.__name__).\
125                 run(decorator_self.timeout, decorator_self.silent, decorator_self.period)
126         # restore the doc text from the TestSlice method even if a bit odd
127         wrappee.__name__ = method.__name__
128         wrappee.__doc__ = slice_method.__doc__
129         return wrappee
130
131 def auth_sfa_mapper(method):
132     def actual(self):
133         overall = True
134         auth_method = TestAuthSfa.__dict__[method.__name__]
135         for auth_spec in self.plc_spec['sfa']['auth_sfa_specs']:
136             test_auth = TestAuthSfa(self, auth_spec)
137             if not auth_method(test_auth, self.options):
138                 overall=False
139         return overall
140     # restore the doc text
141     actual.__doc__ = TestAuthSfa.__dict__[method.__name__].__doc__
142     return actual
143
144 class Ignored:
145     def __init__(self, result):
146         self.result = result
147
148 SEP = '<sep>'
149 SEPSFA = '<sep_sfa>'
150
151 class TestPlc:
152
153     default_steps = [
154         'show', SEP,
155         'plcvm_delete','plcvm_timestamp','plcvm_create', SEP,
156         'plc_install', 'plc_configure', 'plc_start', SEP,
157         'keys_fetch', 'keys_store', 'keys_clear_known_hosts', SEP,
158         'plcapi_urls','speed_up_slices', SEP,
159         'initscripts', 'sites', 'nodes', 'slices', 'nodegroups', 'leases', SEP,
160 # slices created under plcsh interactively seem to be fine but these ones don't have the tags
161 # keep this our of the way for now
162         'check_vsys_defaults_ignore', SEP,
163 # run this first off so it's easier to re-run on another qemu box        
164         'qemu_kill_mine', 'nodestate_reinstall', 'qemu_local_init','bootcd', 'qemu_local_config', SEP,
165         'qemu_clean_mine', 'qemu_export', 'qemu_start', 'qemu_timestamp', SEP,
166         'sfa_install_all', 'sfa_configure', 'cross_sfa_configure', 'sfa_start', 'sfa_import', SEPSFA,
167         'sfi_configure@1', 'sfa_register_site@1','sfa_register_pi@1', SEPSFA,
168         'sfa_register_user@1', 'sfa_update_user@1', 'sfa_register_slice@1', 'sfa_renew_slice@1', SEPSFA,
169         'sfa_remove_user_from_slice@1','sfi_show_slice_researchers@1', 
170         'sfa_insert_user_in_slice@1','sfi_show_slice_researchers@1', SEPSFA,
171         'sfa_discover@1', 'sfa_rspec@1', 'sfa_allocate@1', 'sfa_provision@1', SEPSFA,
172         'sfa_check_slice_plc@1', 'sfa_update_slice@1', SEPSFA,
173         'sfi_list@1', 'sfi_show_site@1', 'sfa_utest@1', SEPSFA,
174         # we used to run plcsh_stress_test, and then ssh_node_debug and ssh_node_boot
175         # but as the stress test might take a while, we sometimes missed the debug mode..
176         'probe_kvm_iptables',
177         'ping_node', 'ssh_node_debug', 'plcsh_stress_test@1', SEP,
178         'ssh_node_boot', 'node_bmlogs', 'ssh_slice', 'ssh_slice_basics', 'check_initscripts', SEP,
179         'ssh_slice_sfa@1', SEPSFA, 
180         'sfa_rspec_empty@1', 'sfa_allocate_empty@1', 'sfa_provision_empty@1','sfa_check_slice_plc_empty@1', SEPSFA,
181         'sfa_delete_slice@1', 'sfa_delete_user@1', SEPSFA,
182         'cross_check_tcp@1', 'check_system_slice', SEP,
183         # for inspecting the slice while it runs the first time
184         #'fail',
185         # check slices are turned off properly
186         'empty_slices', 'ssh_slice_off', 'slice_fs_deleted_ignore', SEP,
187         # check they are properly re-created with the same name
188         'fill_slices', 'ssh_slice_again', SEP,
189         'gather_logs_force', SEP,
190         ]
191     other_steps = [ 
192         'export', 'show_boxes', 'super_speed_up_slices', SEP,
193         'check_hooks', 'plc_stop', 'plcvm_start', 'plcvm_stop', SEP,
194         'delete_initscripts', 'delete_nodegroups','delete_all_sites', SEP,
195         'delete_sites', 'delete_nodes', 'delete_slices', 'keys_clean', SEP,
196         'delete_leases', 'list_leases', SEP,
197         'populate', SEP,
198         'nodestate_show','nodestate_safeboot','nodestate_boot', SEP,
199         'qemu_list_all', 'qemu_list_mine', 'qemu_kill_all', SEP,
200         'sfa_install_core', 'sfa_install_sfatables', 'sfa_install_plc', 'sfa_install_client', SEPSFA,
201         'sfa_plcclean', 'sfa_dbclean', 'sfa_stop','sfa_uninstall', 'sfi_clean', SEPSFA,
202         'sfa_get_expires', SEPSFA,
203         'plc_db_dump' , 'plc_db_restore', SEP,
204         'check_netflow','check_drl', SEP,
205         'debug_nodemanager', 'slice_fs_present', SEP,
206         'standby_1_through_20','yes','no',SEP,
207         ]
208     default_bonding_steps = [
209         'bonding_init_partial',
210         'bonding_add_yum',
211         'bonding_install_rpms', SEP,
212         ]
213
214     @staticmethod
215     def printable_steps(list):
216         single_line = " ".join(list) + " "
217         return single_line.replace(" "+SEP+" ", " \\\n").replace(" "+SEPSFA+" ", " \\\n")
218     @staticmethod
219     def valid_step(step):
220         return step != SEP and step != SEPSFA
221
222     # turn off the sfa-related steps when build has skipped SFA
223     # this was originally for centos5 but is still valid
224     # for up to f12 as recent SFAs with sqlalchemy won't build before f14
225     @staticmethod
226     def _has_sfa_cached(rpms_url):
227         if os.path.isfile(has_sfa_cache_filename):
228             with open(has_sfa_cache_filename) as cache:
229                 cached = cache.read() == "yes"
230             utils.header("build provides SFA (cached):{}".format(cached))
231             return cached
232         # warning, we're now building 'sface' so let's be a bit more picky
233         # full builds are expected to return with 0 here
234         utils.header("Checking if build provides SFA package...")
235         retcod = utils.system("curl --silent {}/ | grep -q sfa-".format(rpms_url)) == 0
236         encoded = 'yes' if retcod else 'no'
237         with open(has_sfa_cache_filename,'w') as cache:
238             cache.write(encoded)
239         return retcod
240         
241     @staticmethod
242     def check_whether_build_has_sfa(rpms_url):
243         has_sfa = TestPlc._has_sfa_cached(rpms_url)
244         if has_sfa:
245             utils.header("build does provide SFA")
246         else:
247             # move all steps containing 'sfa' from default_steps to other_steps
248             utils.header("SFA package not found - removing steps with sfa or sfi")
249             sfa_steps = [ step for step in TestPlc.default_steps
250                           if step.find('sfa') >= 0 or step.find("sfi") >= 0 ]
251             TestPlc.other_steps += sfa_steps
252             for step in sfa_steps:
253                 TestPlc.default_steps.remove(step)
254
255     def __init__(self, plc_spec, options):
256         self.plc_spec = plc_spec
257         self.options = options
258         self.test_ssh = TestSsh(self.plc_spec['host_box'], self.options.buildname)
259         self.vserverip = plc_spec['vserverip']
260         self.vservername = plc_spec['vservername']
261         self.vplchostname = self.vservername.split('-')[-1]
262         self.url = "https://{}:443/PLCAPI/".format(plc_spec['vserverip'])
263         self.apiserver = TestApiserver(self.url, options.dry_run)
264         (self.ssh_node_boot_timeout, self.ssh_node_boot_silent) = plc_spec['ssh_node_boot_timers']
265         (self.ssh_node_debug_timeout, self.ssh_node_debug_silent) = plc_spec['ssh_node_debug_timers']
266         
267     def has_addresses_api(self):
268         return self.apiserver.has_method('AddIpAddress')
269
270     def name(self):
271         name = self.plc_spec['name']
272         return "{}.{}".format(name,self.vservername)
273
274     def hostname(self):
275         return self.plc_spec['host_box']
276
277     def is_local(self):
278         return self.test_ssh.is_local()
279
280     # define the API methods on this object through xmlrpc
281     # would help, but not strictly necessary
282     def connect(self):
283         pass
284
285     def actual_command_in_guest(self,command, backslash=False):
286         raw1 = self.host_to_guest(command)
287         raw2 = self.test_ssh.actual_command(raw1, dry_run=self.options.dry_run, backslash=backslash)
288         return raw2
289     
290     def start_guest(self):
291       return utils.system(self.test_ssh.actual_command(self.start_guest_in_host(),
292                                                        dry_run=self.options.dry_run))
293     
294     def stop_guest(self):
295       return utils.system(self.test_ssh.actual_command(self.stop_guest_in_host(),
296                                                        dry_run=self.options.dry_run))
297     
298     def run_in_guest(self, command, backslash=False):
299         raw = self.actual_command_in_guest(command, backslash)
300         return utils.system(raw)
301     
302     def run_in_host(self,command):
303         return self.test_ssh.run_in_buildname(command, dry_run=self.options.dry_run)
304
305     # backslashing turned out so awful at some point that I've turned off auto-backslashing
306     # see e.g. plc_start esp. the version for f14
307     #command gets run in the plc's vm
308     def host_to_guest(self, command):
309         ssh_leg = TestSsh(self.vplchostname)
310         return ssh_leg.actual_command(command, keep_stdin=True)
311     
312     # this /vservers thing is legacy...
313     def vm_root_in_host(self):
314         return "/vservers/{}/".format(self.vservername)
315
316     def vm_timestamp_path(self):
317         return "/vservers/{}/{}.timestamp".format(self.vservername, self.vservername)
318
319     #start/stop the vserver
320     def start_guest_in_host(self):
321         return "virsh -c lxc:/// start {}".format(self.vservername)
322     
323     def stop_guest_in_host(self):
324         return "virsh -c lxc:/// destroy {}".format(self.vservername)
325     
326     # xxx quick n dirty
327     def run_in_guest_piped(self,local,remote):
328         return utils.system(local+" | "+self.test_ssh.actual_command(self.host_to_guest(remote),
329                                                                      keep_stdin = True))
330
331     def yum_check_installed(self, rpms):
332         if isinstance(rpms, list): 
333             rpms=" ".join(rpms)
334         return self.run_in_guest("rpm -q {}".format(rpms)) == 0
335         
336     # does a yum install in the vs, ignore yum retcod, check with rpm
337     def yum_install(self, rpms):
338         if isinstance(rpms, list): 
339             rpms=" ".join(rpms)
340         self.run_in_guest("yum -y install {}".format(rpms))
341         # yum-complete-transaction comes with yum-utils, that is in vtest.pkgs
342         self.run_in_guest("yum-complete-transaction -y")
343         return self.yum_check_installed(rpms)
344
345     def auth_root(self):
346         return {'Username'   : self.plc_spec['settings']['PLC_ROOT_USER'],
347                 'AuthMethod' : 'password',
348                 'AuthString' : self.plc_spec['settings']['PLC_ROOT_PASSWORD'],
349                 'Role'       : self.plc_spec['role'],
350                 }
351     
352     def locate_site(self,sitename):
353         for site in self.plc_spec['sites']:
354             if site['site_fields']['name'] == sitename:
355                 return site
356             if site['site_fields']['login_base'] == sitename:
357                 return site
358         raise Exception("Cannot locate site {}".format(sitename))
359         
360     def locate_node(self, nodename):
361         for site in self.plc_spec['sites']:
362             for node in site['nodes']:
363                 if node['name'] == nodename:
364                     return site, node
365         raise Exception("Cannot locate node {}".format(nodename))
366         
367     def locate_hostname(self, hostname):
368         for site in self.plc_spec['sites']:
369             for node in site['nodes']:
370                 if node['node_fields']['hostname'] == hostname:
371                     return(site, node)
372         raise Exception("Cannot locate hostname {}".format(hostname))
373         
374     def locate_key(self, key_name):
375         for key in self.plc_spec['keys']:
376             if key['key_name'] == key_name:
377                 return key
378         raise Exception("Cannot locate key {}".format(key_name))
379
380     def locate_private_key_from_key_names(self, key_names):
381         # locate the first avail. key
382         found = False
383         for key_name in key_names:
384             key_spec = self.locate_key(key_name)
385             test_key = TestKey(self,key_spec)
386             publickey = test_key.publicpath()
387             privatekey = test_key.privatepath()
388             if os.path.isfile(publickey) and os.path.isfile(privatekey):
389                 found = True
390         if found:
391             return privatekey
392         else:
393             return None
394
395     def locate_slice(self, slicename):
396         for slice in self.plc_spec['slices']:
397             if slice['slice_fields']['name'] == slicename:
398                 return slice
399         raise Exception("Cannot locate slice {}".format(slicename))
400
401     def all_sliver_objs(self):
402         result = []
403         for slice_spec in self.plc_spec['slices']:
404             slicename = slice_spec['slice_fields']['name']
405             for nodename in slice_spec['nodenames']:
406                 result.append(self.locate_sliver_obj(nodename, slicename))
407         return result
408
409     def locate_sliver_obj(self, nodename, slicename):
410         site,node = self.locate_node(nodename)
411         slice = self.locate_slice(slicename)
412         # build objects
413         test_site = TestSite(self, site)
414         test_node = TestNode(self, test_site, node)
415         # xxx the slice site is assumed to be the node site - mhh - probably harmless
416         test_slice = TestSlice(self, test_site, slice)
417         return TestSliver(self, test_node, test_slice)
418
419     def locate_first_node(self):
420         nodename = self.plc_spec['slices'][0]['nodenames'][0]
421         site,node = self.locate_node(nodename)
422         test_site = TestSite(self, site)
423         test_node = TestNode(self, test_site, node)
424         return test_node
425
426     def locate_first_sliver(self):
427         slice_spec = self.plc_spec['slices'][0]
428         slicename = slice_spec['slice_fields']['name']
429         nodename = slice_spec['nodenames'][0]
430         return self.locate_sliver_obj(nodename,slicename)
431
432     # all different hostboxes used in this plc
433     def get_BoxNodes(self):
434         # maps on sites and nodes, return [ (host_box,test_node) ]
435         tuples = []
436         for site_spec in self.plc_spec['sites']:
437             test_site = TestSite(self,site_spec)
438             for node_spec in site_spec['nodes']:
439                 test_node = TestNode(self, test_site, node_spec)
440                 if not test_node.is_real():
441                     tuples.append( (test_node.host_box(),test_node) )
442         # transform into a dict { 'host_box' -> [ test_node .. ] }
443         result = {}
444         for (box,node) in tuples:
445             if box not in result:
446                 result[box] = [node]
447             else:
448                 result[box].append(node)
449         return result
450                     
451     # a step for checking this stuff
452     def show_boxes(self):
453         'print summary of nodes location'
454         for box,nodes in self.get_BoxNodes().items():
455             print(box,":"," + ".join( [ node.name() for node in nodes ] ))
456         return True
457
458     # make this a valid step
459     def qemu_kill_all(self):
460         'kill all qemu instances on the qemu boxes involved by this setup'
461         # this is the brute force version, kill all qemus on that host box
462         for (box,nodes) in self.get_BoxNodes().items():
463             # pass the first nodename, as we don't push template-qemu on testboxes
464             nodedir = nodes[0].nodedir()
465             TestBoxQemu(box, self.options.buildname).qemu_kill_all(nodedir)
466         return True
467
468     # make this a valid step
469     def qemu_list_all(self):
470         'list all qemu instances on the qemu boxes involved by this setup'
471         for box,nodes in self.get_BoxNodes().items():
472             # this is the brute force version, kill all qemus on that host box
473             TestBoxQemu(box, self.options.buildname).qemu_list_all()
474         return True
475
476     # kill only the qemus related to this test
477     def qemu_list_mine(self):
478         'list qemu instances for our nodes'
479         for (box,nodes) in self.get_BoxNodes().items():
480             # the fine-grain version
481             for node in nodes:
482                 node.list_qemu()
483         return True
484
485     # kill only the qemus related to this test
486     def qemu_clean_mine(self):
487         'cleanup (rm -rf) qemu instances for our nodes'
488         for box,nodes in self.get_BoxNodes().items():
489             # the fine-grain version
490             for node in nodes:
491                 node.qemu_clean()
492         return True
493
494     # kill only the right qemus
495     def qemu_kill_mine(self):
496         'kill the qemu instances for our nodes'
497         for box,nodes in self.get_BoxNodes().items():
498             # the fine-grain version
499             for node in nodes:
500                 node.kill_qemu()
501         return True
502
503     #################### display config
504     def show(self):
505         "show test configuration after localization"
506         self.show_pass(1)
507         self.show_pass(2)
508         return True
509
510     # uggly hack to make sure 'run export' only reports about the 1st plc 
511     # to avoid confusion - also we use 'inri_slice1' in various aliases..
512     exported_id = 1
513     def export(self):
514         "print cut'n paste-able stuff to export env variables to your shell"
515         # guess local domain from hostname
516         if TestPlc.exported_id > 1: 
517             print("export GUESTHOSTNAME{:d}={}".format(TestPlc.exported_id, self.plc_spec['vservername']))
518             return True
519         TestPlc.exported_id += 1
520         domain = socket.gethostname().split('.',1)[1]
521         fqdn   = "{}.{}".format(self.plc_spec['host_box'], domain)
522         print("export BUILD={}".format(self.options.buildname))
523         print("export PLCHOSTLXC={}".format(fqdn))
524         print("export GUESTNAME={}".format(self.vservername))
525         print("export GUESTHOSTNAME={}.{}".format(self.vplchostname, domain))
526         # find hostname of first node
527         hostname, qemubox = self.all_node_infos()[0]
528         print("export KVMHOST={}.{}".format(qemubox, domain))
529         print("export NODE={}".format(hostname))
530         return True
531
532     # entry point
533     always_display_keys=['PLC_WWW_HOST', 'nodes', 'sites']
534     def show_pass(self, passno):
535         for (key,val) in self.plc_spec.items():
536             if not self.options.verbose and key not in TestPlc.always_display_keys:
537                 continue
538             if passno == 2:
539                 if key == 'sites':
540                     for site in val:
541                         self.display_site_spec(site)
542                         for node in site['nodes']:
543                             self.display_node_spec(node)
544                 elif key == 'initscripts':
545                     for initscript in val:
546                         self.display_initscript_spec(initscript)
547                 elif key == 'slices':
548                     for slice in val:
549                         self.display_slice_spec(slice)
550                 elif key == 'keys':
551                     for key in val:
552                         self.display_key_spec(key)
553             elif passno == 1:
554                 if key not in ['sites', 'initscripts', 'slices', 'keys']:
555                     print('+   ', key, ':', val)
556
557     def display_site_spec(self, site):
558         print('+ ======== site', site['site_fields']['name'])
559         for k,v in site.items():
560             if not self.options.verbose and k not in TestPlc.always_display_keys:
561                 continue
562             if k == 'nodes':
563                 if v: 
564                     print('+       ','nodes : ', end=' ')
565                     for node in v:  
566                         print(node['node_fields']['hostname'],'', end=' ')
567                     print('')
568             elif k == 'users':
569                 if v: 
570                     print('+       users : ', end=' ')
571                     for user in v:  
572                         print(user['name'],'', end=' ')
573                     print('')
574             elif k == 'site_fields':
575                 print('+       login_base', ':', v['login_base'])
576             elif k == 'address_fields':
577                 pass
578             else:
579                 print('+       ', end=' ')
580                 utils.pprint(k, v)
581         
582     def display_initscript_spec(self, initscript):
583         print('+ ======== initscript', initscript['initscript_fields']['name'])
584
585     def display_key_spec(self, key):
586         print('+ ======== key', key['key_name'])
587
588     def display_slice_spec(self, slice):
589         print('+ ======== slice', slice['slice_fields']['name'])
590         for k,v in slice.items():
591             if k == 'nodenames':
592                 if v: 
593                     print('+       nodes : ', end=' ')
594                     for nodename in v:  
595                         print(nodename,'', end=' ')
596                     print('')
597             elif k == 'usernames':
598                 if v: 
599                     print('+       users : ', end=' ')
600                     for username in v:  
601                         print(username,'', end=' ')
602                     print('')
603             elif k == 'slice_fields':
604                 print('+       fields',':', end=' ')
605                 print('max_nodes=',v['max_nodes'], end=' ')
606                 print('')
607             else:
608                 print('+       ',k,v)
609
610     def display_node_spec(self, node):
611         print("+           node={} host_box={}".format(node['name'], node['host_box']), end=' ')
612         print("hostname=", node['node_fields']['hostname'], end=' ')
613         print("ip=", node['interface_fields']['ip'])
614         if self.options.verbose:
615             utils.pprint("node details", node, depth=3)
616
617     # another entry point for just showing the boxes involved
618     def display_mapping(self):
619         TestPlc.display_mapping_plc(self.plc_spec)
620         return True
621
622     @staticmethod
623     def display_mapping_plc(plc_spec):
624         print('+ MyPLC',plc_spec['name'])
625         # WARNING this would not be right for lxc-based PLC's - should be harmless though
626         print('+\tvserver address = root@{}:/vservers/{}'.format(plc_spec['host_box'], plc_spec['vservername']))
627         print('+\tIP = {}/{}'.format(plc_spec['settings']['PLC_API_HOST'], plc_spec['vserverip']))
628         for site_spec in plc_spec['sites']:
629             for node_spec in site_spec['nodes']:
630                 TestPlc.display_mapping_node(node_spec)
631
632     @staticmethod
633     def display_mapping_node(node_spec):
634         print('+   NODE {}'.format(node_spec['name']))
635         print('+\tqemu box {}'.format(node_spec['host_box']))
636         print('+\thostname={}'.format(node_spec['node_fields']['hostname']))
637
638     # write a timestamp in /vservers/<>.timestamp
639     # cannot be inside the vserver, that causes vserver .. build to cough
640     def plcvm_timestamp(self):
641         "Create a timestamp to remember creation date for this plc"
642         now = int(time.time())
643         # TODO-lxc check this one
644         # a first approx. is to store the timestamp close to the VM root like vs does
645         stamp_path = self.vm_timestamp_path()
646         stamp_dir = os.path.dirname(stamp_path)
647         utils.system(self.test_ssh.actual_command("mkdir -p {}".format(stamp_dir)))
648         return utils.system(self.test_ssh.actual_command("echo {:d} > {}".format(now, stamp_path))) == 0
649         
650     # this is called inconditionnally at the beginning of the test sequence 
651     # just in case this is a rerun, so if the vm is not running it's fine
652     def plcvm_delete(self):
653         "vserver delete the test myplc"
654         stamp_path = self.vm_timestamp_path()
655         self.run_in_host("rm -f {}".format(stamp_path))
656         self.run_in_host("virsh -c lxc:// destroy {}".format(self.vservername))
657         self.run_in_host("virsh -c lxc:// undefine {}".format(self.vservername))
658         self.run_in_host("rm -fr /vservers/{}".format(self.vservername))
659         return True
660
661     ### install
662     # historically the build was being fetched by the tests
663     # now the build pushes itself as a subdir of the tests workdir
664     # so that the tests do not have to worry about extracting the build (svn, git, or whatever)
665     def plcvm_create(self):
666         "vserver creation (no install done)"
667         # push the local build/ dir to the testplc box 
668         if self.is_local():
669             # a full path for the local calls
670             build_dir = os.path.dirname(sys.argv[0])
671             # sometimes this is empty - set to "." in such a case
672             if not build_dir:
673                 build_dir="."
674             build_dir += "/build"
675         else:
676             # use a standard name - will be relative to remote buildname
677             build_dir = "build"
678             # remove for safety; do *not* mkdir first, otherwise we end up with build/build/
679             self.test_ssh.rmdir(build_dir)
680             self.test_ssh.copy(build_dir, recursive=True)
681         # the repo url is taken from arch-rpms-url 
682         # with the last step (i386) removed
683         repo_url = self.options.arch_rpms_url
684         for level in [ 'arch' ]:
685             repo_url = os.path.dirname(repo_url)
686
687         # invoke initvm (drop support for vs)
688         script = "lbuild-initvm.sh"
689         script_options = ""
690         # pass the vbuild-nightly options to [lv]test-initvm
691         script_options += " -p {}".format(self.options.personality)
692         script_options += " -d {}".format(self.options.pldistro)
693         script_options += " -f {}".format(self.options.fcdistro)
694         script_options += " -r {}".format(repo_url)
695         vserver_name = self.vservername
696         try:
697             vserver_hostname = socket.gethostbyaddr(self.vserverip)[0]
698             script_options += " -n {}".format(vserver_hostname)
699         except:
700             print("Cannot reverse lookup {}".format(self.vserverip))
701             print("This is considered fatal, as this might pollute the test results")
702             return False
703         create_vserver="{build_dir}/{script} {script_options} {vserver_name}".format(**locals())
704         return self.run_in_host(create_vserver) == 0
705
706     ### install_rpm 
707     def plc_install(self):
708         """
709         yum install myplc, noderepo
710         plain bootstrapfs is not installed anymore
711         """
712
713         # compute nodefamily
714         if self.options.personality == "linux32":
715             arch = "i386"
716         elif self.options.personality == "linux64":
717             arch = "x86_64"
718         else:
719             raise Exception("Unsupported personality {}".format(self.options.personality))
720         nodefamily = "{}-{}-{}".format(self.options.pldistro, self.options.fcdistro, arch)
721
722         pkgs_list=[]
723         pkgs_list.append("slicerepo-{}".format(nodefamily))
724         pkgs_list.append("myplc")
725         pkgs_list.append("noderepo-{}".format(nodefamily))
726         pkgs_string=" ".join(pkgs_list)
727         return self.yum_install(pkgs_list)
728
729     ###
730     def mod_python(self):
731         """yum install mod_python, useful on f18 and above so as to avoid broken wsgi"""
732         return self.yum_install( ['mod_python'] )
733
734     ### 
735     def plc_configure(self):
736         "run plc-config-tty"
737         tmpname = '{}.plc-config-tty'.format(self.name())
738         with open(tmpname,'w') as fileconf:
739             for (var,value) in self.plc_spec['settings'].items():
740                 fileconf.write('e {}\n{}\n'.format(var, value))
741             fileconf.write('w\n')
742             fileconf.write('q\n')
743         utils.system('cat {}'.format(tmpname))
744         self.run_in_guest_piped('cat {}'.format(tmpname), 'plc-config-tty')
745         utils.system('rm {}'.format(tmpname))
746         return True
747
748 # f14 is a bit odd in this respect, although this worked fine in guests up to f18
749 # however using a vplc guest under f20 requires this trick
750 # the symptom is this: service plc start
751 # Starting plc (via systemctl):  Failed to get D-Bus connection: \
752 #    Failed to connect to socket /org/freedesktop/systemd1/private: Connection refused
753 # weird thing is the doc says f14 uses upstart by default and not systemd
754 # so this sounds kind of harmless
755     def start_service(self, service):
756         return self.start_stop_service(service, 'start')
757     def stop_service(self, service):
758         return self.start_stop_service(service, 'stop')
759
760     def start_stop_service(self, service, start_or_stop):
761         "utility to start/stop a service with the special trick for f14"
762         if self.options.fcdistro != 'f14':
763             return self.run_in_guest("service {} {}".format(service, start_or_stop)) == 0
764         else:
765             # patch /sbin/service so it does not reset environment
766             self.run_in_guest('sed -i -e \\"s,env -i,env,\\" /sbin/service')
767             # this is because our own scripts in turn call service 
768             return self.run_in_guest("SYSTEMCTL_SKIP_REDIRECT=true service {} {}"\
769                                      .format(service, start_or_stop)) == 0
770
771     def plc_start(self):
772         "service plc start"
773         return self.start_service('plc')
774
775     def plc_stop(self):
776         "service plc stop"
777         return self.stop_service('plc')
778
779     def plcvm_start(self):
780         "start the PLC vserver"
781         self.start_guest()
782         return True
783
784     def plcvm_stop(self):
785         "stop the PLC vserver"
786         self.stop_guest()
787         return True
788
789     # stores the keys from the config for further use
790     def keys_store(self):
791         "stores test users ssh keys in keys/"
792         for key_spec in self.plc_spec['keys']:
793                 TestKey(self,key_spec).store_key()
794         return True
795
796     def keys_clean(self):
797         "removes keys cached in keys/"
798         utils.system("rm -rf ./keys")
799         return True
800
801     # fetches the ssh keys in the plc's /etc/planetlab and stores them in keys/
802     # for later direct access to the nodes
803     def keys_fetch(self):
804         "gets ssh keys in /etc/planetlab/ and stores them locally in keys/"
805         dir="./keys"
806         if not os.path.isdir(dir):
807             os.mkdir(dir)
808         vservername = self.vservername
809         vm_root = self.vm_root_in_host()
810         overall = True
811         prefix = 'debug_ssh_key'
812         for ext in ['pub', 'rsa'] :
813             src = "{vm_root}/etc/planetlab/{prefix}.{ext}".format(**locals())
814             dst = "keys/{vservername}-debug.{ext}".format(**locals())
815             if self.test_ssh.fetch(src, dst) != 0:
816                 overall=False
817         return overall
818
819     def sites(self):
820         "create sites with PLCAPI"
821         return self.do_sites()
822     
823     def delete_sites(self):
824         "delete sites with PLCAPI"
825         return self.do_sites(action="delete")
826     
827     def do_sites(self, action="add"):
828         for site_spec in self.plc_spec['sites']:
829             test_site = TestSite(self,site_spec)
830             if (action != "add"):
831                 utils.header("Deleting site {} in {}".format(test_site.name(), self.name()))
832                 test_site.delete_site()
833                 # deleted with the site
834                 #test_site.delete_users()
835                 continue
836             else:
837                 utils.header("Creating site {} & users in {}".format(test_site.name(), self.name()))
838                 test_site.create_site()
839                 test_site.create_users()
840         return True
841
842     def delete_all_sites(self):
843         "Delete all sites in PLC, and related objects"
844         print('auth_root', self.auth_root())
845         sites = self.apiserver.GetSites(self.auth_root(), {}, ['site_id','login_base'])
846         for site in sites:
847             # keep automatic site - otherwise we shoot in our own foot, root_auth is not valid anymore
848             if site['login_base'] == self.plc_spec['settings']['PLC_SLICE_PREFIX']:
849                 continue
850             site_id = site['site_id']
851             print('Deleting site_id', site_id)
852             self.apiserver.DeleteSite(self.auth_root(), site_id)
853         return True
854
855     def nodes(self):
856         "create nodes with PLCAPI"
857         return self.do_nodes()
858     def delete_nodes(self):
859         "delete nodes with PLCAPI"
860         return self.do_nodes(action="delete")
861
862     def do_nodes(self, action="add"):
863         for site_spec in self.plc_spec['sites']:
864             test_site = TestSite(self, site_spec)
865             if action != "add":
866                 utils.header("Deleting nodes in site {}".format(test_site.name()))
867                 for node_spec in site_spec['nodes']:
868                     test_node = TestNode(self, test_site, node_spec)
869                     utils.header("Deleting {}".format(test_node.name()))
870                     test_node.delete_node()
871             else:
872                 utils.header("Creating nodes for site {} in {}".format(test_site.name(), self.name()))
873                 for node_spec in site_spec['nodes']:
874                     utils.pprint('Creating node {}'.format(node_spec), node_spec)
875                     test_node = TestNode(self, test_site, node_spec)
876                     test_node.create_node()
877         return True
878
879     def nodegroups(self):
880         "create nodegroups with PLCAPI"
881         return self.do_nodegroups("add")
882     def delete_nodegroups(self):
883         "delete nodegroups with PLCAPI"
884         return self.do_nodegroups("delete")
885
886     YEAR = 365*24*3600
887     @staticmethod
888     def translate_timestamp(start, grain, timestamp):
889         if timestamp < TestPlc.YEAR:
890             return start + timestamp*grain
891         else:
892             return timestamp
893
894     @staticmethod
895     def timestamp_printable(timestamp):
896         return time.strftime('%m-%d %H:%M:%S UTC', time.gmtime(timestamp))
897
898     def leases(self):
899         "create leases (on reservable nodes only, use e.g. run -c default -c resa)"
900         now = int(time.time())
901         grain = self.apiserver.GetLeaseGranularity(self.auth_root())
902         print('API answered grain=', grain)
903         start = (now//grain)*grain
904         start += grain
905         # find out all nodes that are reservable
906         nodes = self.all_reservable_nodenames()
907         if not nodes: 
908             utils.header("No reservable node found - proceeding without leases")
909             return True
910         ok = True
911         # attach them to the leases as specified in plc_specs
912         # this is where the 'leases' field gets interpreted as relative of absolute
913         for lease_spec in self.plc_spec['leases']:
914             # skip the ones that come with a null slice id
915             if not lease_spec['slice']:
916                 continue
917             lease_spec['t_from']  = TestPlc.translate_timestamp(start, grain, lease_spec['t_from'])
918             lease_spec['t_until'] = TestPlc.translate_timestamp(start, grain, lease_spec['t_until'])
919             lease_addition = self.apiserver.AddLeases(self.auth_root(), nodes, lease_spec['slice'],
920                                                       lease_spec['t_from'], lease_spec['t_until'])
921             if lease_addition['errors']:
922                 utils.header("Cannot create leases, {}".format(lease_addition['errors']))
923                 ok = False
924             else:
925                 utils.header('Leases on nodes {} for {} from {:d} ({}) until {:d} ({})'\
926                              .format(nodes, lease_spec['slice'],
927                                      lease_spec['t_from'],  TestPlc.timestamp_printable(lease_spec['t_from']),
928                                      lease_spec['t_until'], TestPlc.timestamp_printable(lease_spec['t_until'])))
929                 
930         return ok
931
932     def delete_leases(self):
933         "remove all leases in the myplc side"
934         lease_ids = [ l['lease_id'] for l in self.apiserver.GetLeases(self.auth_root())]
935         utils.header("Cleaning leases {}".format(lease_ids))
936         self.apiserver.DeleteLeases(self.auth_root(), lease_ids)
937         return True
938
939     def list_leases(self):
940         "list all leases known to the myplc"
941         leases = self.apiserver.GetLeases(self.auth_root())
942         now = int(time.time())
943         for l in leases:
944             current = l['t_until'] >= now
945             if self.options.verbose or current:
946                 utils.header("{} {} from {} until {}"\
947                              .format(l['hostname'], l['name'],
948                                      TestPlc.timestamp_printable(l['t_from']), 
949                                      TestPlc.timestamp_printable(l['t_until'])))
950         return True
951
952     # create nodegroups if needed, and populate
953     def do_nodegroups(self, action="add"):
954         # 1st pass to scan contents
955         groups_dict = {}
956         for site_spec in self.plc_spec['sites']:
957             test_site = TestSite(self,site_spec)
958             for node_spec in site_spec['nodes']:
959                 test_node = TestNode(self, test_site, node_spec)
960                 if 'nodegroups' in node_spec:
961                     nodegroupnames = node_spec['nodegroups']
962                     if isinstance(nodegroupnames, str):
963                         nodegroupnames = [ nodegroupnames ]
964                     for nodegroupname in nodegroupnames:
965                         if nodegroupname not in groups_dict:
966                             groups_dict[nodegroupname] = []
967                         groups_dict[nodegroupname].append(test_node.name())
968         auth = self.auth_root()
969         overall = True
970         for (nodegroupname,group_nodes) in groups_dict.items():
971             if action == "add":
972                 print('nodegroups:', 'dealing with nodegroup',\
973                     nodegroupname, 'on nodes', group_nodes)
974                 # first, check if the nodetagtype is here
975                 tag_types = self.apiserver.GetTagTypes(auth, {'tagname':nodegroupname})
976                 if tag_types:
977                     tag_type_id = tag_types[0]['tag_type_id']
978                 else:
979                     tag_type_id = self.apiserver.AddTagType(auth,
980                                                             {'tagname' : nodegroupname,
981                                                              'description' : 'for nodegroup {}'.format(nodegroupname),
982                                                              'category' : 'test'})
983                 print('located tag (type)', nodegroupname, 'as', tag_type_id)
984                 # create nodegroup
985                 nodegroups = self.apiserver.GetNodeGroups(auth, {'groupname' : nodegroupname})
986                 if not nodegroups:
987                     self.apiserver.AddNodeGroup(auth, nodegroupname, tag_type_id, 'yes')
988                     print('created nodegroup', nodegroupname, \
989                         'from tagname', nodegroupname, 'and value', 'yes')
990                 # set node tag on all nodes, value='yes'
991                 for nodename in group_nodes:
992                     try:
993                         self.apiserver.AddNodeTag(auth, nodename, nodegroupname, "yes")
994                     except:
995                         traceback.print_exc()
996                         print('node', nodename, 'seems to already have tag', nodegroupname)
997                     # check anyway
998                     try:
999                         expect_yes = self.apiserver.GetNodeTags(auth,
1000                                                                 {'hostname' : nodename,
1001                                                                  'tagname'  : nodegroupname},
1002                                                                 ['value'])[0]['value']
1003                         if expect_yes != "yes":
1004                             print('Mismatch node tag on node',nodename,'got',expect_yes)
1005                             overall = False
1006                     except:
1007                         if not self.options.dry_run:
1008                             print('Cannot find tag', nodegroupname, 'on node', nodename)
1009                             overall = False
1010             else:
1011                 try:
1012                     print('cleaning nodegroup', nodegroupname)
1013                     self.apiserver.DeleteNodeGroup(auth, nodegroupname)
1014                 except:
1015                     traceback.print_exc()
1016                     overall = False
1017         return overall
1018
1019     # a list of TestNode objs
1020     def all_nodes(self):
1021         nodes=[]
1022         for site_spec in self.plc_spec['sites']:
1023             test_site = TestSite(self,site_spec)
1024             for node_spec in site_spec['nodes']:
1025                 nodes.append(TestNode(self, test_site, node_spec))
1026         return nodes
1027
1028     # return a list of tuples (nodename,qemuname)
1029     def all_node_infos(self) :
1030         node_infos = []
1031         for site_spec in self.plc_spec['sites']:
1032             node_infos += [ (node_spec['node_fields']['hostname'], node_spec['host_box']) \
1033                                 for node_spec in site_spec['nodes'] ]
1034         return node_infos
1035     
1036     def all_nodenames(self):
1037         return [ x[0] for x in self.all_node_infos() ]
1038     def all_reservable_nodenames(self): 
1039         res = []
1040         for site_spec in self.plc_spec['sites']:
1041             for node_spec in site_spec['nodes']:
1042                 node_fields = node_spec['node_fields']
1043                 if 'node_type' in node_fields and node_fields['node_type'] == 'reservable':
1044                     res.append(node_fields['hostname'])
1045         return res
1046
1047     # silent_minutes : during the first <silent_minutes> minutes nothing gets printed
1048     def nodes_check_boot_state(self, target_boot_state, timeout_minutes,
1049                                silent_minutes, period_seconds = 15):
1050         if self.options.dry_run:
1051             print('dry_run')
1052             return True
1053
1054         class CompleterTaskBootState(CompleterTask):
1055             def __init__(self, test_plc, hostname):
1056                 self.test_plc = test_plc
1057                 self.hostname = hostname
1058                 self.last_boot_state = 'undef'
1059             def actual_run(self):
1060                 try:
1061                     node = self.test_plc.apiserver.GetNodes(self.test_plc.auth_root(),
1062                                                             [ self.hostname ],
1063                                                             ['boot_state'])[0]
1064                     self.last_boot_state = node['boot_state'] 
1065                     return self.last_boot_state == target_boot_state
1066                 except:
1067                     return False
1068             def message(self):
1069                 return "CompleterTaskBootState with node {}".format(self.hostname)
1070             def failure_epilogue(self):
1071                 print("node {} in state {} - expected {}"\
1072                     .format(self.hostname, self.last_boot_state, target_boot_state))
1073                 
1074         timeout = timedelta(minutes=timeout_minutes)
1075         graceout = timedelta(minutes=silent_minutes)
1076         period   = timedelta(seconds=period_seconds)
1077         # the nodes that haven't checked yet - start with a full list and shrink over time
1078         utils.header("checking nodes boot state (expected {})".format(target_boot_state))
1079         tasks = [ CompleterTaskBootState(self,hostname) \
1080                       for (hostname,_) in self.all_node_infos() ]
1081         message = 'check_boot_state={}'.format(target_boot_state)
1082         return Completer(tasks, message=message).run(timeout, graceout, period)
1083
1084     def nodes_booted(self):
1085         return self.nodes_check_boot_state('boot', timeout_minutes=30, silent_minutes=28)
1086
1087     def probe_kvm_iptables(self):
1088         (_,kvmbox) = self.all_node_infos()[0]
1089         TestSsh(kvmbox).run("iptables-save")
1090         return True
1091
1092     # probing nodes
1093     def check_nodes_ping(self, timeout_seconds=30, period_seconds=10):
1094         class CompleterTaskPingNode(CompleterTask):
1095             def __init__(self, hostname):
1096                 self.hostname = hostname
1097             def run(self, silent):
1098                 command="ping -c 1 -w 1 {} >& /dev/null".format(self.hostname)
1099                 return utils.system(command, silent=silent) == 0
1100             def failure_epilogue(self):
1101                 print("Cannot ping node with name {}".format(self.hostname))
1102         timeout = timedelta(seconds = timeout_seconds)
1103         graceout = timeout
1104         period = timedelta(seconds = period_seconds)
1105         node_infos = self.all_node_infos()
1106         tasks = [ CompleterTaskPingNode(h) for (h,_) in node_infos ]
1107         return Completer(tasks, message='ping_node').run(timeout, graceout, period)
1108
1109     # ping node before we try to reach ssh, helpful for troubleshooting failing bootCDs
1110     def ping_node(self):
1111         "Ping nodes"
1112         return self.check_nodes_ping()
1113
1114     def check_nodes_ssh(self, debug, timeout_minutes, silent_minutes, period_seconds=15):
1115         # various delays 
1116         timeout  = timedelta(minutes=timeout_minutes)
1117         graceout = timedelta(minutes=silent_minutes)
1118         period   = timedelta(seconds=period_seconds)
1119         vservername = self.vservername
1120         if debug: 
1121             message = "debug"
1122             completer_message = 'ssh_node_debug'
1123             local_key = "keys/{vservername}-debug.rsa".format(**locals())
1124         else: 
1125             message = "boot"
1126             completer_message = 'ssh_node_boot'
1127             local_key = "keys/key_admin.rsa"
1128         utils.header("checking ssh access to nodes (expected in {} mode)".format(message))
1129         node_infos = self.all_node_infos()
1130         tasks = [ CompleterTaskNodeSsh(nodename, qemuname, local_key,
1131                                         boot_state=message, dry_run=self.options.dry_run) \
1132                       for (nodename, qemuname) in node_infos ]
1133         return Completer(tasks, message=completer_message).run(timeout, graceout, period)
1134         
1135     def ssh_node_debug(self):
1136         "Tries to ssh into nodes in debug mode with the debug ssh key"
1137         return self.check_nodes_ssh(debug = True,
1138                                     timeout_minutes = self.ssh_node_debug_timeout,
1139                                     silent_minutes = self.ssh_node_debug_silent)
1140     
1141     def ssh_node_boot(self):
1142         "Tries to ssh into nodes in production mode with the root ssh key"
1143         return self.check_nodes_ssh(debug = False,
1144                                     timeout_minutes = self.ssh_node_boot_timeout,
1145                                     silent_minutes = self.ssh_node_boot_silent)
1146
1147     def node_bmlogs(self):
1148         "Checks that there's a non-empty dir. /var/log/bm/raw"
1149         return utils.system(self.actual_command_in_guest("ls /var/log/bm/raw")) == 0
1150     
1151     @node_mapper
1152     def qemu_local_init(self): pass
1153     @node_mapper
1154     def bootcd(self): pass
1155     @node_mapper
1156     def qemu_local_config(self): pass
1157     @node_mapper
1158     def nodestate_reinstall(self): pass
1159     @node_mapper
1160     def nodestate_safeboot(self): pass
1161     @node_mapper
1162     def nodestate_boot(self): pass
1163     @node_mapper
1164     def nodestate_show(self): pass
1165     @node_mapper
1166     def qemu_export(self): pass
1167         
1168     ### check hooks : invoke scripts from hooks/{node,slice}
1169     def check_hooks_node(self): 
1170         return self.locate_first_node().check_hooks()
1171     def check_hooks_sliver(self) : 
1172         return self.locate_first_sliver().check_hooks()
1173     
1174     def check_hooks(self):
1175         "runs unit tests in the node and slice contexts - see hooks/{node,slice}"
1176         return self.check_hooks_node() and self.check_hooks_sliver()
1177
1178     ### initscripts
1179     def do_check_initscripts(self):
1180         class CompleterTaskInitscript(CompleterTask):
1181             def __init__(self, test_sliver, stamp):
1182                 self.test_sliver = test_sliver
1183                 self.stamp = stamp
1184             def actual_run(self):
1185                 return self.test_sliver.check_initscript_stamp(self.stamp)
1186             def message(self):
1187                 return "initscript checker for {}".format(self.test_sliver.name())
1188             def failure_epilogue(self):
1189                 print("initscript stamp {} not found in sliver {}"\
1190                     .format(self.stamp, self.test_sliver.name()))
1191             
1192         tasks = []
1193         for slice_spec in self.plc_spec['slices']:
1194             if 'initscriptstamp' not in slice_spec:
1195                 continue
1196             stamp = slice_spec['initscriptstamp']
1197             slicename = slice_spec['slice_fields']['name']
1198             for nodename in slice_spec['nodenames']:
1199                 print('nodename', nodename, 'slicename', slicename, 'stamp', stamp)
1200                 site,node = self.locate_node(nodename)
1201                 # xxx - passing the wrong site - probably harmless
1202                 test_site = TestSite(self, site)
1203                 test_slice = TestSlice(self, test_site, slice_spec)
1204                 test_node = TestNode(self, test_site, node)
1205                 test_sliver = TestSliver(self, test_node, test_slice)
1206                 tasks.append(CompleterTaskInitscript(test_sliver, stamp))
1207         return Completer(tasks, message='check_initscripts').\
1208             run (timedelta(minutes=5), timedelta(minutes=4), timedelta(seconds=10))
1209             
1210     def check_initscripts(self):
1211         "check that the initscripts have triggered"
1212         return self.do_check_initscripts()
1213     
1214     def initscripts(self):
1215         "create initscripts with PLCAPI"
1216         for initscript in self.plc_spec['initscripts']:
1217             utils.pprint('Adding Initscript in plc {}'.format(self.plc_spec['name']), initscript)
1218             self.apiserver.AddInitScript(self.auth_root(), initscript['initscript_fields'])
1219         return True
1220
1221     def delete_initscripts(self):
1222         "delete initscripts with PLCAPI"
1223         for initscript in self.plc_spec['initscripts']:
1224             initscript_name = initscript['initscript_fields']['name']
1225             print(('Attempting to delete {} in plc {}'.format(initscript_name, self.plc_spec['name'])))
1226             try:
1227                 self.apiserver.DeleteInitScript(self.auth_root(), initscript_name)
1228                 print(initscript_name, 'deleted')
1229             except:
1230                 print('deletion went wrong - probably did not exist')
1231         return True
1232
1233     ### manage slices
1234     def slices(self):
1235         "create slices with PLCAPI"
1236         return self.do_slices(action="add")
1237
1238     def delete_slices(self):
1239         "delete slices with PLCAPI"
1240         return self.do_slices(action="delete")
1241
1242     def fill_slices(self):
1243         "add nodes in slices with PLCAPI"
1244         return self.do_slices(action="fill")
1245
1246     def empty_slices(self):
1247         "remove nodes from slices with PLCAPI"
1248         return self.do_slices(action="empty")
1249
1250     def do_slices(self,  action="add"):
1251         for slice in self.plc_spec['slices']:
1252             site_spec = self.locate_site(slice['sitename'])
1253             test_site = TestSite(self,site_spec)
1254             test_slice=TestSlice(self,test_site,slice)
1255             if action == "delete":
1256                 test_slice.delete_slice()
1257             elif action == "fill":
1258                 test_slice.add_nodes()
1259             elif action == "empty":
1260                 test_slice.delete_nodes()
1261             else:
1262                 test_slice.create_slice()
1263         return True
1264         
1265     @slice_mapper__tasks(20, 10, 15)
1266     def ssh_slice(self): pass
1267     @slice_mapper__tasks(20, 19, 15)
1268     def ssh_slice_off(self): pass
1269     @slice_mapper__tasks(1, 1, 15)
1270     def slice_fs_present(self): pass
1271     @slice_mapper__tasks(1, 1, 15)
1272     def slice_fs_deleted(self): pass
1273
1274     # use another name so we can exclude/ignore it from the tests on the nightly command line
1275     def ssh_slice_again(self): return self.ssh_slice()
1276     # note that simply doing ssh_slice_again=ssh_slice would kind of work too
1277     # but for some reason the ignore-wrapping thing would not
1278
1279     @slice_mapper
1280     def ssh_slice_basics(self): pass
1281     @slice_mapper
1282     def check_vsys_defaults(self): pass
1283
1284     @node_mapper
1285     def keys_clear_known_hosts(self): pass
1286     
1287     def plcapi_urls(self):
1288         """
1289         attempts to reach the PLCAPI with various forms for the URL
1290         """
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