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