7 from types import StringTypes
10 from TestSite import TestSite
11 from TestNode import TestNode
12 from TestUser import TestUser
13 from TestKey import TestKey
14 from TestSlice import TestSlice
15 from TestSliver import TestSliver
16 from TestBox import TestBox
17 from TestSsh import TestSsh
18 from TestApiserver import TestApiserver
20 # step methods must take (self) and return a boolean (options is a member of the class)
22 def standby(minutes,dry_run):
23 utils.header('Entering StandBy for %d mn'%minutes)
27 time.sleep(60*minutes)
30 def standby_generic (func):
32 minutes=int(func.__name__.split("_")[1])
33 return standby(minutes,self.options.dry_run)
36 def node_mapper (method):
39 node_method = TestNode.__dict__[method.__name__]
40 for site_spec in self.plc_spec['sites']:
41 test_site = TestSite (self,site_spec)
42 for node_spec in site_spec['nodes']:
43 test_node = TestNode (self,test_site,node_spec)
44 if not node_method(test_node): overall=False
48 def slice_mapper_options (method):
51 slice_method = TestSlice.__dict__[method.__name__]
52 for slice_spec in self.plc_spec['slices']:
53 site_spec = self.locate_site (slice_spec['sitename'])
54 test_site = TestSite(self,site_spec)
55 test_slice=TestSlice(self,test_site,slice_spec)
56 if not slice_method(test_slice,self.options): overall=False
64 default_steps = ['uninstall','install','install_rpm',
65 'configure', 'start', SEP,
66 'store_keys', 'clear_known_hosts', 'initscripts', SEP,
67 'sites', 'nodes', 'slices', 'nodegroups', SEP,
68 'init_node','bootcd', 'configure_qemu', 'export_qemu',
69 'kill_all_qemus', 'reinstall_node','start_node', SEP,
70 'nodes_booted', 'nodes_ssh', 'check_slice',
71 'check_initscripts', 'check_tcp',SEP,
72 'force_gather_logs', 'force_kill_qemus', 'force_record_tracker','force_free_tracker' ]
73 other_steps = [ 'stop_all_vservers','fresh_install', 'cache_rpm', 'stop', SEP,
74 'clean_sites', 'clean_nodes', 'clean_slices', 'clean_keys', SEP,
75 'show_boxes', 'list_all_qemus', 'list_qemus', SEP,
76 'db_dump' , 'db_restore', ' cleanup_tracker',
77 'standby_1 through 20'
81 def printable_steps (list):
82 return " ".join(list).replace(" "+SEP+" "," \\\n")
84 def valid_step (step):
87 def __init__ (self,plc_spec,options):
88 self.plc_spec=plc_spec
90 self.test_ssh=TestSsh(self.plc_spec['hostname'],self.options.buildname)
92 self.vserverip=plc_spec['vserverip']
93 self.vservername=plc_spec['vservername']
94 self.url="https://%s:443/PLCAPI/"%plc_spec['vserverip']
97 raise Exception,'chroot-based myplc testing is deprecated'
98 self.apiserver=TestApiserver(self.url,options.dry_run)
101 name=self.plc_spec['name']
102 return "%s.%s"%(name,self.vservername)
105 return self.plc_spec['hostname']
108 return self.test_ssh.is_local()
110 # define the API methods on this object through xmlrpc
111 # would help, but not strictly necessary
115 def actual_command_in_guest (self,command):
116 return self.test_ssh.actual_command(self.host_to_guest(command))
118 def run_in_guest (self,command):
119 return utils.system(self.actual_command_in_guest(command))
121 def run_in_host (self,command):
122 return self.test_ssh.run_in_buildname(command)
124 #command gets run in the vserver
125 def host_to_guest(self,command):
126 return "vserver %s exec %s"%(self.vservername,command)
129 def run_in_guest_piped (self,local,remote):
130 return utils.system(local+" | "+self.test_ssh.actual_command(self.host_to_guest(remote),keep_stdin=True))
132 def auth_root (self):
133 return {'Username':self.plc_spec['PLC_ROOT_USER'],
134 'AuthMethod':'password',
135 'AuthString':self.plc_spec['PLC_ROOT_PASSWORD'],
136 'Role' : self.plc_spec['role']
138 def locate_site (self,sitename):
139 for site in self.plc_spec['sites']:
140 if site['site_fields']['name'] == sitename:
142 if site['site_fields']['login_base'] == sitename:
144 raise Exception,"Cannot locate site %s"%sitename
146 def locate_node (self,nodename):
147 for site in self.plc_spec['sites']:
148 for node in site['nodes']:
149 if node['name'] == nodename:
151 raise Exception,"Cannot locate node %s"%nodename
153 def locate_hostname (self,hostname):
154 for site in self.plc_spec['sites']:
155 for node in site['nodes']:
156 if node['node_fields']['hostname'] == hostname:
158 raise Exception,"Cannot locate hostname %s"%hostname
160 def locate_key (self,keyname):
161 for key in self.plc_spec['keys']:
162 if key['name'] == keyname:
164 raise Exception,"Cannot locate key %s"%keyname
166 def locate_slice (self, slicename):
167 for slice in self.plc_spec['slices']:
168 if slice['slice_fields']['name'] == slicename:
170 raise Exception,"Cannot locate slice %s"%slicename
172 # all different hostboxes used in this plc
173 def gather_hostBoxes(self):
174 # maps on sites and nodes, return [ (host_box,test_node) ]
176 for site_spec in self.plc_spec['sites']:
177 test_site = TestSite (self,site_spec)
178 for node_spec in site_spec['nodes']:
179 test_node = TestNode (self, test_site, node_spec)
180 if not test_node.is_real():
181 tuples.append( (test_node.host_box(),test_node) )
182 # transform into a dict { 'host_box' -> [ test_node .. ] }
184 for (box,node) in tuples:
185 if not result.has_key(box):
188 result[box].append(node)
191 # a step for checking this stuff
192 def show_boxes (self):
193 for (box,nodes) in self.gather_hostBoxes().iteritems():
194 print box,":"," + ".join( [ node.name() for node in nodes ] )
197 # make this a valid step
198 def kill_all_qemus(self):
199 # this is the brute force version, kill all qemus on that host box
200 for (box,nodes) in self.gather_hostBoxes().iteritems():
201 # pass the first nodename, as we don't push template-qemu on testboxes
202 nodedir=nodes[0].nodedir()
203 TestBox(box,self.options.buildname).kill_all_qemus(nodedir)
206 # make this a valid step
207 def list_all_qemus(self):
208 for (box,nodes) in self.gather_hostBoxes().iteritems():
209 # this is the brute force version, kill all qemus on that host box
210 TestBox(box,self.options.buildname).list_all_qemus()
213 # kill only the right qemus
214 def list_qemus(self):
215 for (box,nodes) in self.gather_hostBoxes().iteritems():
216 # the fine-grain version
221 # kill only the right qemus
222 def kill_qemus(self):
223 for (box,nodes) in self.gather_hostBoxes().iteritems():
224 # the fine-grain version
230 ### utility methods for handling the pool of IP addresses allocated to plcs
232 # (*) running plcs are recorded in the file named ~/running-test-plcs
233 # (*) this file contains a line for each running plc, older first
234 # (*) each line contains the vserver name + the hostname of the (vserver) testbox where it sits
235 # (*) the free_tracker method performs a vserver stop on the oldest entry
236 # (*) the record_tracker method adds an entry at the bottom of the file
237 # (*) the cleanup_tracker method stops all known vservers and removes the tracker file
239 TRACKER_FILE="~/running-test-plcs"
241 def record_tracker (self):
242 command="echo %s %s >> %s"%(self.vservername,self.test_ssh.hostname,TestPlc.TRACKER_FILE)
243 (code,output) = utils.output_of (self.test_ssh.actual_command(command))
245 print "WARNING : COULD NOT record_tracker %s as a running plc on %s"%(self.vservername,self.test_ssh.hostname)
247 print "Recorded %s in running plcs on host %s"%(self.vservername,self.test_ssh.hostname)
250 def free_tracker (self):
251 command="head -1 %s"%TestPlc.TRACKER_FILE
252 (code,line) = utils.output_of(self.test_ssh.actual_command(command))
254 print "No entry found in %s on %s"%(TestPlc.TRACKER_FILE,self.test_ssh.hostname)
257 [vserver_to_stop,hostname] = line.split()
259 print "WARNING: free_tracker: Could not parse %s - skipped"%TestPlc.TRACKER_FILE
261 stop_command = "vserver --silent %s stop"%vserver_to_stop
262 utils.system(self.test_ssh.actual_command(stop_command))
263 x=TestPlc.TRACKER_FILE
264 flush_command = "tail --lines=+2 %s > %s.tmp ; mv %s.tmp %s"%(x,x,x,x)
265 utils.system(self.test_ssh.actual_command(flush_command))
268 # this should/could stop only the ones in TRACKER_FILE if that turns out to be reliable
269 def cleanup_tracker (self):
270 stop_all = "cd /vservers ; for i in * ; do vserver --silent $i stop ; done"
271 utils.system(self.test_ssh.actual_command(stop_all))
272 clean_tracker = "rm -f %s"%TestPlc.TRACKER_FILE
273 utils.system(self.test_ssh.actual_command(clean_tracker))
276 self.run_in_host("vserver --silent %s delete"%self.vservername)
281 # we need build dir for vtest-init-vserver
283 # a full path for the local calls
284 build_dir=os.path.dirname(sys.argv[0])+"/build"
286 # use a standard name - will be relative to remote buildname
288 # run checkout in any case - would do an update if already exists
289 build_checkout = "svn checkout %s %s"%(self.options.build_url,build_dir)
290 if self.run_in_host(build_checkout) != 0:
292 # the repo url is taken from arch-rpms-url
293 # with the last step (i386.) removed
294 repo_url = self.options.arch_rpms_url
295 for level in [ 'arch' ]:
296 repo_url = os.path.dirname(repo_url)
297 if self.options.arch == "i386":
298 personality="-p linux32"
300 personality="-p linux64"
301 create_vserver="%s/vtest-init-vserver.sh %s %s %s -- --interface eth0:%s"%\
302 (build_dir,personality,self.vservername,repo_url,self.vserverip)
303 return self.run_in_host(create_vserver) == 0
306 def install_rpm(self):
307 return self.run_in_guest("yum -y install myplc-native")==0
311 tmpname='%s.plc-config-tty'%(self.name())
312 fileconf=open(tmpname,'w')
313 for var in [ 'PLC_NAME',
317 'PLC_MAIL_SUPPORT_ADDRESS',
324 fileconf.write ('e %s\n%s\n'%(var,self.plc_spec[var]))
325 fileconf.write('w\n')
326 fileconf.write('q\n')
328 utils.system('cat %s'%tmpname)
329 self.run_in_guest_piped('cat %s'%tmpname,'plc-config-tty')
330 utils.system('rm %s'%tmpname)
334 self.run_in_guest('service plc start')
338 self.run_in_guest('service plc stop')
341 # could use a TestKey class
342 def store_keys(self):
343 for key_spec in self.plc_spec['keys']:
344 TestKey(self,key_spec).store_key()
347 def clean_keys(self):
348 utils.system("rm -rf %s/keys/"%os.path(sys.argv[0]))
351 return self.do_sites()
353 def clean_sites (self):
354 return self.do_sites(action="delete")
356 def do_sites (self,action="add"):
357 for site_spec in self.plc_spec['sites']:
358 test_site = TestSite (self,site_spec)
359 if (action != "add"):
360 utils.header("Deleting site %s in %s"%(test_site.name(),self.name()))
361 test_site.delete_site()
362 # deleted with the site
363 #test_site.delete_users()
366 utils.header("Creating site %s & users in %s"%(test_site.name(),self.name()))
367 test_site.create_site()
368 test_site.create_users()
372 return self.do_nodes()
373 def clean_nodes (self):
374 return self.do_nodes(action="delete")
376 def do_nodes (self,action="add"):
377 for site_spec in self.plc_spec['sites']:
378 test_site = TestSite (self,site_spec)
380 utils.header("Deleting nodes in site %s"%test_site.name())
381 for node_spec in site_spec['nodes']:
382 test_node=TestNode(self,test_site,node_spec)
383 utils.header("Deleting %s"%test_node.name())
384 test_node.delete_node()
386 utils.header("Creating nodes for site %s in %s"%(test_site.name(),self.name()))
387 for node_spec in site_spec['nodes']:
388 utils.pprint('Creating node %s'%node_spec,node_spec)
389 test_node = TestNode (self,test_site,node_spec)
390 test_node.create_node ()
393 # create nodegroups if needed, and populate
394 # no need for a clean_nodegroups if we are careful enough
395 def nodegroups (self):
396 # 1st pass to scan contents
398 for site_spec in self.plc_spec['sites']:
399 test_site = TestSite (self,site_spec)
400 for node_spec in site_spec['nodes']:
401 test_node=TestNode (self,test_site,node_spec)
402 if node_spec.has_key('nodegroups'):
403 nodegroupnames=node_spec['nodegroups']
404 if isinstance(nodegroupnames,StringTypes):
405 nodegroupnames = [ nodegroupnames ]
406 for nodegroupname in nodegroupnames:
407 if not groups_dict.has_key(nodegroupname):
408 groups_dict[nodegroupname]=[]
409 groups_dict[nodegroupname].append(test_node.name())
410 auth=self.auth_root()
411 for (nodegroupname,group_nodes) in groups_dict.iteritems():
413 self.apiserver.GetNodeGroups(auth,{'name':nodegroupname})[0]
415 self.apiserver.AddNodeGroup(auth,{'name':nodegroupname})
416 for node in group_nodes:
417 self.apiserver.AddNodeToNodeGroup(auth,node,nodegroupname)
420 def all_hostnames (self) :
422 for site_spec in self.plc_spec['sites']:
423 hostnames += [ node_spec['node_fields']['hostname'] \
424 for node_spec in site_spec['nodes'] ]
427 # gracetime : during the first <gracetime> minutes nothing gets printed
428 def do_nodes_booted (self, minutes, gracetime,period=30):
429 if self.options.dry_run:
433 timeout = datetime.datetime.now()+datetime.timedelta(minutes=minutes)
434 graceout = datetime.datetime.now()+datetime.timedelta(minutes=gracetime)
435 # the nodes that haven't checked yet - start with a full list and shrink over time
436 tocheck = self.all_hostnames()
437 utils.header("checking nodes %r"%tocheck)
438 # create a dict hostname -> status
439 status = dict ( [ (hostname,'undef') for hostname in tocheck ] )
442 tocheck_status=self.apiserver.GetNodes(self.auth_root(), tocheck, ['hostname','boot_state' ] )
444 for array in tocheck_status:
445 hostname=array['hostname']
446 boot_state=array['boot_state']
447 if boot_state == 'boot':
448 utils.header ("%s has reached the 'boot' state"%hostname)
450 # if it's a real node, never mind
451 (site_spec,node_spec)=self.locate_hostname(hostname)
452 if TestNode.is_real_model(node_spec['node_fields']['model']):
453 utils.header("WARNING - Real node %s in %s - ignored"%(hostname,boot_state))
456 elif datetime.datetime.now() > graceout:
457 utils.header ("%s still in '%s' state"%(hostname,boot_state))
458 graceout=datetime.datetime.now()+datetime.timedelta(1)
459 status[hostname] = boot_state
461 tocheck = [ hostname for (hostname,boot_state) in status.iteritems() if boot_state != 'boot' ]
464 if datetime.datetime.now() > timeout:
465 for hostname in tocheck:
466 utils.header("FAILURE due to %s in '%s' state"%(hostname,status[hostname]))
468 # otherwise, sleep for a while
470 # only useful in empty plcs
473 def nodes_booted(self):
474 return self.do_nodes_booted(minutes=20,gracetime=15)
476 def do_nodes_ssh(self,minutes,gracetime,period=30):
478 timeout = datetime.datetime.now()+datetime.timedelta(minutes=minutes)
479 graceout = datetime.datetime.now()+datetime.timedelta(minutes=gracetime)
480 tocheck = self.all_hostnames()
481 # self.scan_publicKeys(tocheck)
482 utils.header("checking Connectivity on nodes %r"%tocheck)
484 for hostname in tocheck:
485 # try to ssh in nodes
486 node_test_ssh = TestSsh (hostname,key="/etc/planetlab/root_ssh_key.rsa")
487 success=self.run_in_guest(node_test_ssh.actual_command("hostname"))==0
489 utils.header('The node %s is sshable -->'%hostname)
491 tocheck.remove(hostname)
493 # we will have tried real nodes once, in case they're up - but if not, just skip
494 (site_spec,node_spec)=self.locate_hostname(hostname)
495 if TestNode.is_real_model(node_spec['node_fields']['model']):
496 utils.header ("WARNING : check ssh access into real node %s - skipped"%hostname)
497 tocheck.remove(hostname)
498 elif datetime.datetime.now() > graceout:
499 utils.header("Could not ssh-enter root context on %s"%hostname)
502 if datetime.datetime.now() > timeout:
503 for hostname in tocheck:
504 utils.header("FAILURE to ssh into %s"%hostname)
506 # otherwise, sleep for a while
508 # only useful in empty plcs
512 return self.do_nodes_ssh(minutes=6,gracetime=4)
515 def init_node (self): pass
517 def bootcd (self): pass
519 def configure_qemu (self): pass
521 def reinstall_node (self): pass
523 def export_qemu (self): pass
525 def do_check_initscripts(self):
527 for slice_spec in self.plc_spec['slices']:
528 if not slice_spec.has_key('initscriptname'):
530 initscript=slice_spec['initscriptname']
531 for nodename in slice_spec['nodenames']:
532 (site,node) = self.locate_node (nodename)
533 # xxx - passing the wrong site - probably harmless
534 test_site = TestSite (self,site)
535 test_slice = TestSlice (self,test_site,slice_spec)
536 test_node = TestNode (self,test_site,node)
537 test_sliver = TestSliver (self, test_node, test_slice)
538 if not test_sliver.check_initscript(initscript):
542 def check_initscripts(self):
543 return self.do_check_initscripts()
545 def initscripts (self):
546 for initscript in self.plc_spec['initscripts']:
547 utils.pprint('Adding Initscript in plc %s'%self.plc_spec['name'],initscript)
548 self.apiserver.AddInitScript(self.auth_root(),initscript['initscript_fields'])
552 return self.do_slices()
554 def clean_slices (self):
555 return self.do_slices("delete")
557 def do_slices (self, action="add"):
558 for slice in self.plc_spec['slices']:
559 site_spec = self.locate_site (slice['sitename'])
560 test_site = TestSite(self,site_spec)
561 test_slice=TestSlice(self,test_site,slice)
563 utils.header("Deleting slices in site %s"%test_site.name())
564 test_slice.delete_slice()
566 utils.pprint("Creating slice",slice)
567 test_slice.create_slice()
568 utils.header('Created Slice %s'%slice['slice_fields']['name'])
571 @slice_mapper_options
572 def check_slice(self): pass
575 def clear_known_hosts (self): pass
578 def start_node (self) : pass
580 def all_sliver_objs (self):
582 for slice_spec in self.plc_spec['slices']:
583 slicename = slice_spec['slice_fields']['name']
584 for nodename in slice_spec['nodenames']:
585 result.append(self.locate_sliver_obj (nodename,slicename))
588 def locate_sliver_obj (self,nodename,slicename):
589 (site,node) = self.locate_node(nodename)
590 slice = self.locate_slice (slicename)
592 test_site = TestSite (self, site)
593 test_node = TestNode (self, test_site,node)
594 # xxx the slice site is assumed to be the node site - mhh - probably harmless
595 test_slice = TestSlice (self, test_site, slice)
596 return TestSliver (self, test_node, test_slice)
598 def check_tcp (self):
599 specs = self.plc_spec['tcp_test']
604 s_test_sliver = self.locate_sliver_obj (spec['server_node'],spec['server_slice'])
605 if not s_test_sliver.run_tcp_server(port,timeout=10):
609 # idem for the client side
610 c_test_sliver = self.locate_sliver_obj(spec['server_node'],spec['server_slice'])
611 if not c_test_sliver.run_tcp_client(s_test_sliver.test_node.name(),port):
616 def gather_logs (self):
617 # (1) get the plc's /var/log and store it locally in logs/myplc.var-log.<plcname>/*
618 # (2) get all the nodes qemu log and store it as logs/node.qemu.<node>.log
619 # (3) get the nodes /var/log and store is as logs/node.var-log.<node>/*
620 # (4) as far as possible get the slice's /var/log as logs/sliver.var-log.<sliver>/*
622 print "-------------------- TestPlc.gather_logs : PLC's /var/log"
623 self.gather_var_logs ()
625 print "-------------------- TestPlc.gather_logs : nodes's QEMU logs"
626 for site_spec in self.plc_spec['sites']:
627 test_site = TestSite (self,site_spec)
628 for node_spec in site_spec['nodes']:
629 test_node=TestNode(self,test_site,node_spec)
630 test_node.gather_qemu_logs()
632 print "-------------------- TestPlc.gather_logs : nodes's /var/log"
633 self.gather_nodes_var_logs()
635 print "-------------------- TestPlc.gather_logs : sample sliver's /var/log"
636 self.gather_slivers_var_logs()
639 def gather_slivers_var_logs(self):
640 for test_sliver in self.all_sliver_objs():
641 remote = test_sliver.tar_var_logs()
642 utils.system("mkdir -p logs/sliver.var-log.%s"%test_sliver.name())
643 command = remote + " | tar -C logs/sliver.var-log.%s -xf -"%test_sliver.name()
644 utils.system(command)
647 def gather_var_logs (self):
648 to_plc = self.actual_command_in_guest("tar -C /var/log/ -cf - .")
649 command = to_plc + "| tar -C logs/myplc.var-log.%s -xf -"%self.name()
650 utils.system("mkdir -p logs/myplc.var-log.%s"%self.name())
651 utils.system(command)
653 def gather_nodes_var_logs (self):
654 for site_spec in self.plc_spec['sites']:
655 test_site = TestSite (self,site_spec)
656 for node_spec in site_spec['nodes']:
657 test_node=TestNode(self,test_site,node_spec)
658 test_ssh = TestSsh (test_node.name(),key="/etc/planetlab/root_ssh_key.rsa")
659 to_plc = self.actual_command_in_guest ( test_ssh.actual_command("tar -C /var/log -cf - ."))
660 command = to_plc + "| tar -C logs/node.var-log.%s -xf -"%test_node.name()
661 utils.system("mkdir -p logs/node.var-log.%s"%test_node.name())
662 utils.system(command)
665 # returns the filename to use for sql dump/restore, using options.dbname if set
666 def dbfile (self, database):
667 # uses options.dbname if it is found
669 name=self.options.dbname
670 if not isinstance(name,StringTypes):
673 t=datetime.datetime.now()
676 return "/root/%s-%s.sql"%(database,name)
679 dump=self.dbfile("planetab4")
680 self.run_in_guest('pg_dump -U pgsqluser planetlab4 -f '+ dump)
681 utils.header('Dumped planetlab4 database in %s'%dump)
684 def db_restore(self):
685 dump=self.dbfile("planetab4")
687 self.run_in_guest('service httpd stop')
688 # xxx - need another wrapper
689 self.run_in_guest_piped('echo drop database planetlab4','psql --user=pgsqluser template1')
690 self.run_in_guest('createdb -U postgres --encoding=UNICODE --owner=pgsqluser planetlab4')
691 self.run_in_guest('psql -U pgsqluser planetlab4 -f '+dump)
692 ##starting httpd service
693 self.run_in_guest('service httpd start')
695 utils.header('Database restored from ' + dump)
698 def standby_1(): pass
700 def standby_2(): pass
702 def standby_3(): pass
704 def standby_4(): pass
706 def standby_5(): pass
708 def standby_6(): pass
710 def standby_7(): pass
712 def standby_8(): pass
714 def standby_9(): pass
716 def standby_10(): pass
718 def standby_11(): pass
720 def standby_12(): pass
722 def standby_13(): pass
724 def standby_14(): pass
726 def standby_15(): pass
728 def standby_16(): pass
730 def standby_17(): pass
732 def standby_18(): pass
734 def standby_19(): pass
736 def standby_20(): pass