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