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