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