fe7bd44fc4b7267b31924636e1b1cfca2824b33f
[tests.git] / system / TestPlc.py
1 # $Id$
2 import os, os.path
3 import datetime
4 import time
5 import sys
6 import xmlrpclib
7 import datetime
8 import traceback
9 from types import StringTypes
10
11 import utils
12 from TestSite import TestSite
13 from TestNode import TestNode
14 from TestUser import TestUser
15 from TestKey import TestKey
16 from TestSlice import TestSlice
17 from TestSliver import TestSliver
18 from TestBox import TestBox
19 from TestSsh import TestSsh
20
21 # step methods must take (self, options) and return a boolean
22
23 def standby(minutes):
24         utils.header('Entering StandBy for %d mn'%minutes)
25         time.sleep(60*minutes)
26         return True
27
28 def standby_generic (func):
29     def actual(self,options):
30         minutes=int(func.__name__.split("_")[1])
31         return standby(minutes)
32     return actual
33
34 class TestPlc:
35
36     def __init__ (self,plc_spec):
37         self.plc_spec=plc_spec
38         self.path=os.path.dirname(sys.argv[0])
39         self.test_ssh=TestSsh(self.plc_spec['hostname'],self.path)
40         try:
41             self.vserverip=plc_spec['vserverip']
42             self.vservername=plc_spec['vservername']
43             self.url="https://%s:443/PLCAPI/"%plc_spec['vserverip']
44             self.vserver=True
45         except:
46             self.vserver=False
47             self.url="https://%s:443/PLCAPI/"%plc_spec['hostname']
48 #        utils.header('Using API url %s'%self.url)
49         self.server=xmlrpclib.Server(self.url,allow_none=True)
50         
51     def name(self):
52         name=self.plc_spec['name']
53         if self.vserver:
54             return name+"[%s]"%self.vservername
55         else:
56             return name+"[chroot]"
57
58     def hostname(self):
59         return self.plc_spec['hostname']
60
61     def is_local (self):
62         return self.test_ssh.is_local()
63
64     # define the API methods on this object through xmlrpc
65     # would help, but not strictly necessary
66     def connect (self):
67         pass
68
69     def full_command(self,command):
70         return self.test_ssh.to_host(self.host_to_guest(command))
71
72     def run_in_guest (self,command):
73         return utils.system(self.full_command(command))
74     
75     def run_in_host (self,command):
76         return utils.system(self.test_ssh.to_host(command))
77
78     #command gets run in the chroot/vserver
79     def host_to_guest(self,command):
80         if self.vserver:
81             return "vserver %s exec %s"%(self.vservername,command)
82         else:
83             return "chroot /plc/root %s"%TestSsh.backslash_shell_specials(command)
84     
85     # copy a file to the myplc root image - pass in_data=True if the file must go in /plc/data
86     def copy_in_guest (self, localfile, remotefile, in_data=False):
87         if in_data:
88             chroot_dest="/plc/data"
89         else:
90             chroot_dest="/plc/root"
91         if self.is_local():
92             if not self.vserver:
93                 utils.system("cp %s %s/%s"%(localfile,chroot_dest,remotefile))
94             else:
95                 utils.system("cp %s /vservers/%s/%s"%(localfile,self.vservername,remotefile))
96         else:
97             if not self.vserver:
98                 utils.system("scp %s %s:%s/%s"%(localfile,self.hostname(),chroot_dest,remotefile))
99             else:
100                 utils.system("scp %s %s@/vservers/%s/%s"%(localfile,self.hostname(),self.vservername,remotefile))
101
102
103         # xxx quick n dirty
104     def run_in_guest_piped (self,local,remote):
105         return utils.system(local+" | "+self.full_command(remote))
106         
107
108     def auth_root (self):
109         return {'Username':self.plc_spec['PLC_ROOT_USER'],
110                 'AuthMethod':'password',
111                 'AuthString':self.plc_spec['PLC_ROOT_PASSWORD'],
112                 'Role' : self.plc_spec['role']
113                 }
114     def locate_site (self,sitename):
115         for site in self.plc_spec['sites']:
116             if site['site_fields']['name'] == sitename:
117                 return site
118             if site['site_fields']['login_base'] == sitename:
119                 return site
120         raise Exception,"Cannot locate site %s"%sitename
121         
122     def locate_node (self,nodename):
123         for site in self.plc_spec['sites']:
124             for node in site['nodes']:
125                 if node['name'] == nodename:
126                     return (site,node)
127         raise Exception,"Cannot locate node %s"%nodename
128         
129     def locate_hostname (self,hostname):
130         for site in self.plc_spec['sites']:
131             for node in site['nodes']:
132                 if node['node_fields']['hostname'] == hostname:
133                     return (site,node)
134         raise Exception,"Cannot locate hostname %s"%hostname
135         
136     def locate_key (self,keyname):
137         for key in self.plc_spec['keys']:
138             if key['name'] == keyname:
139                 return key
140         raise Exception,"Cannot locate key %s"%keyname
141
142     # all different hostboxes used in this plc
143     def gather_hostBoxes(self):
144         # maps on sites and nodes, return [ (host_box,test_node) ]
145         tuples=[]
146         for site_spec in self.plc_spec['sites']:
147             test_site = TestSite (self,site_spec)
148             for node_spec in site_spec['nodes']:
149                 test_node = TestNode (self, test_site, node_spec)
150                 if not test_node.is_real():
151                     tuples.append( (test_node.host_box(),test_node) )
152         # transform into a dict { 'host_box' -> [ hostnames .. ] }
153         result = {}
154         for (box,node) in tuples:
155             if not result.has_key(box):
156                 result[box]=[node]
157             else:
158                 result[box].append(node)
159         return result
160                     
161     # a step for checking this stuff
162     def showboxes (self,options):
163         print 'showboxes'
164         for (box,nodes) in self.gather_hostBoxes().iteritems():
165             print box,":"," + ".join( [ node.name() for node in nodes ] )
166         return True
167
168     # make this a valid step
169     def kill_all_qemus(self,options):
170         for (box,nodes) in self.gather_hostBoxes().iteritems():
171             # this is the brute force version, kill all qemus on that host box
172             TestBox(box,options.buildname).kill_all_qemus()
173         return True
174
175     # make this a valid step
176     def list_all_qemus(self,options):
177         for (box,nodes) in self.gather_hostBoxes().iteritems():
178             # this is the brute force version, kill all qemus on that host box
179             TestBox(box,options.buildname).list_all_qemus()
180         return True
181
182     # kill only the right qemus
183     def list_qemus(self,options):
184         for (box,nodes) in self.gather_hostBoxes().iteritems():
185             # the fine-grain version
186             for node in nodes:
187                 node.list_qemu()
188         return True
189
190     # kill only the right qemus
191     def kill_qemus(self,options):
192         for (box,nodes) in self.gather_hostBoxes().iteritems():
193             # the fine-grain version
194             for node in nodes:
195                 node.kill_qemu()
196         return True
197
198     def clear_ssh_config (self,options):
199         # install local ssh_config file as root's .ssh/config - ssh should be quiet
200         # dir might need creation first
201         self.run_in_guest("mkdir /root/.ssh")
202         self.run_in_guest("chmod 700 /root/.ssh")
203         # this does not work - > redirection somehow makes it until an argument to cat
204         #self.run_in_guest_piped("cat ssh_config","cat > /root/.ssh/config")
205         self.copy_in_guest("ssh_config","/root/.ssh/config",True)
206         return True
207             
208     #################### step methods
209
210     ### uninstall
211     def uninstall_chroot(self,options):
212         self.run_in_host('service plc safestop')
213         #####detecting the last myplc version installed and remove it
214         self.run_in_host('rpm -e myplc')
215         ##### Clean up the /plc directory
216         self.run_in_host('rm -rf  /plc/data')
217         ##### stop any running vservers
218         self.run_in_host('for vserver in $(ls /vservers/* | sed -e s,/vservers/,,) ; do vserver $vserver stop ; done')
219         return True
220
221     def uninstall_vserver(self,options):
222         self.run_in_host("vserver --silent %s delete"%self.vservername)
223         return True
224
225     def uninstall(self,options):
226         # if there's a chroot-based myplc running, and then a native-based myplc is being deployed
227         # it sounds safer to have the former uninstalled too
228         # now the vserver method cannot be invoked for chroot instances as vservername is required
229         if self.vserver:
230             self.uninstall_vserver(options)
231             self.uninstall_chroot(options)
232         else:
233             self.uninstall_chroot(options)
234         return True
235
236     ### install
237     def install_chroot(self,options):
238         # nothing to do
239         return True
240
241     def install_vserver(self,options):
242         # we need build dir for vtest-init-vserver
243         if self.is_local():
244             # a full path for the local calls
245             build_dir=self.path+"/build"
246         else:
247             # use a standard name - will be relative to HOME 
248             build_dir="options.buildname"
249         # run checkout in any case - would do an update if already exists
250         build_checkout = "svn checkout %s %s"%(options.build_url,build_dir)
251         if self.run_in_host(build_checkout) != 0:
252             raise Exception,"Cannot checkout build dir"
253         # the repo url is taken from myplc-url 
254         # with the last two steps (i386/myplc...) removed
255         repo_url = options.myplc_url
256         for level in [ 'rpmname','arch' ]:
257             repo_url = os.path.dirname(repo_url)
258         create_vserver="%s/vtest-init-vserver.sh %s %s -- --interface eth0:%s"%\
259             (build_dir,self.vservername,repo_url,self.vserverip)
260         if self.run_in_host(create_vserver) != 0:
261             raise Exception,"Could not create vserver for %s"%self.vservername
262         return True
263
264     def install(self,options):
265         if self.vserver:
266             return self.install_vserver(options)
267         else:
268             return self.install_chroot(options)
269     
270     ### install_rpm
271     def cache_rpm(self,url):
272         self.run_in_host('rm -rf *.rpm')
273         utils.header('Curling rpm from %s'%url)
274         id= self.run_in_host('curl -O '+url)
275         if (id != 0):
276                 raise Exception,"Could not get rpm from  %s"%url
277                 return False
278         return True
279
280     def install_rpm_chroot(self,options):
281         rpm = os.path.basename(options.myplc_url)
282         if (not os.path.isfile(rpm)):
283                 self.cache_rpm(options.myplc_url)
284         utils.header('Installing the :  %s'%rpm)
285         self.run_in_host('rpm -Uvh '+rpm)
286         self.run_in_host('service plc mount')
287         return True
288
289     def install_rpm_vserver(self,options):
290         self.run_in_guest("yum -y install myplc-native")
291         return True
292
293     def install_rpm(self,options):
294         if self.vserver:
295             return self.install_rpm_vserver(options)
296         else:
297             return self.install_rpm_chroot(options)
298
299     ### 
300     def configure(self,options):
301         tmpname='%s.plc-config-tty'%(self.name())
302         fileconf=open(tmpname,'w')
303         for var in [ 'PLC_NAME',
304                      'PLC_ROOT_PASSWORD',
305                      'PLC_ROOT_USER',
306                      'PLC_MAIL_ENABLED',
307                      'PLC_MAIL_SUPPORT_ADDRESS',
308                      'PLC_DB_HOST',
309                      'PLC_API_HOST',
310                      'PLC_WWW_HOST',
311                      'PLC_BOOT_HOST',
312                      'PLC_NET_DNS1',
313                      'PLC_NET_DNS2']:
314             fileconf.write ('e %s\n%s\n'%(var,self.plc_spec[var]))
315         fileconf.write('w\n')
316         fileconf.write('q\n')
317         fileconf.close()
318         utils.system('cat %s'%tmpname)
319         self.run_in_guest_piped('cat %s'%tmpname,'plc-config-tty')
320         utils.system('rm %s'%tmpname)
321         return True
322
323     # the chroot install is slightly different to this respect
324     def start(self, options):
325         if self.vserver:
326             self.run_in_guest('service plc start')
327         else:
328             self.run_in_host('service plc start')
329         return True
330         
331     def stop(self, options):
332         if self.vserver:
333             self.run_in_guest('service plc stop')
334         else:
335             self.run_in_host('service plc stop')
336         return True
337         
338     # could use a TestKey class
339     def store_keys(self, options):
340         for key_spec in self.plc_spec['keys']:
341                 TestKey(self,key_spec).store_key()
342         return True
343
344     def clean_keys(self, options):
345         utils.system("rm -rf %s/keys/"%self.path)
346
347     def sites (self,options):
348         return self.do_sites(options)
349     
350     def clean_sites (self,options):
351         return self.do_sites(options,action="delete")
352     
353     def do_sites (self,options,action="add"):
354         for site_spec in self.plc_spec['sites']:
355             test_site = TestSite (self,site_spec)
356             if (action != "add"):
357                 utils.header("Deleting site %s in %s"%(test_site.name(),self.name()))
358                 test_site.delete_site()
359                 # deleted with the site
360                 #test_site.delete_users()
361                 continue
362             else:
363                 utils.header("Creating site %s & users in %s"%(test_site.name(),self.name()))
364                 test_site.create_site()
365                 test_site.create_users()
366         return True
367
368     def nodes (self, options):
369         return self.do_nodes(options)
370     def clean_nodes (self, options):
371         return self.do_nodes(options,action="delete")
372
373     def do_nodes (self, options,action="add"):
374         for site_spec in self.plc_spec['sites']:
375             test_site = TestSite (self,site_spec)
376             if action != "add":
377                 utils.header("Deleting nodes in site %s"%test_site.name())
378                 for node_spec in site_spec['nodes']:
379                     test_node=TestNode(self,test_site,node_spec)
380                     utils.header("Deleting %s"%test_node.name())
381                     test_node.delete_node()
382             else:
383                 utils.header("Creating nodes for site %s in %s"%(test_site.name(),self.name()))
384                 for node_spec in site_spec['nodes']:
385                     utils.pprint('Creating node %s'%node_spec,node_spec)
386                     test_node = TestNode (self,test_site,node_spec)
387                     test_node.create_node ()
388         return True
389
390     # create nodegroups if needed, and populate
391     # no need for a clean_nodegroups if we are careful enough
392     def nodegroups (self, options):
393         # 1st pass to scan contents
394         groups_dict = {}
395         for site_spec in self.plc_spec['sites']:
396             test_site = TestSite (self,site_spec)
397             for node_spec in site_spec['nodes']:
398                 test_node=TestNode (self,test_site,node_spec)
399                 if node_spec.has_key('nodegroups'):
400                     nodegroupnames=node_spec['nodegroups']
401                     if isinstance(nodegroupnames,StringTypes):
402                         nodegroupnames = [ nodegroupnames ]
403                     for nodegroupname in nodegroupnames:
404                         if not groups_dict.has_key(nodegroupname):
405                             groups_dict[nodegroupname]=[]
406                         groups_dict[nodegroupname].append(test_node.name())
407         auth=self.auth_root()
408         for (nodegroupname,group_nodes) in groups_dict.iteritems():
409             try:
410                 self.server.GetNodeGroups(auth,{'name':nodegroupname})[0]
411             except:
412                 self.server.AddNodeGroup(auth,{'name':nodegroupname})
413             for node in group_nodes:
414                 self.server.AddNodeToNodeGroup(auth,node,nodegroupname)
415         return True
416
417     def all_hostnames (self) :
418         hostnames = []
419         for site_spec in self.plc_spec['sites']:
420             hostnames += [ node_spec['node_fields']['hostname'] \
421                            for node_spec in site_spec['nodes'] ]
422         return hostnames
423
424     # gracetime : during the first <gracetime> minutes nothing gets printed
425     def do_nodes_booted (self, minutes, gracetime=2):
426         # compute timeout
427         timeout = datetime.datetime.now()+datetime.timedelta(minutes=minutes)
428         graceout = datetime.datetime.now()+datetime.timedelta(minutes=gracetime)
429         # the nodes that haven't checked yet - start with a full list and shrink over time
430         tocheck = self.all_hostnames()
431         utils.header("checking nodes %r"%tocheck)
432         # create a dict hostname -> status
433         status = dict ( [ (hostname,'undef') for hostname in tocheck ] )
434         while tocheck:
435             # get their status
436             tocheck_status=self.server.GetNodes(self.auth_root(), tocheck, ['hostname','boot_state' ] )
437             # update status
438             for array in tocheck_status:
439                 hostname=array['hostname']
440                 boot_state=array['boot_state']
441                 if boot_state == 'boot':
442                     utils.header ("%s has reached the 'boot' state"%hostname)
443                 else:
444                     # if it's a real node, never mind
445                     (site_spec,node_spec)=self.locate_hostname(hostname)
446                     if TestNode.is_real_model(node_spec['node_fields']['model']):
447                         utils.header("WARNING - Real node %s in %s - ignored"%(hostname,boot_state))
448                         # let's cheat
449                         boot_state = 'boot'
450                     if datetime.datetime.now() > graceout:
451                         utils.header ("%s still in '%s' state"%(hostname,boot_state))
452                         graceout=datetime.datetime.now()+datetime.timedelta(1)
453                 status[hostname] = boot_state
454             # refresh tocheck
455             tocheck = [ hostname for (hostname,boot_state) in status.iteritems() if boot_state != 'boot' ]
456             if not tocheck:
457                 return True
458             if datetime.datetime.now() > timeout:
459                 for hostname in tocheck:
460                     utils.header("FAILURE due to %s in '%s' state"%(hostname,status[hostname]))
461                 return False
462             # otherwise, sleep for a while
463             time.sleep(15)
464         # only useful in empty plcs
465         return True
466
467     def nodes_booted(self,options):
468         return self.do_nodes_booted(minutes=0)
469     
470     #to scan and store the nodes's public keys and avoid to ask for confirmation when  ssh 
471     def scan_publicKeys(self,hostnames):
472         try:
473             temp_knownhosts="/root/known_hosts"
474             remote_knownhosts="/root/.ssh/known_hosts"
475             self.run_in_host("touch %s"%temp_knownhosts )
476             for hostname in hostnames:
477                 utils.header("Scan Public %s key and store it in the known_host file(under the root image) "%hostname)
478                 scan=self.run_in_host('ssh-keyscan -t rsa %s >> %s '%(hostname,temp_knownhosts))
479             #Store the public keys in the right root image
480             self.copy_in_guest(temp_knownhosts,remote_knownhosts,True)
481             #clean the temp keys file used
482             self.run_in_host('rm -f  %s '%temp_knownhosts )
483         except Exception, err:
484             print err
485             
486     def do_check_nodesSsh(self,minutes):
487         # compute timeout
488         timeout = datetime.datetime.now()+datetime.timedelta(minutes=minutes)
489         tocheck = self.all_hostnames()
490         self.scan_publicKeys(tocheck)
491         utils.header("checking Connectivity on nodes %r"%tocheck)
492         while tocheck:
493             for hostname in tocheck:
494                 # try to ssh in nodes
495                 access=self.run_in_guest('ssh -o StrictHostKeyChecking=no -o BatchMode=yes -i /etc/planetlab/root_ssh_key.rsa root@%s date'%hostname )
496                 if (not access):
497                     utils.header('The node %s is sshable -->'%hostname)
498                     # refresh tocheck
499                     tocheck.remove(hostname)
500                 else:
501                     (site_spec,node_spec)=self.locate_hostname(hostname)
502                     if TestNode.is_real_model(node_spec['node_fields']['model']):
503                         utils.header ("WARNING : check ssh access into real node %s - skipped"%hostname)
504                         tocheck.remove(hostname)
505             if  not tocheck:
506                 return True
507             if datetime.datetime.now() > timeout:
508                 for hostname in tocheck:
509                     utils.header("FAILURE to ssh into %s"%hostname)
510                 return False
511             # otherwise, sleep for a while
512             time.sleep(15)
513         # only useful in empty plcs
514         return True
515         
516     def nodes_ssh(self, options):
517         return  self.do_check_nodesSsh(minutes=2)
518     
519     def bootcd (self, options):
520         for site_spec in self.plc_spec['sites']:
521             test_site = TestSite (self,site_spec)
522             for node_spec in site_spec['nodes']:
523                 test_node=TestNode (self,test_site,node_spec)
524                 test_node.prepare_area()
525                 test_node.create_boot_cd()
526                 test_node.configure_qemu()
527         return True
528
529     def do_check_intiscripts(self):
530         for site_spec in self.plc_spec['sites']:
531                 test_site = TestSite (self,site_spec)
532                 test_node = TestNode (self,test_site,site_spec['nodes'])
533                 for slice_spec in self.plc_spec['slices']:
534                         test_slice=TestSlice (self,test_site,slice_spec)
535                         test_sliver=TestSliver(self,test_node,test_slice)
536                         init_status=test_sliver.get_initscript(slice_spec)
537                         if (not init_status):
538                                 return False
539                 return init_status
540             
541     def check_initscripts(self, options):
542             return self.do_check_intiscripts()
543                     
544     def initscripts (self, options):
545         for initscript in self.plc_spec['initscripts']:
546             utils.pprint('Adding Initscript in plc %s'%self.plc_spec['name'],initscript)
547             self.server.AddInitScript(self.auth_root(),initscript['initscript_fields'])
548         return True
549
550     def slices (self, options):
551         return self.do_slices()
552
553     def clean_slices (self, options):
554         return self.do_slices("delete")
555
556     def do_slices (self,  action="add"):
557         for slice in self.plc_spec['slices']:
558             site_spec = self.locate_site (slice['sitename'])
559             test_site = TestSite(self,site_spec)
560             test_slice=TestSlice(self,test_site,slice)
561             if action != "add":
562                 utils.header("Deleting slices in site %s"%test_site.name())
563                 test_slice.delete_slice()
564             else:    
565                 utils.pprint("Creating slice",slice)
566                 test_slice.create_slice()
567                 utils.header('Created Slice %s'%slice['slice_fields']['name'])
568         return True
569         
570     def check_slices(self, options):
571         for slice_spec in self.plc_spec['slices']:
572             site_spec = self.locate_site (slice_spec['sitename'])
573             test_site = TestSite(self,site_spec)
574             test_slice=TestSlice(self,test_site,slice_spec)
575             status=test_slice.do_check_slice(options)
576             if (not status):
577                 return False
578         return status
579     
580     def start_nodes (self, options):
581         utils.header("Starting  nodes")
582         for site_spec in self.plc_spec['sites']:
583             TestSite(self,site_spec).start_nodes (options)
584         return True
585
586     def stop_nodes (self, options):
587         self.kill_all_qemus(options)
588         return True
589
590     def check_tcp (self, options):
591             #we just need to create a sliver object nothing else
592             test_sliver=TestSliver(self,
593                                    TestNode(self, TestSite(self,self.plc_spec['sites'][0]),
594                                             self.plc_spec['sites'][0]['nodes'][0]),
595                                    TestSlice(self,TestSite(self,self.plc_spec['sites'][0]),
596                                              self.plc_spec['slices']))
597             return test_sliver.do_check_tcp(self.plc_spec['tcp_param'],options)
598
599     # returns the filename to use for sql dump/restore, using options.dbname if set
600     def dbfile (self, database, options):
601         # uses options.dbname if it is found
602         try:
603             name=options.dbname
604             if not isinstance(name,StringTypes):
605                 raise Exception
606         except:
607             t=datetime.datetime.now()
608             d=t.date()
609             name=str(d)
610         return "/root/%s-%s.sql"%(database,name)
611
612     def db_dump(self, options):
613         
614         dump=self.dbfile("planetab4",options)
615         self.run_in_guest('pg_dump -U pgsqluser planetlab4 -f '+ dump)
616         utils.header('Dumped planetlab4 database in %s'%dump)
617         return True
618
619     def db_restore(self, options):
620         dump=self.dbfile("planetab4",options)
621         ##stop httpd service
622         self.run_in_guest('service httpd stop')
623         # xxx - need another wrapper
624         self.run_in_guest_piped('echo drop database planetlab4','psql --user=pgsqluser template1')
625         self.run_in_guest('createdb -U postgres --encoding=UNICODE --owner=pgsqluser planetlab4')
626         self.run_in_guest('psql -U pgsqluser planetlab4 -f '+dump)
627         ##starting httpd service
628         self.run_in_guest('service httpd start')
629
630         utils.header('Database restored from ' + dump)
631
632     @standby_generic 
633     def standby_1(): pass
634     @standby_generic 
635     def standby_2(): pass
636     @standby_generic 
637     def standby_3(): pass
638     @standby_generic 
639     def standby_4(): pass
640     @standby_generic 
641     def standby_5(): pass
642     @standby_generic 
643     def standby_6(): pass
644     @standby_generic 
645     def standby_7(): pass
646     @standby_generic 
647     def standby_8(): pass
648     @standby_generic 
649     def standby_9(): pass
650     @standby_generic 
651     def standby_10(): pass
652     @standby_generic 
653     def standby_11(): pass
654     @standby_generic 
655     def standby_12(): pass
656     @standby_generic 
657     def standby_13(): pass
658     @standby_generic 
659     def standby_14(): pass
660     @standby_generic 
661     def standby_15(): pass
662     @standby_generic 
663     def standby_16(): pass
664     @standby_generic 
665     def standby_17(): pass
666     @standby_generic 
667     def standby_18(): pass
668     @standby_generic 
669     def standby_19(): pass
670     @standby_generic 
671     def standby_20(): pass
672