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