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