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