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