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