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