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