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