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