cosmetic
[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     # silent_minutes : during the first <silent_minutes> minutes nothing gets printed
600     def nodes_check_boot_state (self, target_boot_state, timeout_minutes, silent_minutes,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=timeout_minutes)
606         graceout = datetime.datetime.now()+datetime.timedelta(minutes=silent_minutes)
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',timeout_minutes=20,silent_minutes=15)
647
648     def check_nodes_ssh(self,debug,timeout_minutes,silent_minutes,period=20):
649         # compute timeout
650         timeout = datetime.datetime.now()+datetime.timedelta(minutes=timeout_minutes)
651         graceout = datetime.datetime.now()+datetime.timedelta(minutes=silent_minutes)
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         utils.header("max timeout is %d minutes, silent for %d minutes"%(timeout_minutes,silent_minutes))
662         while tocheck:
663             for hostname in tocheck:
664                 # try to run 'hostname' in the node
665                 command = TestSsh (hostname,key=local_key).actual_command("hostname;uname -a")
666                 # don't spam logs - show the command only after the grace period 
667                 if datetime.datetime.now() > graceout:
668                     success=utils.system(command)
669                 else:
670                     success=os.system(command)
671                 if success==0:
672                     utils.header('Successfully entered root@%s (%s)'%(hostname,message))
673                     # refresh tocheck
674                     tocheck.remove(hostname)
675                 else:
676                     # we will have tried real nodes once, in case they're up - but if not, just skip
677                     (site_spec,node_spec)=self.locate_hostname(hostname)
678                     if TestNode.is_real_model(node_spec['node_fields']['model']):
679                         utils.header ("WARNING : check ssh access into real node %s - skipped"%hostname)
680                         tocheck.remove(hostname)
681             if  not tocheck:
682                 return True
683             if datetime.datetime.now() > timeout:
684                 for hostname in tocheck:
685                     utils.header("FAILURE to ssh into %s"%hostname)
686                 return False
687             # otherwise, sleep for a while
688             time.sleep(period)
689         # only useful in empty plcs
690         return True
691         
692     def nodes_debug_ssh(self):
693         return self.check_nodes_ssh(debug=True,timeout_minutes=30,silent_minutes=10)
694     
695     def nodes_boot_ssh(self):
696         return self.check_nodes_ssh(debug=False,timeout_minutes=30,silent_minutes=10)
697     
698     @node_mapper
699     def init_node (self): pass
700     @node_mapper
701     def bootcd (self): pass
702     @node_mapper
703     def configure_qemu (self): pass
704     @node_mapper
705     def reinstall_node (self): pass
706     @node_mapper
707     def export_qemu (self): pass
708         
709     ### check sanity : invoke scripts from qaapi/qa/tests/{node,slice}
710     def check_sanity_node (self): 
711         return self.locate_first_node().check_sanity()
712     def check_sanity_sliver (self) : 
713         return self.locate_first_sliver().check_sanity()
714     
715     def check_sanity (self):
716         return self.check_sanity_node() and self.check_sanity_sliver()
717
718     ### initscripts
719     def do_check_initscripts(self):
720         overall = True
721         for slice_spec in self.plc_spec['slices']:
722             if not slice_spec.has_key('initscriptname'):
723                 continue
724             initscript=slice_spec['initscriptname']
725             for nodename in slice_spec['nodenames']:
726                 (site,node) = self.locate_node (nodename)
727                 # xxx - passing the wrong site - probably harmless
728                 test_site = TestSite (self,site)
729                 test_slice = TestSlice (self,test_site,slice_spec)
730                 test_node = TestNode (self,test_site,node)
731                 test_sliver = TestSliver (self, test_node, test_slice)
732                 if not test_sliver.check_initscript(initscript):
733                     overall = False
734         return overall
735             
736     def check_initscripts(self):
737             return self.do_check_initscripts()
738                     
739     def initscripts (self):
740         for initscript in self.plc_spec['initscripts']:
741             utils.pprint('Adding Initscript in plc %s'%self.plc_spec['name'],initscript)
742             self.apiserver.AddInitScript(self.auth_root(),initscript['initscript_fields'])
743         return True
744
745     def clean_initscripts (self):
746         for initscript in self.plc_spec['initscripts']:
747             initscript_name = initscript['initscript_fields']['name']
748             print('Attempting to delete %s in plc %s'%(initscript_name,self.plc_spec['name']))
749             try:
750                 self.apiserver.DeleteInitScript(self.auth_root(),initscript_name)
751                 print initscript_name,'deleted'
752             except:
753                 print 'deletion went wrong - probably did not exist'
754         return True
755
756     ### manage slices
757     def slices (self):
758         return self.do_slices()
759
760     def clean_slices (self):
761         return self.do_slices("delete")
762
763     def do_slices (self,  action="add"):
764         for slice in self.plc_spec['slices']:
765             site_spec = self.locate_site (slice['sitename'])
766             test_site = TestSite(self,site_spec)
767             test_slice=TestSlice(self,test_site,slice)
768             if action != "add":
769                 utils.header("Deleting slices in site %s"%test_site.name())
770                 test_slice.delete_slice()
771             else:    
772                 utils.pprint("Creating slice",slice)
773                 test_slice.create_slice()
774                 utils.header('Created Slice %s'%slice['slice_fields']['name'])
775         return True
776         
777     @slice_mapper_options
778     def check_slice(self): pass
779
780     @node_mapper
781     def clear_known_hosts (self): pass
782     
783     @node_mapper
784     def start_node (self) : pass
785
786     def check_tcp (self):
787         specs = self.plc_spec['tcp_test']
788         overall=True
789         for spec in specs:
790             port = spec['port']
791             # server side
792             s_test_sliver = self.locate_sliver_obj (spec['server_node'],spec['server_slice'])
793             if not s_test_sliver.run_tcp_server(port,timeout=10):
794                 overall=False
795                 break
796
797             # idem for the client side
798             c_test_sliver = self.locate_sliver_obj(spec['server_node'],spec['server_slice'])
799             if not c_test_sliver.run_tcp_client(s_test_sliver.test_node.name(),port):
800                 overall=False
801         return overall
802
803     def plcsh_stress_test (self):
804         # install the stress-test in the plc image
805         location = "/usr/share/plc_api/plcsh-stress-test.py"
806         remote="/vservers/%s/%s"%(self.vservername,location)
807         self.test_ssh.copy_abs("plcsh-stress-test.py",remote)
808         command = location
809         command += " -- --check"
810         if self.options.small_test:
811             command +=  " --tiny"
812         return ( self.run_in_guest(command) == 0)
813
814     def gather_logs (self):
815         # (1.a) get the plc's /var/log/ and store it locally in logs/myplc.var-log.<plcname>/*
816         # (1.b) get the plc's  /var/lib/pgsql/data/pg_log/ -> logs/myplc.pgsql-log.<plcname>/*
817         # (2) get all the nodes qemu log and store it as logs/node.qemu.<node>.log
818         # (3) get the nodes /var/log and store is as logs/node.var-log.<node>/*
819         # (4) as far as possible get the slice's /var/log as logs/sliver.var-log.<sliver>/*
820         # (1.a)
821         print "-------------------- TestPlc.gather_logs : PLC's /var/log"
822         self.gather_var_logs ()
823         # (1.b)
824         print "-------------------- TestPlc.gather_logs : PLC's /var/lib/psql/data/pg_log/"
825         self.gather_pgsql_logs ()
826         # (2) 
827         print "-------------------- TestPlc.gather_logs : nodes's QEMU logs"
828         for site_spec in self.plc_spec['sites']:
829             test_site = TestSite (self,site_spec)
830             for node_spec in site_spec['nodes']:
831                 test_node=TestNode(self,test_site,node_spec)
832                 test_node.gather_qemu_logs()
833         # (3)
834         print "-------------------- TestPlc.gather_logs : nodes's /var/log"
835         self.gather_nodes_var_logs()
836         # (4)
837         print "-------------------- TestPlc.gather_logs : sample sliver's /var/log"
838         self.gather_slivers_var_logs()
839         return True
840
841     def gather_slivers_var_logs(self):
842         for test_sliver in self.all_sliver_objs():
843             remote = test_sliver.tar_var_logs()
844             utils.system("mkdir -p logs/sliver.var-log.%s"%test_sliver.name())
845             command = remote + " | tar -C logs/sliver.var-log.%s -xf -"%test_sliver.name()
846             utils.system(command)
847         return True
848
849     def gather_var_logs (self):
850         utils.system("mkdir -p logs/myplc.var-log.%s"%self.name())
851         to_plc = self.actual_command_in_guest("tar -C /var/log/ -cf - .")        
852         command = to_plc + "| tar -C logs/myplc.var-log.%s -xf -"%self.name()
853         utils.system(command)
854         command = "chmod a+r,a+x logs/myplc.var-log.%s/httpd"%self.name()
855         utils.system(command)
856
857     def gather_pgsql_logs (self):
858         utils.system("mkdir -p logs/myplc.pgsql-log.%s"%self.name())
859         to_plc = self.actual_command_in_guest("tar -C /var/lib/pgsql/data/pg_log/ -cf - .")        
860         command = to_plc + "| tar -C logs/myplc.pgsql-log.%s -xf -"%self.name()
861         utils.system(command)
862
863     def gather_nodes_var_logs (self):
864         for site_spec in self.plc_spec['sites']:
865             test_site = TestSite (self,site_spec)
866             for node_spec in site_spec['nodes']:
867                 test_node=TestNode(self,test_site,node_spec)
868                 test_ssh = TestSsh (test_node.name(),key="/etc/planetlab/root_ssh_key.rsa")
869                 to_plc = self.actual_command_in_guest ( test_ssh.actual_command("tar -C /var/log -cf - ."))
870                 command = to_plc + "| tar -C logs/node.var-log.%s -xf -"%test_node.name()
871                 utils.system("mkdir -p logs/node.var-log.%s"%test_node.name())
872                 utils.system(command)
873
874
875     # returns the filename to use for sql dump/restore, using options.dbname if set
876     def dbfile (self, database):
877         # uses options.dbname if it is found
878         try:
879             name=self.options.dbname
880             if not isinstance(name,StringTypes):
881                 raise Exception
882         except:
883             t=datetime.datetime.now()
884             d=t.date()
885             name=str(d)
886         return "/root/%s-%s.sql"%(database,name)
887
888     def db_dump(self):
889         dump=self.dbfile("planetab4")
890         self.run_in_guest('pg_dump -U pgsqluser planetlab4 -f '+ dump)
891         utils.header('Dumped planetlab4 database in %s'%dump)
892         return True
893
894     def db_restore(self):
895         dump=self.dbfile("planetab4")
896         ##stop httpd service
897         self.run_in_guest('service httpd stop')
898         # xxx - need another wrapper
899         self.run_in_guest_piped('echo drop database planetlab4','psql --user=pgsqluser template1')
900         self.run_in_guest('createdb -U postgres --encoding=UNICODE --owner=pgsqluser planetlab4')
901         self.run_in_guest('psql -U pgsqluser planetlab4 -f '+dump)
902         ##starting httpd service
903         self.run_in_guest('service httpd start')
904
905         utils.header('Database restored from ' + dump)
906
907     @standby_generic 
908     def standby_1(): pass
909     @standby_generic 
910     def standby_2(): pass
911     @standby_generic 
912     def standby_3(): pass
913     @standby_generic 
914     def standby_4(): pass
915     @standby_generic 
916     def standby_5(): pass
917     @standby_generic 
918     def standby_6(): pass
919     @standby_generic 
920     def standby_7(): pass
921     @standby_generic 
922     def standby_8(): pass
923     @standby_generic 
924     def standby_9(): pass
925     @standby_generic 
926     def standby_10(): pass
927     @standby_generic 
928     def standby_11(): pass
929     @standby_generic 
930     def standby_12(): pass
931     @standby_generic 
932     def standby_13(): pass
933     @standby_generic 
934     def standby_14(): pass
935     @standby_generic 
936     def standby_15(): pass
937     @standby_generic 
938     def standby_16(): pass
939     @standby_generic 
940     def standby_17(): pass
941     @standby_generic 
942     def standby_18(): pass
943     @standby_generic 
944     def standby_19(): pass
945     @standby_generic 
946     def standby_20(): pass
947