fetches debug keys and knows how to use them
[tests.git] / system / TestPlc.py
1 # $Id$
2 import os, os.path
3 import datetime
4 import time
5 import sys
6 import traceback
7 from types import StringTypes
8 import socket
9
10 import utils
11 from TestSite import TestSite
12 from TestNode import TestNode
13 from TestUser import TestUser
14 from TestKey import TestKey
15 from TestSlice import TestSlice
16 from TestSliver import TestSliver
17 from TestBox import TestBox
18 from TestSsh import TestSsh
19 from TestApiserver import TestApiserver
20
21 # step methods must take (self) and return a boolean (options is a member of the class)
22
23 def standby(minutes,dry_run):
24     utils.header('Entering StandBy for %d mn'%minutes)
25     if dry_run:
26         print 'dry_run'
27     else:
28         time.sleep(60*minutes)
29     return True
30
31 def standby_generic (func):
32     def actual(self):
33         minutes=int(func.__name__.split("_")[1])
34         return standby(minutes,self.options.dry_run)
35     return actual
36
37 def node_mapper (method):
38     def actual(self):
39         overall=True
40         node_method = TestNode.__dict__[method.__name__]
41         for site_spec in self.plc_spec['sites']:
42             test_site = TestSite (self,site_spec)
43             for node_spec in site_spec['nodes']:
44                 test_node = TestNode (self,test_site,node_spec)
45                 if not node_method(test_node): overall=False
46         return overall
47     return actual
48
49 def slice_mapper_options (method):
50     def actual(self):
51         overall=True
52         slice_method = TestSlice.__dict__[method.__name__]
53         for slice_spec in self.plc_spec['slices']:
54             site_spec = self.locate_site (slice_spec['sitename'])
55             test_site = TestSite(self,site_spec)
56             test_slice=TestSlice(self,test_site,slice_spec)
57             if not slice_method(test_slice,self.options): overall=False
58         return overall
59     return actual
60
61 SEP='<sep>'
62
63 class TestPlc:
64
65     default_steps = ['display','uninstall','install','install_rpm', 
66                      'configure', 'start', 'fetch_keys', SEP,
67                      'store_keys', 'clear_known_hosts', 'initscripts', SEP,
68                      'sites', 'nodes', 'slices', 'nodegroups', SEP,
69                      'init_node','bootcd', 'configure_qemu', 'export_qemu',
70                      'kill_all_qemus', 'reinstall_node','start_node', SEP,
71                      'nodes_debug_ssh', 'nodes_boot_ssh', 'check_slice', 'check_initscripts', SEP,
72                      'check_sanity', 'check_tcp', 'plcsh_stress_test', SEP,
73                      'force_gather_logs', 'force_kill_qemus', 'force_record_tracker','force_free_tracker' ]
74     other_steps = [ 'stop_all_vservers','fresh_install', 'cache_rpm', 'stop', 'vs_start', SEP,
75                     'clean_initscripts', 'clean_nodegroups','clean_all_sites', SEP,
76                     'clean_sites', 'clean_nodes', 
77                     'clean_slices', 'clean_keys', SEP,
78                     'show_boxes', 'list_all_qemus', 'list_qemus', SEP,
79                     'db_dump' , 'db_restore', 'cleanup_trackers', 'cleanup_all_trackers',
80                     'standby_1 through 20'
81                     ]
82
83     @staticmethod
84     def printable_steps (list):
85         return " ".join(list).replace(" "+SEP+" "," \\\n")
86     @staticmethod
87     def valid_step (step):
88         return step != SEP
89
90     def __init__ (self,plc_spec,options):
91         self.plc_spec=plc_spec
92         self.options=options
93         self.test_ssh=TestSsh(self.plc_spec['hostname'],self.options.buildname)
94         try:
95             self.vserverip=plc_spec['vserverip']
96             self.vservername=plc_spec['vservername']
97             self.url="https://%s:443/PLCAPI/"%plc_spec['vserverip']
98             self.vserver=True
99         except:
100             raise Exception,'chroot-based myplc testing is deprecated'
101         self.apiserver=TestApiserver(self.url,options.dry_run)
102         
103     def name(self):
104         name=self.plc_spec['name']
105         return "%s.%s"%(name,self.vservername)
106
107     def hostname(self):
108         return self.plc_spec['hostname']
109
110     def is_local (self):
111         return self.test_ssh.is_local()
112
113     # define the API methods on this object through xmlrpc
114     # would help, but not strictly necessary
115     def connect (self):
116         pass
117
118     def actual_command_in_guest (self,command):
119         return self.test_ssh.actual_command(self.host_to_guest(command))
120     
121     def start_guest (self):
122       return utils.system(self.test_ssh.actual_command(self.start_guest_in_host()))
123     
124     def run_in_guest (self,command):
125         return utils.system(self.actual_command_in_guest(command))
126     
127     def run_in_host (self,command):
128         return self.test_ssh.run_in_buildname(command)
129
130     #command gets run in the vserver
131     def host_to_guest(self,command):
132         return "vserver %s exec %s"%(self.vservername,command)
133     
134     #command gets run in the vserver
135     def start_guest_in_host(self):
136         return "vserver %s start"%(self.vservername)
137     
138     # xxx quick n dirty
139     def run_in_guest_piped (self,local,remote):
140         return utils.system(local+" | "+self.test_ssh.actual_command(self.host_to_guest(remote),keep_stdin=True))
141
142     def auth_root (self):
143         return {'Username':self.plc_spec['PLC_ROOT_USER'],
144                 'AuthMethod':'password',
145                 'AuthString':self.plc_spec['PLC_ROOT_PASSWORD'],
146                 'Role' : self.plc_spec['role']
147                 }
148     def locate_site (self,sitename):
149         for site in self.plc_spec['sites']:
150             if site['site_fields']['name'] == sitename:
151                 return site
152             if site['site_fields']['login_base'] == sitename:
153                 return site
154         raise Exception,"Cannot locate site %s"%sitename
155         
156     def locate_node (self,nodename):
157         for site in self.plc_spec['sites']:
158             for node in site['nodes']:
159                 if node['name'] == nodename:
160                     return (site,node)
161         raise Exception,"Cannot locate node %s"%nodename
162         
163     def locate_hostname (self,hostname):
164         for site in self.plc_spec['sites']:
165             for node in site['nodes']:
166                 if node['node_fields']['hostname'] == hostname:
167                     return (site,node)
168         raise Exception,"Cannot locate hostname %s"%hostname
169         
170     def locate_key (self,keyname):
171         for key in self.plc_spec['keys']:
172             if key['name'] == keyname:
173                 return key
174         raise Exception,"Cannot locate key %s"%keyname
175
176     def locate_slice (self, slicename):
177         for slice in self.plc_spec['slices']:
178             if slice['slice_fields']['name'] == slicename:
179                 return slice
180         raise Exception,"Cannot locate slice %s"%slicename
181
182     def all_sliver_objs (self):
183         result=[]
184         for slice_spec in self.plc_spec['slices']:
185             slicename = slice_spec['slice_fields']['name']
186             for nodename in slice_spec['nodenames']:
187                 result.append(self.locate_sliver_obj (nodename,slicename))
188         return result
189
190     def locate_sliver_obj (self,nodename,slicename):
191         (site,node) = self.locate_node(nodename)
192         slice = self.locate_slice (slicename)
193         # build objects
194         test_site = TestSite (self, site)
195         test_node = TestNode (self, test_site,node)
196         # xxx the slice site is assumed to be the node site - mhh - probably harmless
197         test_slice = TestSlice (self, test_site, slice)
198         return TestSliver (self, test_node, test_slice)
199
200     def locate_first_node(self):
201         nodename=self.plc_spec['slices'][0]['nodenames'][0]
202         (site,node) = self.locate_node(nodename)
203         test_site = TestSite (self, site)
204         test_node = TestNode (self, test_site,node)
205         return test_node
206
207     def locate_first_sliver (self):
208         slice_spec=self.plc_spec['slices'][0]
209         slicename=slice_spec['slice_fields']['name']
210         nodename=slice_spec['nodenames'][0]
211         return self.locate_sliver_obj(nodename,slicename)
212
213     # all different hostboxes used in this plc
214     def gather_hostBoxes(self):
215         # maps on sites and nodes, return [ (host_box,test_node) ]
216         tuples=[]
217         for site_spec in self.plc_spec['sites']:
218             test_site = TestSite (self,site_spec)
219             for node_spec in site_spec['nodes']:
220                 test_node = TestNode (self, test_site, node_spec)
221                 if not test_node.is_real():
222                     tuples.append( (test_node.host_box(),test_node) )
223         # transform into a dict { 'host_box' -> [ test_node .. ] }
224         result = {}
225         for (box,node) in tuples:
226             if not result.has_key(box):
227                 result[box]=[node]
228             else:
229                 result[box].append(node)
230         return result
231                     
232     # a step for checking this stuff
233     def show_boxes (self):
234         for (box,nodes) in self.gather_hostBoxes().iteritems():
235             print box,":"," + ".join( [ node.name() for node in nodes ] )
236         return True
237
238     # make this a valid step
239     def kill_all_qemus(self):
240         # this is the brute force version, kill all qemus on that host box
241         for (box,nodes) in self.gather_hostBoxes().iteritems():
242             # pass the first nodename, as we don't push template-qemu on testboxes
243             nodedir=nodes[0].nodedir()
244             TestBox(box,self.options.buildname).kill_all_qemus(nodedir)
245         return True
246
247     # make this a valid step
248     def list_all_qemus(self):
249         for (box,nodes) in self.gather_hostBoxes().iteritems():
250             # this is the brute force version, kill all qemus on that host box
251             TestBox(box,self.options.buildname).list_all_qemus()
252         return True
253
254     # kill only the right qemus
255     def list_qemus(self):
256         for (box,nodes) in self.gather_hostBoxes().iteritems():
257             # the fine-grain version
258             for node in nodes:
259                 node.list_qemu()
260         return True
261
262     # kill only the right qemus
263     def kill_qemus(self):
264         for (box,nodes) in self.gather_hostBoxes().iteritems():
265             # the fine-grain version
266             for node in nodes:
267                 node.kill_qemu()
268         return True
269
270     def display (self):
271         utils.show_plc_spec (self.plc_spec)
272         return True
273
274     ### utility methods for handling the pool of IP addresses allocated to plcs
275     # Logic
276     # (*) running plcs are recorded in the file named ~/running-test-plcs
277     # (*) this file contains a line for each running plc, older first
278     # (*) each line contains the vserver name + the hostname of the (vserver) testbox where it sits
279     # (*) the free_tracker method performs a vserver stop on the oldest entry
280     # (*) the record_tracker method adds an entry at the bottom of the file
281     # (*) the cleanup_tracker method stops all known vservers and removes the tracker file
282
283     TRACKER_FILE=os.environ['HOME']+"/running-test-plcs"
284     # how many concurrent plcs are we keeping alive - adjust with the IP pool size
285     TRACKER_KEEP_VSERVERS = 12
286
287     def record_tracker (self):
288         try:
289             lines=file(TestPlc.TRACKER_FILE).readlines()
290         except:
291             lines=[]
292
293         this_line="%s %s\n"%(self.vservername,self.test_ssh.hostname)
294         for line in lines:
295             if line==this_line:
296                 print 'this vserver is already included in %s'%TestPlc.TRACKER_FILE
297                 return True
298         if self.options.dry_run:
299             print 'dry_run: record_tracker - skipping tracker update'
300             return True
301         tracker=file(TestPlc.TRACKER_FILE,"w")
302         for line in lines+[this_line]:
303             tracker.write(line)
304         tracker.close()
305         print "Recorded %s in running plcs on host %s"%(self.vservername,self.test_ssh.hostname)
306         return True
307
308     def free_tracker (self, keep_vservers=None):
309         if not keep_vservers: keep_vservers=TestPlc.TRACKER_KEEP_VSERVERS
310         try:
311             lines=file(TestPlc.TRACKER_FILE).readlines()
312         except:
313             print 'dry_run: free_tracker - skipping tracker update'
314             return True
315         how_many = len(lines) - keep_vservers
316         # nothing todo until we have more than keep_vservers in the tracker
317         if how_many <= 0:
318             print 'free_tracker : limit %d not reached'%keep_vservers
319             return True
320         to_stop = lines[:how_many]
321         to_keep = lines[how_many:]
322         for line in to_stop:
323             print '>%s<'%line
324             [vname,hostname]=line.split()
325             command=TestSsh(hostname).actual_command("vserver --silent %s stop"%vname)
326             utils.system(command)
327         if self.options.dry_run:
328             print 'dry_run: free_tracker would stop %d vservers'%len(to_stop)
329             for line in to_stop: print line,
330             print 'dry_run: free_tracker would keep %d vservers'%len(to_keep)
331             for line in to_keep: print line,
332             return True
333         print "Storing %d remaining vservers in %s"%(len(to_keep),TestPlc.TRACKER_FILE)
334         tracker=open(TestPlc.TRACKER_FILE,"w")
335         for line in to_keep:
336             tracker.write(line)
337         tracker.close()
338         return True
339
340     # this should/could stop only the ones in TRACKER_FILE if that turns out to be reliable
341     def cleanup_trackers (self):
342         try:
343             for line in TestPlc.TRACKER_FILE.readlines():
344                 [vname,hostname]=line.split()
345                 stop="vserver --silent %s stop"%vname
346                 command=TestSsh(hostname).actual_command(stop)
347                 utils.system(command)
348             clean_tracker = "rm -f %s"%TestPlc.TRACKER_FILE
349             utils.system(self.test_ssh.actual_command(clean_tracker))
350         except:
351             return True
352
353     # this should/could stop only the ones in TRACKER_FILE if that turns out to be reliable
354     def cleanup_all_trackers (self):
355         stop_all = "cd /vservers ; for i in * ; do vserver --silent $i stop ; done"
356         utils.system(self.test_ssh.actual_command(stop_all))
357         clean_tracker = "rm -f %s"%TestPlc.TRACKER_FILE
358         utils.system(self.test_ssh.actual_command(clean_tracker))
359         return True
360
361     def uninstall(self):
362         self.run_in_host("vserver --silent %s delete"%self.vservername)
363         return True
364
365     ### install
366     def install(self):
367         if self.is_local():
368             # a full path for the local calls
369             build_dir=os.path.dirname(sys.argv[0])
370             # sometimes this is empty - set to "." in such a case
371             if not build_dir: build_dir="."
372             build_dir += "/build"
373         else:
374             # use a standard name - will be relative to remote buildname
375             build_dir="build"
376         # run checkout in any case - would do an update if already exists
377         build_checkout = "svn checkout %s %s"%(self.options.build_url,build_dir)
378         if self.run_in_host(build_checkout) != 0:
379             return False
380         # the repo url is taken from arch-rpms-url 
381         # with the last step (i386.) removed
382         repo_url = self.options.arch_rpms_url
383         for level in [ 'arch' ]:
384             repo_url = os.path.dirname(repo_url)
385         # pass the vbuild-nightly options to vtest-init-vserver
386         test_env_options=""
387         test_env_options += " -p %s"%self.options.personality
388         test_env_options += " -d %s"%self.options.pldistro
389         test_env_options += " -f %s"%self.options.fcdistro
390         script="vtest-init-vserver.sh"
391         vserver_name = self.vservername
392         vserver_options="--netdev eth0 --interface %s"%self.vserverip
393         try:
394             vserver_hostname=socket.gethostbyaddr(self.vserverip)[0]
395             vserver_options += " --hostname %s"%vserver_hostname
396         except:
397             pass
398         create_vserver="%(build_dir)s/%(script)s %(test_env_options)s %(vserver_name)s %(repo_url)s -- %(vserver_options)s"%locals()
399         return self.run_in_host(create_vserver) == 0
400
401     ### install_rpm 
402     def install_rpm(self):
403         return self.run_in_guest("yum -y install myplc-native")==0 \
404             and self.run_in_guest("yum -y install noderepo-%s-%s"%(self.options.pldistro,self.options.arch))==0
405
406     ### 
407     def configure(self):
408         tmpname='%s.plc-config-tty'%(self.name())
409         fileconf=open(tmpname,'w')
410         for var in [ 'PLC_NAME',
411                      'PLC_ROOT_PASSWORD',
412                      'PLC_ROOT_USER',
413                      'PLC_MAIL_ENABLED',
414                      'PLC_MAIL_SUPPORT_ADDRESS',
415                      'PLC_DB_HOST',
416                      'PLC_API_HOST',
417                      'PLC_WWW_HOST',
418                      'PLC_BOOT_HOST',
419                      'PLC_NET_DNS1',
420                      'PLC_NET_DNS2']:
421             fileconf.write ('e %s\n%s\n'%(var,self.plc_spec[var]))
422         fileconf.write('w\n')
423         fileconf.write('q\n')
424         fileconf.close()
425         utils.system('cat %s'%tmpname)
426         self.run_in_guest_piped('cat %s'%tmpname,'plc-config-tty')
427         utils.system('rm %s'%tmpname)
428         return True
429
430     def start(self):
431         self.run_in_guest('service plc start')
432         return True
433
434     def stop(self):
435         self.run_in_guest('service plc stop')
436         return True
437         
438     def vs_start (self):
439         self.start_guest()
440         return True
441
442     # stores the keys from the config for further use
443     def store_keys(self):
444         for key_spec in self.plc_spec['keys']:
445                 TestKey(self,key_spec).store_key()
446         return True
447
448     def clean_keys(self):
449         utils.system("rm -rf %s/keys/"%os.path(sys.argv[0]))
450
451     # fetches the ssh keys in the plc's /etc/planetlab and stores them in keys/
452     # for later direct access to the nodes
453     def fetch_keys(self):
454         dir="./keys"
455         if not os.path.isdir(dir):
456             os.mkdir(dir)
457         vservername=self.vservername
458         overall=True
459         prefix = 'root_ssh_key'
460         for ext in [ 'pub', 'rsa' ] :
461             src="/vservers/%(vservername)s/etc/planetlab/%(prefix)s.%(ext)s"%locals()
462             dst="keys/%(vservername)s.%(ext)s"%locals()
463             if self.test_ssh.fetch(src,dst) != 0: overall=False
464         prefix = 'debug_ssh_key'
465         for ext in [ 'pub', 'rsa' ] :
466             src="/vservers/%(vservername)s/etc/planetlab/%(prefix)s.%(ext)s"%locals()
467             dst="keys/%(vservername)s-debug.%(ext)s"%locals()
468             if self.test_ssh.fetch(src,dst) != 0: overall=False
469         return overall
470
471     def sites (self):
472         return self.do_sites()
473     
474     def clean_sites (self):
475         return self.do_sites(action="delete")
476     
477     def do_sites (self,action="add"):
478         for site_spec in self.plc_spec['sites']:
479             test_site = TestSite (self,site_spec)
480             if (action != "add"):
481                 utils.header("Deleting site %s in %s"%(test_site.name(),self.name()))
482                 test_site.delete_site()
483                 # deleted with the site
484                 #test_site.delete_users()
485                 continue
486             else:
487                 utils.header("Creating site %s & users in %s"%(test_site.name(),self.name()))
488                 test_site.create_site()
489                 test_site.create_users()
490         return True
491
492     def clean_all_sites (self):
493         print 'auth_root',self.auth_root()
494         site_ids = [s['site_id'] for s in self.apiserver.GetSites(self.auth_root(), {}, ['site_id'])]
495         for site_id in site_ids:
496             print 'Deleting site_id',site_id
497             self.apiserver.DeleteSite(self.auth_root(),site_id)
498
499     def nodes (self):
500         return self.do_nodes()
501     def clean_nodes (self):
502         return self.do_nodes(action="delete")
503
504     def do_nodes (self,action="add"):
505         for site_spec in self.plc_spec['sites']:
506             test_site = TestSite (self,site_spec)
507             if action != "add":
508                 utils.header("Deleting nodes in site %s"%test_site.name())
509                 for node_spec in site_spec['nodes']:
510                     test_node=TestNode(self,test_site,node_spec)
511                     utils.header("Deleting %s"%test_node.name())
512                     test_node.delete_node()
513             else:
514                 utils.header("Creating nodes for site %s in %s"%(test_site.name(),self.name()))
515                 for node_spec in site_spec['nodes']:
516                     utils.pprint('Creating node %s'%node_spec,node_spec)
517                     test_node = TestNode (self,test_site,node_spec)
518                     test_node.create_node ()
519         return True
520
521     def nodegroups (self):
522         return self.do_nodegroups("add")
523     def clean_nodegroups (self):
524         return self.do_nodegroups("delete")
525
526     # create nodegroups if needed, and populate
527     def do_nodegroups (self, action="add"):
528         # 1st pass to scan contents
529         groups_dict = {}
530         for site_spec in self.plc_spec['sites']:
531             test_site = TestSite (self,site_spec)
532             for node_spec in site_spec['nodes']:
533                 test_node=TestNode (self,test_site,node_spec)
534                 if node_spec.has_key('nodegroups'):
535                     nodegroupnames=node_spec['nodegroups']
536                     if isinstance(nodegroupnames,StringTypes):
537                         nodegroupnames = [ nodegroupnames ]
538                     for nodegroupname in nodegroupnames:
539                         if not groups_dict.has_key(nodegroupname):
540                             groups_dict[nodegroupname]=[]
541                         groups_dict[nodegroupname].append(test_node.name())
542         auth=self.auth_root()
543         overall = True
544         for (nodegroupname,group_nodes) in groups_dict.iteritems():
545             if action == "add":
546                 print 'nodegroups:','dealing with nodegroup',nodegroupname,'on nodes',group_nodes
547                 # first, check if the nodetagtype is here
548                 tag_types = self.apiserver.GetTagTypes(auth,{'tagname':nodegroupname})
549                 if tag_types:
550                     tag_type_id = tag_types[0]['tag_type_id']
551                 else:
552                     tag_type_id = self.apiserver.AddTagType(auth,
553                                                             {'tagname':nodegroupname,
554                                                              'description': 'for nodegroup %s'%nodegroupname,
555                                                              'category':'test',
556                                                              'min_role_id':10})
557                 print 'located tag (type)',nodegroupname,'as',tag_type_id
558                 # create nodegroup
559                 nodegroups = self.apiserver.GetNodeGroups (auth, {'groupname':nodegroupname})
560                 if not nodegroups:
561                     self.apiserver.AddNodeGroup(auth, nodegroupname, tag_type_id, 'yes')
562                     print 'created nodegroup',nodegroupname,'from tagname',nodegroupname,'and value','yes'
563                 # set node tag on all nodes, value='yes'
564                 for nodename in group_nodes:
565                     try:
566                         self.apiserver.AddNodeTag(auth, nodename, nodegroupname, "yes")
567                     except:
568                         traceback.print_exc()
569                         print 'node',nodename,'seems to already have tag',nodegroupname
570                     # check anyway
571                     try:
572                         expect_yes = self.apiserver.GetNodeTags(auth,
573                                                                 {'hostname':nodename,
574                                                                  'tagname':nodegroupname},
575                                                                 ['tagvalue'])[0]['tagvalue']
576                         if expect_yes != "yes":
577                             print 'Mismatch node tag on node',nodename,'got',expect_yes
578                             overall=False
579                     except:
580                         if not self.options.dry_run:
581                             print 'Cannot find tag',nodegroupname,'on node',nodename
582                             overall = False
583             else:
584                 try:
585                     print 'cleaning nodegroup',nodegroupname
586                     self.apiserver.DeleteNodeGroup(auth,nodegroupname)
587                 except:
588                     traceback.print_exc()
589                     overall=False
590         return overall
591
592     def all_hostnames (self) :
593         hostnames = []
594         for site_spec in self.plc_spec['sites']:
595             hostnames += [ node_spec['node_fields']['hostname'] \
596                            for node_spec in site_spec['nodes'] ]
597         return hostnames
598
599     # gracetime : during the first <gracetime> minutes nothing gets printed
600     def nodes_check_boot_state (self, target_boot_state, minutes, gracetime,period=15):
601         if self.options.dry_run:
602             print 'dry_run'
603             return True
604         # compute timeout
605         timeout = datetime.datetime.now()+datetime.timedelta(minutes=minutes)
606         graceout = datetime.datetime.now()+datetime.timedelta(minutes=gracetime)
607         # the nodes that haven't checked yet - start with a full list and shrink over time
608         tocheck = self.all_hostnames()
609         utils.header("checking nodes %r"%tocheck)
610         # create a dict hostname -> status
611         status = dict ( [ (hostname,'undef') for hostname in tocheck ] )
612         while tocheck:
613             # get their status
614             tocheck_status=self.apiserver.GetNodes(self.auth_root(), tocheck, ['hostname','boot_state' ] )
615             # update status
616             for array in tocheck_status:
617                 hostname=array['hostname']
618                 boot_state=array['boot_state']
619                 if boot_state == target_boot_state:
620                     utils.header ("%s has reached the %s state"%(hostname,target_boot_state))
621                 else:
622                     # if it's a real node, never mind
623                     (site_spec,node_spec)=self.locate_hostname(hostname)
624                     if TestNode.is_real_model(node_spec['node_fields']['model']):
625                         utils.header("WARNING - Real node %s in %s - ignored"%(hostname,boot_state))
626                         # let's cheat
627                         boot_state = target_boot_state
628                     elif datetime.datetime.now() > graceout:
629                         utils.header ("%s still in '%s' state"%(hostname,boot_state))
630                         graceout=datetime.datetime.now()+datetime.timedelta(1)
631                 status[hostname] = boot_state
632             # refresh tocheck
633             tocheck = [ hostname for (hostname,boot_state) in status.iteritems() if boot_state != target_boot_state ]
634             if not tocheck:
635                 return True
636             if datetime.datetime.now() > timeout:
637                 for hostname in tocheck:
638                     utils.header("FAILURE due to %s in '%s' state"%(hostname,status[hostname]))
639                 return False
640             # otherwise, sleep for a while
641             time.sleep(period)
642         # only useful in empty plcs
643         return True
644
645     def nodes_booted(self):
646         return self.nodes_check_boot_state('boot',minutes=20,gracetime=15)
647
648     def check_nodes_ssh(self,debug,minutes,gracetime,period=20):
649         # compute timeout
650         timeout = datetime.datetime.now()+datetime.timedelta(minutes=minutes)
651         graceout = datetime.datetime.now()+datetime.timedelta(minutes=gracetime)
652         vservername=self.vservername
653         if debug: 
654             message="debug"
655             local_key = "keys/%(vservername)s-debug.rsa"%locals()
656         else: 
657             message="boot"
658             local_key = "keys/%(vservername)s.rsa"%locals()
659         tocheck = self.all_hostnames()
660         utils.header("checking ssh access (expected in %s mode) to nodes %r"%(message,tocheck))
661         while tocheck:
662             for hostname in tocheck:
663                 # try to run 'hostname' in the node
664                 command = TestSsh (hostname,key=local_key).actual_command("hostname;uname -a")
665                 # don't spam logs - show the command only after the grace period 
666                 if datetime.datetime.now() > graceout:
667                     success=utils.system(command)
668                 else:
669                     success=os.system(command)
670                 if success==0:
671                     utils.header('Successfully entered root@%s (%s)'%(hostname,message))
672                     # refresh tocheck
673                     tocheck.remove(hostname)
674                 else:
675                     # we will have tried real nodes once, in case they're up - but if not, just skip
676                     (site_spec,node_spec)=self.locate_hostname(hostname)
677                     if TestNode.is_real_model(node_spec['node_fields']['model']):
678                         utils.header ("WARNING : check ssh access into real node %s - skipped"%hostname)
679                         tocheck.remove(hostname)
680             if  not tocheck:
681                 return True
682             if datetime.datetime.now() > timeout:
683                 for hostname in tocheck:
684                     utils.header("FAILURE to ssh into %s"%hostname)
685                 return False
686             # otherwise, sleep for a while
687             time.sleep(period)
688         # only useful in empty plcs
689         return True
690         
691     def nodes_debug_ssh(self):
692         return self.check_nodes_ssh(debug=True,minutes=30,gracetime=10)
693     
694     def nodes_boot_ssh(self):
695         return self.check_nodes_ssh(debug=False,minutes=30,gracetime=10)
696     
697     @node_mapper
698     def init_node (self): pass
699     @node_mapper
700     def bootcd (self): pass
701     @node_mapper
702     def configure_qemu (self): pass
703     @node_mapper
704     def reinstall_node (self): pass
705     @node_mapper
706     def export_qemu (self): pass
707         
708     ### check sanity : invoke scripts from qaapi/qa/tests/{node,slice}
709     def check_sanity_node (self): 
710         return self.locate_first_node().check_sanity()
711     def check_sanity_sliver (self) : 
712         return self.locate_first_sliver().check_sanity()
713     
714     def check_sanity (self):
715         return self.check_sanity_node() and self.check_sanity_sliver()
716
717     ### initscripts
718     def do_check_initscripts(self):
719         overall = True
720         for slice_spec in self.plc_spec['slices']:
721             if not slice_spec.has_key('initscriptname'):
722                 continue
723             initscript=slice_spec['initscriptname']
724             for nodename in slice_spec['nodenames']:
725                 (site,node) = self.locate_node (nodename)
726                 # xxx - passing the wrong site - probably harmless
727                 test_site = TestSite (self,site)
728                 test_slice = TestSlice (self,test_site,slice_spec)
729                 test_node = TestNode (self,test_site,node)
730                 test_sliver = TestSliver (self, test_node, test_slice)
731                 if not test_sliver.check_initscript(initscript):
732                     overall = False
733         return overall
734             
735     def check_initscripts(self):
736             return self.do_check_initscripts()
737                     
738     def initscripts (self):
739         for initscript in self.plc_spec['initscripts']:
740             utils.pprint('Adding Initscript in plc %s'%self.plc_spec['name'],initscript)
741             self.apiserver.AddInitScript(self.auth_root(),initscript['initscript_fields'])
742         return True
743
744     def clean_initscripts (self):
745         for initscript in self.plc_spec['initscripts']:
746             initscript_name = initscript['initscript_fields']['name']
747             print('Attempting to delete %s in plc %s'%(initscript_name,self.plc_spec['name']))
748             try:
749                 self.apiserver.DeleteInitScript(self.auth_root(),initscript_name)
750                 print initscript_name,'deleted'
751             except:
752                 print 'deletion went wrong - probably did not exist'
753         return True
754
755     ### manage slices
756     def slices (self):
757         return self.do_slices()
758
759     def clean_slices (self):
760         return self.do_slices("delete")
761
762     def do_slices (self,  action="add"):
763         for slice in self.plc_spec['slices']:
764             site_spec = self.locate_site (slice['sitename'])
765             test_site = TestSite(self,site_spec)
766             test_slice=TestSlice(self,test_site,slice)
767             if action != "add":
768                 utils.header("Deleting slices in site %s"%test_site.name())
769                 test_slice.delete_slice()
770             else:    
771                 utils.pprint("Creating slice",slice)
772                 test_slice.create_slice()
773                 utils.header('Created Slice %s'%slice['slice_fields']['name'])
774         return True
775         
776     @slice_mapper_options
777     def check_slice(self): pass
778
779     @node_mapper
780     def clear_known_hosts (self): pass
781     
782     @node_mapper
783     def start_node (self) : pass
784
785     def check_tcp (self):
786         specs = self.plc_spec['tcp_test']
787         overall=True
788         for spec in specs:
789             port = spec['port']
790             # server side
791             s_test_sliver = self.locate_sliver_obj (spec['server_node'],spec['server_slice'])
792             if not s_test_sliver.run_tcp_server(port,timeout=10):
793                 overall=False
794                 break
795
796             # idem for the client side
797             c_test_sliver = self.locate_sliver_obj(spec['server_node'],spec['server_slice'])
798             if not c_test_sliver.run_tcp_client(s_test_sliver.test_node.name(),port):
799                 overall=False
800         return overall
801
802     def plcsh_stress_test (self):
803         # install the stress-test in the plc image
804         location = "/usr/share/plc_api/plcsh-stress-test.py"
805         remote="/vservers/%s/%s"%(self.vservername,location)
806         self.test_ssh.copy_abs("plcsh-stress-test.py",remote)
807         command = location
808         command += " -- --check"
809         if self.options.small_test:
810             command +=  " --tiny"
811         return ( self.run_in_guest(command) == 0)
812
813     def gather_logs (self):
814         # (1.a) get the plc's /var/log/ and store it locally in logs/myplc.var-log.<plcname>/*
815         # (1.b) get the plc's  /var/lib/pgsql/data/pg_log/ -> logs/myplc.pgsql-log.<plcname>/*
816         # (2) get all the nodes qemu log and store it as logs/node.qemu.<node>.log
817         # (3) get the nodes /var/log and store is as logs/node.var-log.<node>/*
818         # (4) as far as possible get the slice's /var/log as logs/sliver.var-log.<sliver>/*
819         # (1.a)
820         print "-------------------- TestPlc.gather_logs : PLC's /var/log"
821         self.gather_var_logs ()
822         # (1.b)
823         print "-------------------- TestPlc.gather_logs : PLC's /var/lib/psql/data/pg_log/"
824         self.gather_pgsql_logs ()
825         # (2) 
826         print "-------------------- TestPlc.gather_logs : nodes's QEMU logs"
827         for site_spec in self.plc_spec['sites']:
828             test_site = TestSite (self,site_spec)
829             for node_spec in site_spec['nodes']:
830                 test_node=TestNode(self,test_site,node_spec)
831                 test_node.gather_qemu_logs()
832         # (3)
833         print "-------------------- TestPlc.gather_logs : nodes's /var/log"
834         self.gather_nodes_var_logs()
835         # (4)
836         print "-------------------- TestPlc.gather_logs : sample sliver's /var/log"
837         self.gather_slivers_var_logs()
838         return True
839
840     def gather_slivers_var_logs(self):
841         for test_sliver in self.all_sliver_objs():
842             remote = test_sliver.tar_var_logs()
843             utils.system("mkdir -p logs/sliver.var-log.%s"%test_sliver.name())
844             command = remote + " | tar -C logs/sliver.var-log.%s -xf -"%test_sliver.name()
845             utils.system(command)
846         return True
847
848     def gather_var_logs (self):
849         utils.system("mkdir -p logs/myplc.var-log.%s"%self.name())
850         to_plc = self.actual_command_in_guest("tar -C /var/log/ -cf - .")        
851         command = to_plc + "| tar -C logs/myplc.var-log.%s -xf -"%self.name()
852         utils.system(command)
853         command = "chmod a+r,a+x logs/myplc.var-log.%s/httpd"%self.name()
854         utils.system(command)
855
856     def gather_pgsql_logs (self):
857         utils.system("mkdir -p logs/myplc.pgsql-log.%s"%self.name())
858         to_plc = self.actual_command_in_guest("tar -C /var/lib/pgsql/data/pg_log/ -cf - .")        
859         command = to_plc + "| tar -C logs/myplc.pgsql-log.%s -xf -"%self.name()
860         utils.system(command)
861
862     def gather_nodes_var_logs (self):
863         for site_spec in self.plc_spec['sites']:
864             test_site = TestSite (self,site_spec)
865             for node_spec in site_spec['nodes']:
866                 test_node=TestNode(self,test_site,node_spec)
867                 test_ssh = TestSsh (test_node.name(),key="/etc/planetlab/root_ssh_key.rsa")
868                 to_plc = self.actual_command_in_guest ( test_ssh.actual_command("tar -C /var/log -cf - ."))
869                 command = to_plc + "| tar -C logs/node.var-log.%s -xf -"%test_node.name()
870                 utils.system("mkdir -p logs/node.var-log.%s"%test_node.name())
871                 utils.system(command)
872
873
874     # returns the filename to use for sql dump/restore, using options.dbname if set
875     def dbfile (self, database):
876         # uses options.dbname if it is found
877         try:
878             name=self.options.dbname
879             if not isinstance(name,StringTypes):
880                 raise Exception
881         except:
882             t=datetime.datetime.now()
883             d=t.date()
884             name=str(d)
885         return "/root/%s-%s.sql"%(database,name)
886
887     def db_dump(self):
888         dump=self.dbfile("planetab4")
889         self.run_in_guest('pg_dump -U pgsqluser planetlab4 -f '+ dump)
890         utils.header('Dumped planetlab4 database in %s'%dump)
891         return True
892
893     def db_restore(self):
894         dump=self.dbfile("planetab4")
895         ##stop httpd service
896         self.run_in_guest('service httpd stop')
897         # xxx - need another wrapper
898         self.run_in_guest_piped('echo drop database planetlab4','psql --user=pgsqluser template1')
899         self.run_in_guest('createdb -U postgres --encoding=UNICODE --owner=pgsqluser planetlab4')
900         self.run_in_guest('psql -U pgsqluser planetlab4 -f '+dump)
901         ##starting httpd service
902         self.run_in_guest('service httpd start')
903
904         utils.header('Database restored from ' + dump)
905
906     @standby_generic 
907     def standby_1(): pass
908     @standby_generic 
909     def standby_2(): pass
910     @standby_generic 
911     def standby_3(): pass
912     @standby_generic 
913     def standby_4(): pass
914     @standby_generic 
915     def standby_5(): pass
916     @standby_generic 
917     def standby_6(): pass
918     @standby_generic 
919     def standby_7(): pass
920     @standby_generic 
921     def standby_8(): pass
922     @standby_generic 
923     def standby_9(): pass
924     @standby_generic 
925     def standby_10(): pass
926     @standby_generic 
927     def standby_11(): pass
928     @standby_generic 
929     def standby_12(): pass
930     @standby_generic 
931     def standby_13(): pass
932     @standby_generic 
933     def standby_14(): pass
934     @standby_generic 
935     def standby_15(): pass
936     @standby_generic 
937     def standby_16(): pass
938     @standby_generic 
939     def standby_17(): pass
940     @standby_generic 
941     def standby_18(): pass
942     @standby_generic 
943     def standby_19(): pass
944     @standby_generic 
945     def standby_20(): pass
946