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