8 from types import StringTypes
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
21 # step methods must take (self) and return a boolean (options is a member of the class)
23 def standby(minutes,dry_run):
24 utils.header('Entering StandBy for %d mn'%minutes)
28 time.sleep(60*minutes)
31 def standby_generic (func):
33 minutes=int(func.__name__.split("_")[1])
34 return standby(minutes,self.options.dry_run)
37 def node_mapper (method):
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
49 def slice_mapper_options (method):
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
65 default_steps = ['uninstall','install','install_rpm',
66 'configure', 'start', SEP,
67 'store_keys', 'clear_known_hosts', 'initscripts', SEP,
68 'sites', 'nodes', 'slices', 'nodegroups', SEP,
69 'init_node','bootcd', 'configure_qemu',
70 'kill_all_qemus', 'reinstall_node','start_node', SEP,
72 'nodes_booted', 'nodes_ssh', 'check_slice',
73 'check_initscripts', 'check_tcp',SEP,
74 'force_gather_logs', 'force_kill_qemus', ]
75 other_steps = [ 'stop_all_vservers','fresh_install', 'cache_rpm', 'stop', SEP,
76 'clean_sites', 'clean_nodes', 'clean_slices', 'clean_keys', SEP,
77 'show_boxes', 'list_all_qemus', 'list_qemus', SEP,
78 'db_dump' , 'db_restore',
79 'standby_1 through 20'
83 def printable_steps (list):
84 return " ".join(list).replace(" "+SEP+" ","\n")
86 def __init__ (self,plc_spec,options):
87 self.plc_spec=plc_spec
89 self.test_ssh=TestSsh(self.plc_spec['hostname'],self.options.buildname)
91 self.vserverip=plc_spec['vserverip']
92 self.vservername=plc_spec['vservername']
93 self.url="https://%s:443/PLCAPI/"%plc_spec['vserverip']
97 self.url="https://%s:443/PLCAPI/"%plc_spec['hostname']
98 # utils.header('Using API url %s'%self.url)
99 self.apiserver=TestApiserver(self.url,options.dry_run)
102 name=self.plc_spec['name']
104 return name+".vserver.%s"%self.vservername
106 return name+".chroot"
109 return self.plc_spec['hostname']
112 return self.test_ssh.is_local()
114 # define the API methods on this object through xmlrpc
115 # would help, but not strictly necessary
119 def actual_command_in_guest (self,command):
120 return self.test_ssh.actual_command(self.host_to_guest(command))
122 def run_in_guest (self,command):
123 return utils.system(self.actual_command_in_guest(command))
125 def run_in_host (self,command):
126 return self.test_ssh.run_in_buildname(command)
128 #command gets run in the chroot/vserver
129 def host_to_guest(self,command):
131 return "vserver %s exec %s"%(self.vservername,command)
133 return "chroot /plc/root %s"%TestSsh.backslash_shell_specials(command)
135 # copy a file to the myplc root image - pass in_data=True if the file must go in /plc/data
136 def copy_in_guest (self, localfile, remotefile, in_data=False):
138 chroot_dest="/plc/data"
140 chroot_dest="/plc/root"
143 utils.system("cp %s %s/%s"%(localfile,chroot_dest,remotefile))
145 utils.system("cp %s /vservers/%s/%s"%(localfile,self.vservername,remotefile))
148 utils.system("scp %s %s:%s/%s"%(localfile,self.hostname(),chroot_dest,remotefile))
150 utils.system("scp %s %s@/vservers/%s/%s"%(localfile,self.hostname(),self.vservername,remotefile))
154 def run_in_guest_piped (self,local,remote):
155 return utils.system(local+" | "+self.test_ssh.actual_command(self.host_to_guest(remote)))
157 def auth_root (self):
158 return {'Username':self.plc_spec['PLC_ROOT_USER'],
159 'AuthMethod':'password',
160 'AuthString':self.plc_spec['PLC_ROOT_PASSWORD'],
161 'Role' : self.plc_spec['role']
163 def locate_site (self,sitename):
164 for site in self.plc_spec['sites']:
165 if site['site_fields']['name'] == sitename:
167 if site['site_fields']['login_base'] == sitename:
169 raise Exception,"Cannot locate site %s"%sitename
171 def locate_node (self,nodename):
172 for site in self.plc_spec['sites']:
173 for node in site['nodes']:
174 if node['name'] == nodename:
176 raise Exception,"Cannot locate node %s"%nodename
178 def locate_hostname (self,hostname):
179 for site in self.plc_spec['sites']:
180 for node in site['nodes']:
181 if node['node_fields']['hostname'] == hostname:
183 raise Exception,"Cannot locate hostname %s"%hostname
185 def locate_key (self,keyname):
186 for key in self.plc_spec['keys']:
187 if key['name'] == keyname:
189 raise Exception,"Cannot locate key %s"%keyname
191 def locate_slice (self, slicename):
192 for slice in self.plc_spec['slices']:
193 if slice['slice_fields']['name'] == slicename:
195 raise Exception,"Cannot locate slice %s"%slicename
197 # all different hostboxes used in this plc
198 def gather_hostBoxes(self):
199 # maps on sites and nodes, return [ (host_box,test_node) ]
201 for site_spec in self.plc_spec['sites']:
202 test_site = TestSite (self,site_spec)
203 for node_spec in site_spec['nodes']:
204 test_node = TestNode (self, test_site, node_spec)
205 if not test_node.is_real():
206 tuples.append( (test_node.host_box(),test_node) )
207 # transform into a dict { 'host_box' -> [ test_node .. ] }
209 for (box,node) in tuples:
210 if not result.has_key(box):
213 result[box].append(node)
216 # a step for checking this stuff
217 def show_boxes (self):
218 for (box,nodes) in self.gather_hostBoxes().iteritems():
219 print box,":"," + ".join( [ node.name() for node in nodes ] )
222 # make this a valid step
223 def kill_all_qemus(self):
224 # this is the brute force version, kill all qemus on that host box
225 for (box,nodes) in self.gather_hostBoxes().iteritems():
226 # pass the first nodename, as we don't push template-qemu on testboxes
227 nodename=nodes[0].name()
228 TestBox(box,self.options.buildname).kill_all_qemus(nodename)
231 # make this a valid step
232 def list_all_qemus(self):
233 for (box,nodes) in self.gather_hostBoxes().iteritems():
234 # this is the brute force version, kill all qemus on that host box
235 TestBox(box,self.options.buildname).list_all_qemus()
238 # kill only the right qemus
239 def list_qemus(self):
240 for (box,nodes) in self.gather_hostBoxes().iteritems():
241 # the fine-grain version
246 # kill only the right qemus
247 def kill_qemus(self):
248 for (box,nodes) in self.gather_hostBoxes().iteritems():
249 # the fine-grain version
254 #################### step methods
257 def uninstall_chroot(self):
258 self.run_in_host('service plc safestop')
259 #####detecting the last myplc version installed and remove it
260 self.run_in_host('rpm -e myplc')
261 ##### Clean up the /plc directory
262 self.run_in_host('rm -rf /plc/data')
263 ##### stop any running vservers
264 self.run_in_host('for vserver in $(ls -d /vservers/* | sed -e s,/vservers/,,) ; do case $vserver in vtest*) echo Shutting down vserver $vserver ; vserver $vserver stop ;; esac ; done')
267 def uninstall_vserver(self):
268 self.run_in_host("vserver --silent %s delete"%self.vservername)
272 # if there's a chroot-based myplc running, and then a native-based myplc is being deployed
273 # it sounds safer to have the former uninstalled too
274 # now the vserver method cannot be invoked for chroot instances as vservername is required
276 self.uninstall_vserver()
277 self.uninstall_chroot()
279 self.uninstall_chroot()
283 def install_chroot(self):
287 def install_vserver(self):
288 # we need build dir for vtest-init-vserver
290 # a full path for the local calls
291 build_dir=os.path(sys.argv[0])+"/build"
293 # use a standard name - will be relative to HOME
294 build_dir="options.buildname"
295 # run checkout in any case - would do an update if already exists
296 build_checkout = "svn checkout %s %s"%(self.options.build_url,build_dir)
297 if self.run_in_host(build_checkout) != 0:
299 # the repo url is taken from myplc-url
300 # with the last two steps (i386/myplc...) removed
301 repo_url = self.options.myplc_url
302 for level in [ 'rpmname','arch' ]:
303 repo_url = os.path.dirname(repo_url)
304 create_vserver="%s/vtest-init-vserver.sh %s %s -- --interface eth0:%s"%\
305 (build_dir,self.vservername,repo_url,self.vserverip)
306 return self.run_in_host(create_vserver) == 0
310 return self.install_vserver()
312 return self.install_chroot()
314 ### install_rpm - make this an optional step
316 url = self.options.myplc_url
317 rpm = os.path.basename(url)
318 cache_fetch="pwd;if [ -f %(rpm)s ] ; then echo Using cached rpm %(rpm)s ; else echo Fetching %(url)s ; curl -O %(url)s; fi"%locals()
319 return self.run_in_host(cache_fetch)==0
321 def install_rpm_chroot(self):
322 url = self.options.myplc_url
323 rpm = os.path.basename(url)
324 if not self.cache_rpm():
326 utils.header('Installing the : %s'%rpm)
327 return self.run_in_host('rpm -Uvh '+rpm)==0 and self.run_in_host('service plc mount')==0
329 def install_rpm_vserver(self):
330 return self.run_in_guest("yum -y install myplc-native")==0
332 def install_rpm(self):
334 return self.install_rpm_vserver()
336 return self.install_rpm_chroot()
340 tmpname='%s.plc-config-tty'%(self.name())
341 fileconf=open(tmpname,'w')
342 for var in [ 'PLC_NAME',
346 'PLC_MAIL_SUPPORT_ADDRESS',
353 fileconf.write ('e %s\n%s\n'%(var,self.plc_spec[var]))
354 fileconf.write('w\n')
355 fileconf.write('q\n')
357 utils.system('cat %s'%tmpname)
358 self.run_in_guest_piped('cat %s'%tmpname,'plc-config-tty')
359 utils.system('rm %s'%tmpname)
362 # the chroot install is slightly different to this respect
365 self.run_in_guest('service plc start')
367 self.run_in_host('service plc start')
372 self.run_in_guest('service plc stop')
374 self.run_in_host('service plc stop')
377 # could use a TestKey class
378 def store_keys(self):
379 for key_spec in self.plc_spec['keys']:
380 TestKey(self,key_spec).store_key()
383 def clean_keys(self):
384 utils.system("rm -rf %s/keys/"%os.path(sys.argv[0]))
387 return self.do_sites()
389 def clean_sites (self):
390 return self.do_sites(action="delete")
392 def do_sites (self,action="add"):
393 for site_spec in self.plc_spec['sites']:
394 test_site = TestSite (self,site_spec)
395 if (action != "add"):
396 utils.header("Deleting site %s in %s"%(test_site.name(),self.name()))
397 test_site.delete_site()
398 # deleted with the site
399 #test_site.delete_users()
402 utils.header("Creating site %s & users in %s"%(test_site.name(),self.name()))
403 test_site.create_site()
404 test_site.create_users()
408 return self.do_nodes()
409 def clean_nodes (self):
410 return self.do_nodes(action="delete")
412 def do_nodes (self,action="add"):
413 for site_spec in self.plc_spec['sites']:
414 test_site = TestSite (self,site_spec)
416 utils.header("Deleting nodes in site %s"%test_site.name())
417 for node_spec in site_spec['nodes']:
418 test_node=TestNode(self,test_site,node_spec)
419 utils.header("Deleting %s"%test_node.name())
420 test_node.delete_node()
422 utils.header("Creating nodes for site %s in %s"%(test_site.name(),self.name()))
423 for node_spec in site_spec['nodes']:
424 utils.pprint('Creating node %s'%node_spec,node_spec)
425 test_node = TestNode (self,test_site,node_spec)
426 test_node.create_node ()
429 # create nodegroups if needed, and populate
430 # no need for a clean_nodegroups if we are careful enough
431 def nodegroups (self):
432 # 1st pass to scan contents
434 for site_spec in self.plc_spec['sites']:
435 test_site = TestSite (self,site_spec)
436 for node_spec in site_spec['nodes']:
437 test_node=TestNode (self,test_site,node_spec)
438 if node_spec.has_key('nodegroups'):
439 nodegroupnames=node_spec['nodegroups']
440 if isinstance(nodegroupnames,StringTypes):
441 nodegroupnames = [ nodegroupnames ]
442 for nodegroupname in nodegroupnames:
443 if not groups_dict.has_key(nodegroupname):
444 groups_dict[nodegroupname]=[]
445 groups_dict[nodegroupname].append(test_node.name())
446 auth=self.auth_root()
447 for (nodegroupname,group_nodes) in groups_dict.iteritems():
449 self.apiserver.GetNodeGroups(auth,{'name':nodegroupname})[0]
451 self.apiserver.AddNodeGroup(auth,{'name':nodegroupname})
452 for node in group_nodes:
453 self.apiserver.AddNodeToNodeGroup(auth,node,nodegroupname)
456 def all_hostnames (self) :
458 for site_spec in self.plc_spec['sites']:
459 hostnames += [ node_spec['node_fields']['hostname'] \
460 for node_spec in site_spec['nodes'] ]
463 # gracetime : during the first <gracetime> minutes nothing gets printed
464 def do_nodes_booted (self, minutes, gracetime=2):
465 if self.options.dry_run:
469 timeout = datetime.datetime.now()+datetime.timedelta(minutes=minutes)
470 graceout = datetime.datetime.now()+datetime.timedelta(minutes=gracetime)
471 # the nodes that haven't checked yet - start with a full list and shrink over time
472 tocheck = self.all_hostnames()
473 utils.header("checking nodes %r"%tocheck)
474 # create a dict hostname -> status
475 status = dict ( [ (hostname,'undef') for hostname in tocheck ] )
478 tocheck_status=self.apiserver.GetNodes(self.auth_root(), tocheck, ['hostname','boot_state' ] )
480 for array in tocheck_status:
481 hostname=array['hostname']
482 boot_state=array['boot_state']
483 if boot_state == 'boot':
484 utils.header ("%s has reached the 'boot' state"%hostname)
486 # if it's a real node, never mind
487 (site_spec,node_spec)=self.locate_hostname(hostname)
488 if TestNode.is_real_model(node_spec['node_fields']['model']):
489 utils.header("WARNING - Real node %s in %s - ignored"%(hostname,boot_state))
492 if datetime.datetime.now() > graceout:
493 utils.header ("%s still in '%s' state"%(hostname,boot_state))
494 graceout=datetime.datetime.now()+datetime.timedelta(1)
495 status[hostname] = boot_state
497 tocheck = [ hostname for (hostname,boot_state) in status.iteritems() if boot_state != 'boot' ]
500 if datetime.datetime.now() > timeout:
501 for hostname in tocheck:
502 utils.header("FAILURE due to %s in '%s' state"%(hostname,status[hostname]))
504 # otherwise, sleep for a while
506 # only useful in empty plcs
509 def nodes_booted(self):
510 return self.do_nodes_booted(minutes=0)
513 def do_nodes_ssh(self,minutes):
515 timeout = datetime.datetime.now()+datetime.timedelta(minutes=minutes)
516 tocheck = self.all_hostnames()
517 # self.scan_publicKeys(tocheck)
518 utils.header("checking Connectivity on nodes %r"%tocheck)
520 for hostname in tocheck:
521 # try to ssh in nodes
522 node_test_ssh = TestSsh (hostname,key="/etc/planetlab/root_ssh_key.rsa")
523 access=self.run_in_guest(node_test_ssh.actual_command("date"))
525 utils.header('The node %s is sshable -->'%hostname)
527 tocheck.remove(hostname)
529 # we will have tried real nodes once, in case they're up - but if not, just skip
530 (site_spec,node_spec)=self.locate_hostname(hostname)
531 if TestNode.is_real_model(node_spec['node_fields']['model']):
532 utils.header ("WARNING : check ssh access into real node %s - skipped"%hostname)
533 tocheck.remove(hostname)
536 if datetime.datetime.now() > timeout:
537 for hostname in tocheck:
538 utils.header("FAILURE to ssh into %s"%hostname)
540 # otherwise, sleep for a while
542 # only useful in empty plcs
546 return self.do_nodes_ssh(minutes=2)
549 def init_node (self): pass
551 def bootcd (self): pass
553 def configure_qemu (self): pass
555 def reinstall_node (self): pass
557 def do_check_initscripts(self):
559 for slice_spec in self.plc_spec['slices']:
560 if not slice_spec.has_key('initscriptname'):
562 initscript=slice_spec['initscriptname']
563 for nodename in slice_spec['nodenames']:
564 (site,node) = self.locate_node (nodename)
565 # xxx - passing the wrong site - probably harmless
566 test_site = TestSite (self,site)
567 test_slice = TestSlice (self,test_site,slice_spec)
568 test_node = TestNode (self,test_site,node)
569 test_sliver = TestSliver (self, test_node, test_slice)
570 if not test_sliver.check_initscript(initscript):
574 def check_initscripts(self):
575 return self.do_check_initscripts()
577 def initscripts (self):
578 for initscript in self.plc_spec['initscripts']:
579 utils.pprint('Adding Initscript in plc %s'%self.plc_spec['name'],initscript)
580 self.apiserver.AddInitScript(self.auth_root(),initscript['initscript_fields'])
584 return self.do_slices()
586 def clean_slices (self):
587 return self.do_slices("delete")
589 def do_slices (self, action="add"):
590 for slice in self.plc_spec['slices']:
591 site_spec = self.locate_site (slice['sitename'])
592 test_site = TestSite(self,site_spec)
593 test_slice=TestSlice(self,test_site,slice)
595 utils.header("Deleting slices in site %s"%test_site.name())
596 test_slice.delete_slice()
598 utils.pprint("Creating slice",slice)
599 test_slice.create_slice()
600 utils.header('Created Slice %s'%slice['slice_fields']['name'])
603 @slice_mapper_options
604 def check_slice(self): pass
607 def clear_known_hosts (self): pass
610 def start_node (self) : pass
612 def locate_first_sliver (self):
613 slice_spec = self.plc_spec['slices'][0]
614 slicename = slice_spec['slice_fields']['name']
615 nodename = slice_spec['nodenames'][0]
616 return self.locate_sliver_obj(nodename,slicename)
618 def locate_sliver_obj (self,nodename,slicename):
619 (site,node) = self.locate_node(nodename)
620 slice = self.locate_slice (slicename)
622 test_site = TestSite (self, site)
623 test_node = TestNode (self, test_site,node)
624 # xxx the slice site is assumed to be the node site - mhh - probably harmless
625 test_slice = TestSlice (self, test_site, slice)
626 return TestSliver (self, test_node, test_slice)
628 def check_tcp (self):
629 specs = self.plc_spec['tcp_test']
634 s_test_sliver = self.locate_sliver_obj (spec['server_node'],spec['server_slice'])
635 if not s_test_sliver.run_tcp_server(port,timeout=10):
639 # idem for the client side
640 c_test_sliver = self.locate_sliver_obj(spec['server_node'],spec['server_slice'])
641 if not c_test_sliver.run_tcp_client(s_test_sliver.test_node.name(),port):
646 def gather_logs (self):
647 # (1) get the plc's /var/log and store it locally in logs/<plcname>-var-log/*
648 # (2) get all the nodes qemu log and store it as logs/<node>-qemu.log
649 # (3) get the nodes /var/log and store is as logs/<node>-var-log/*
650 # (4) as far as possible get the slice's /var/log as logs/<slice>-<node>-var-log/*
652 print "-------------------- TestPlc.gather_logs : PLC's /var/log"
653 self.gather_var_logs ()
655 print "-------------------- TestPlc.gather_logs : nodes's QEMU logs"
656 for site_spec in self.plc_spec['sites']:
657 test_site = TestSite (self,site_spec)
658 for node_spec in site_spec['nodes']:
659 test_node=TestNode(self,test_site,node_spec)
660 test_node.gather_qemu_logs()
662 print "-------------------- TestPlc.gather_logs : nodes's /var/log"
663 self.gather_nodes_var_logs()
665 print "-------------------- TestPlc.gather_logs : sample sliver's /var/log"
666 self.gather_first_sliver_logs()
669 def gather_first_sliver_logs(self):
671 test_sliver = self.locate_first_sliver()
672 remote = test_sliver.tar_var_logs()
673 utils.system("mkdir -p logs/%s-var-log"%test_sliver.name())
674 command = remote + " | tar -C logs/%s-var-log -xf -"%test_sliver.name()
675 utils.system(command)
677 print 'Cannot locate first sliver - giving up',e
680 def gather_var_logs (self):
681 to_plc = self.actual_command_in_guest("tar -C /var/log/ -cf - .")
682 command = to_plc + "| tar -C logs/%s-var-log -xf -"%self.name()
683 utils.system("mkdir -p logs/%s-var-log"%self.name())
684 utils.system(command)
686 def gather_nodes_var_logs (self):
687 for site_spec in self.plc_spec['sites']:
688 test_site = TestSite (self,site_spec)
689 for node_spec in site_spec['nodes']:
690 test_node=TestNode(self,test_site,node_spec)
691 test_ssh = TestSsh (test_node.name(),key="/etc/planetlab/root_ssh_key.rsa")
692 to_plc = self.actual_command_in_guest ( test_ssh.actual_command("tar -C /var/log -cf - ."))
693 command = to_plc + "| tar -C logs/%s-var-log -xf -"%test_node.name()
694 utils.system("mkdir -p logs/%s-var-log"%test_node.name())
695 utils.system(command)
698 # returns the filename to use for sql dump/restore, using options.dbname if set
699 def dbfile (self, database):
700 # uses options.dbname if it is found
702 name=self.options.dbname
703 if not isinstance(name,StringTypes):
706 t=datetime.datetime.now()
709 return "/root/%s-%s.sql"%(database,name)
712 dump=self.dbfile("planetab4")
713 self.run_in_guest('pg_dump -U pgsqluser planetlab4 -f '+ dump)
714 utils.header('Dumped planetlab4 database in %s'%dump)
717 def db_restore(self):
718 dump=self.dbfile("planetab4")
720 self.run_in_guest('service httpd stop')
721 # xxx - need another wrapper
722 self.run_in_guest_piped('echo drop database planetlab4','psql --user=pgsqluser template1')
723 self.run_in_guest('createdb -U postgres --encoding=UNICODE --owner=pgsqluser planetlab4')
724 self.run_in_guest('psql -U pgsqluser planetlab4 -f '+dump)
725 ##starting httpd service
726 self.run_in_guest('service httpd start')
728 utils.header('Database restored from ' + dump)
731 def standby_1(): pass
733 def standby_2(): pass
735 def standby_3(): pass
737 def standby_4(): pass
739 def standby_5(): pass
741 def standby_6(): pass
743 def standby_7(): pass
745 def standby_8(): pass
747 def standby_9(): pass
749 def standby_10(): pass
751 def standby_11(): pass
753 def standby_12(): pass
755 def standby_13(): pass
757 def standby_14(): pass
759 def standby_15(): pass
761 def standby_16(): pass
763 def standby_17(): pass
765 def standby_18(): pass
767 def standby_19(): pass
769 def standby_20(): pass