1 # Thierry Parmentelat <thierry.parmentelat@inria.fr>
2 # Copyright (C) 2010 INRIA
9 from datetime import datetime, timedelta
12 from Completer import Completer, CompleterTask
13 from TestSite import TestSite
14 from TestNode import TestNode, CompleterTaskNodeSsh
15 from TestUser import TestUser
16 from TestKey import TestKey
17 from TestSlice import TestSlice
18 from TestSliver import TestSliver
19 from TestBoxQemu import TestBoxQemu
20 from TestSsh import TestSsh
21 from TestApiserver import TestApiserver
22 from TestAuthSfa import TestAuthSfa
23 from PlcapiUrlScanner import PlcapiUrlScanner
25 from TestBonding import TestBonding
27 has_sfa_cache_filename="sfa-cache"
29 # step methods must take (self) and return a boolean (options is a member of the class)
31 def standby(minutes, dry_run):
32 utils.header('Entering StandBy for {:d} mn'.format(minutes))
36 time.sleep(60*minutes)
39 def standby_generic(func):
41 minutes = int(func.__name__.split("_")[1])
42 return standby(minutes, self.options.dry_run)
45 def node_mapper(method):
46 def map_on_nodes(self, *args, **kwds):
48 node_method = TestNode.__dict__[method.__name__]
49 for test_node in self.all_nodes():
50 if not node_method(test_node, *args, **kwds):
53 # maintain __name__ for ignore_result
54 map_on_nodes.__name__ = method.__name__
55 # restore the doc text
56 map_on_nodes.__doc__ = TestNode.__dict__[method.__name__].__doc__
59 def slice_mapper(method):
60 def map_on_slices(self):
62 slice_method = TestSlice.__dict__[method.__name__]
63 for slice_spec in self.plc_spec['slices']:
64 site_spec = self.locate_site (slice_spec['sitename'])
65 test_site = TestSite(self,site_spec)
66 test_slice = TestSlice(self,test_site,slice_spec)
67 if not slice_method(test_slice, self.options):
70 # maintain __name__ for ignore_result
71 map_on_slices.__name__ = method.__name__
72 # restore the doc text
73 map_on_slices.__doc__ = TestSlice.__dict__[method.__name__].__doc__
76 def bonding_redirector(method):
77 bonding_name = method.__name__.replace('bonding_', '')
79 bonding_method = TestBonding.__dict__[bonding_name]
80 return bonding_method(self.test_bonding)
81 # maintain __name__ for ignore_result
82 redirect.__name__ = method.__name__
83 # restore the doc text
84 redirect.__doc__ = TestBonding.__dict__[bonding_name].__doc__
87 # run a step but return True so that we can go on
88 def ignore_result(method):
90 # ssh_slice_ignore->ssh_slice
91 ref_name = method.__name__.replace('_ignore', '').replace('force_', '')
92 ref_method = TestPlc.__dict__[ref_name]
93 result = ref_method(self)
94 print("Actual (but ignored) result for {ref_name} is {result}".format(**locals()))
95 return Ignored(result)
96 name = method.__name__.replace('_ignore', '').replace('force_', '')
97 ignoring.__name__ = name
98 ignoring.__doc__ = "ignored version of " + name
101 # a variant that expects the TestSlice method to return a list of CompleterTasks that
102 # are then merged into a single Completer run to avoid wating for all the slices
103 # esp. useful when a test fails of course
104 # because we need to pass arguments we use a class instead..
105 class slice_mapper__tasks(object):
106 # could not get this to work with named arguments
107 def __init__(self, timeout_minutes, silent_minutes, period_seconds):
108 self.timeout = timedelta(minutes = timeout_minutes)
109 self.silent = timedelta(minutes = silent_minutes)
110 self.period = timedelta(seconds = period_seconds)
111 def __call__(self, method):
113 # compute augmented method name
114 method_name = method.__name__ + "__tasks"
115 # locate in TestSlice
116 slice_method = TestSlice.__dict__[ method_name ]
119 for slice_spec in self.plc_spec['slices']:
120 site_spec = self.locate_site (slice_spec['sitename'])
121 test_site = TestSite(self, site_spec)
122 test_slice = TestSlice(self, test_site, slice_spec)
123 tasks += slice_method (test_slice, self.options)
124 return Completer (tasks, message=method.__name__).\
125 run(decorator_self.timeout, decorator_self.silent, decorator_self.period)
126 # restore the doc text from the TestSlice method even if a bit odd
127 wrappee.__name__ = method.__name__
128 wrappee.__doc__ = slice_method.__doc__
131 def auth_sfa_mapper(method):
134 auth_method = TestAuthSfa.__dict__[method.__name__]
135 for auth_spec in self.plc_spec['sfa']['auth_sfa_specs']:
136 test_auth = TestAuthSfa(self, auth_spec)
137 if not auth_method(test_auth, self.options):
140 # restore the doc text
141 actual.__doc__ = TestAuthSfa.__dict__[method.__name__].__doc__
145 def __init__(self, result):
155 'plcvm_delete','plcvm_timestamp','plcvm_create', SEP,
156 'plc_install', 'plc_configure', 'plc_start', SEP,
157 'keys_fetch', 'keys_store', 'keys_clear_known_hosts', SEP,
158 'plcapi_urls','speed_up_slices', SEP,
159 'initscripts', 'sites', 'nodes', 'slices', 'nodegroups', 'leases', SEP,
160 # slices created under plcsh interactively seem to be fine but these ones don't have the tags
161 # keep this our of the way for now
162 'check_vsys_defaults_ignore', SEP,
163 # run this first off so it's easier to re-run on another qemu box
164 'qemu_kill_mine', 'nodestate_reinstall', 'qemu_local_init','bootcd', 'qemu_local_config', SEP,
165 'qemu_clean_mine', 'qemu_export', 'qemu_start', 'qemu_timestamp', SEP,
166 'sfa_install_all', 'sfa_configure', 'cross_sfa_configure', 'sfa_start', 'sfa_import', SEPSFA,
167 'sfi_configure@1', 'sfa_register_site@1','sfa_register_pi@1', SEPSFA,
168 'sfa_register_user@1', 'sfa_update_user@1', 'sfa_register_slice@1', 'sfa_renew_slice@1', SEPSFA,
169 'sfa_remove_user_from_slice@1','sfi_show_slice_researchers@1',
170 'sfa_insert_user_in_slice@1','sfi_show_slice_researchers@1', SEPSFA,
171 'sfa_discover@1', 'sfa_rspec@1', 'sfa_allocate@1', 'sfa_provision@1', SEPSFA,
172 'sfa_check_slice_plc@1', 'sfa_update_slice@1', SEPSFA,
173 'sfi_list@1', 'sfi_show_site@1', 'sfa_utest@1', SEPSFA,
174 # we used to run plcsh_stress_test, and then ssh_node_debug and ssh_node_boot
175 # but as the stress test might take a while, we sometimes missed the debug mode..
176 'probe_kvm_iptables',
177 'ping_node', 'ssh_node_debug', 'plcsh_stress_test@1', SEP,
178 'ssh_node_boot', 'node_bmlogs', 'ssh_slice', 'ssh_slice_basics', 'check_initscripts', SEP,
179 'ssh_slice_sfa@1', SEPSFA,
180 'sfa_rspec_empty@1', 'sfa_allocate_empty@1', 'sfa_provision_empty@1','sfa_check_slice_plc_empty@1', SEPSFA,
181 'sfa_delete_slice@1', 'sfa_delete_user@1', SEPSFA,
182 'cross_check_tcp@1', 'check_system_slice', SEP,
183 # for inspecting the slice while it runs the first time
185 # check slices are turned off properly
186 'empty_slices', 'ssh_slice_off', 'slice_fs_deleted_ignore', SEP,
187 # check they are properly re-created with the same name
188 'fill_slices', 'ssh_slice_again', SEP,
189 'gather_logs_force', SEP,
192 'export', 'show_boxes', 'super_speed_up_slices', SEP,
193 'check_hooks', 'plc_stop', 'plcvm_start', 'plcvm_stop', SEP,
194 'delete_initscripts', 'delete_nodegroups','delete_all_sites', SEP,
195 'delete_sites', 'delete_nodes', 'delete_slices', 'keys_clean', SEP,
196 'delete_leases', 'list_leases', SEP,
198 'nodestate_show','nodestate_safeboot','nodestate_boot', SEP,
199 'qemu_list_all', 'qemu_list_mine', 'qemu_kill_all', SEP,
200 'sfa_install_core', 'sfa_install_sfatables', 'sfa_install_plc', 'sfa_install_client', SEPSFA,
201 'sfa_plcclean', 'sfa_dbclean', 'sfa_stop','sfa_uninstall', 'sfi_clean', SEPSFA,
202 'sfa_get_expires', SEPSFA,
203 'plc_db_dump' , 'plc_db_restore', SEP,
204 'check_netflow','check_drl', SEP,
205 'debug_nodemanager', 'slice_fs_present', SEP,
206 'standby_1_through_20','yes','no',SEP,
208 default_bonding_steps = [
209 'bonding_init_partial',
211 'bonding_install_rpms', SEP,
215 def printable_steps(list):
216 single_line = " ".join(list) + " "
217 return single_line.replace(" "+SEP+" ", " \\\n").replace(" "+SEPSFA+" ", " \\\n")
219 def valid_step(step):
220 return step != SEP and step != SEPSFA
222 # turn off the sfa-related steps when build has skipped SFA
223 # this was originally for centos5 but is still valid
224 # for up to f12 as recent SFAs with sqlalchemy won't build before f14
226 def _has_sfa_cached(rpms_url):
227 if os.path.isfile(has_sfa_cache_filename):
228 with open(has_sfa_cache_filename) as cache:
229 cached = cache.read() == "yes"
230 utils.header("build provides SFA (cached):{}".format(cached))
232 # warning, we're now building 'sface' so let's be a bit more picky
233 # full builds are expected to return with 0 here
234 utils.header("Checking if build provides SFA package...")
235 retcod = utils.system("curl --silent {}/ | grep -q sfa-".format(rpms_url)) == 0
236 encoded = 'yes' if retcod else 'no'
237 with open(has_sfa_cache_filename,'w') as cache:
242 def check_whether_build_has_sfa(rpms_url):
243 has_sfa = TestPlc._has_sfa_cached(rpms_url)
245 utils.header("build does provide SFA")
247 # move all steps containing 'sfa' from default_steps to other_steps
248 utils.header("SFA package not found - removing steps with sfa or sfi")
249 sfa_steps = [ step for step in TestPlc.default_steps
250 if step.find('sfa') >= 0 or step.find("sfi") >= 0 ]
251 TestPlc.other_steps += sfa_steps
252 for step in sfa_steps:
253 TestPlc.default_steps.remove(step)
255 def __init__(self, plc_spec, options):
256 self.plc_spec = plc_spec
257 self.options = options
258 self.test_ssh = TestSsh(self.plc_spec['host_box'], self.options.buildname)
259 self.vserverip = plc_spec['vserverip']
260 self.vservername = plc_spec['vservername']
261 self.vplchostname = self.vservername.split('-')[-1]
262 self.url = "https://{}:443/PLCAPI/".format(plc_spec['vserverip'])
263 self.apiserver = TestApiserver(self.url, options.dry_run)
264 (self.ssh_node_boot_timeout, self.ssh_node_boot_silent) = plc_spec['ssh_node_boot_timers']
265 (self.ssh_node_debug_timeout, self.ssh_node_debug_silent) = plc_spec['ssh_node_debug_timers']
267 def has_addresses_api(self):
268 return self.apiserver.has_method('AddIpAddress')
271 name = self.plc_spec['name']
272 return "{}.{}".format(name,self.vservername)
275 return self.plc_spec['host_box']
278 return self.test_ssh.is_local()
280 # define the API methods on this object through xmlrpc
281 # would help, but not strictly necessary
285 def actual_command_in_guest(self,command, backslash=False):
286 raw1 = self.host_to_guest(command)
287 raw2 = self.test_ssh.actual_command(raw1, dry_run=self.options.dry_run, backslash=backslash)
290 def start_guest(self):
291 return utils.system(self.test_ssh.actual_command(self.start_guest_in_host(),
292 dry_run=self.options.dry_run))
294 def stop_guest(self):
295 return utils.system(self.test_ssh.actual_command(self.stop_guest_in_host(),
296 dry_run=self.options.dry_run))
298 def run_in_guest(self, command, backslash=False):
299 raw = self.actual_command_in_guest(command, backslash)
300 return utils.system(raw)
302 def run_in_host(self,command):
303 return self.test_ssh.run_in_buildname(command, dry_run=self.options.dry_run)
305 # backslashing turned out so awful at some point that I've turned off auto-backslashing
306 # see e.g. plc_start esp. the version for f14
307 #command gets run in the plc's vm
308 def host_to_guest(self, command):
309 ssh_leg = TestSsh(self.vplchostname)
310 return ssh_leg.actual_command(command, keep_stdin=True)
312 # this /vservers thing is legacy...
313 def vm_root_in_host(self):
314 return "/vservers/{}/".format(self.vservername)
316 def vm_timestamp_path(self):
317 return "/vservers/{}/{}.timestamp".format(self.vservername, self.vservername)
319 #start/stop the vserver
320 def start_guest_in_host(self):
321 return "virsh -c lxc:/// start {}".format(self.vservername)
323 def stop_guest_in_host(self):
324 return "virsh -c lxc:/// destroy {}".format(self.vservername)
327 def run_in_guest_piped(self,local,remote):
328 return utils.system(local+" | "+self.test_ssh.actual_command(self.host_to_guest(remote),
331 def yum_check_installed(self, rpms):
332 if isinstance(rpms, list):
334 return self.run_in_guest("rpm -q {}".format(rpms)) == 0
336 # does a yum install in the vs, ignore yum retcod, check with rpm
337 def yum_install(self, rpms):
338 if isinstance(rpms, list):
340 self.run_in_guest("yum -y install {}".format(rpms))
341 # yum-complete-transaction comes with yum-utils, that is in vtest.pkgs
342 self.run_in_guest("yum-complete-transaction -y")
343 return self.yum_check_installed(rpms)
346 return {'Username' : self.plc_spec['settings']['PLC_ROOT_USER'],
347 'AuthMethod' : 'password',
348 'AuthString' : self.plc_spec['settings']['PLC_ROOT_PASSWORD'],
349 'Role' : self.plc_spec['role'],
352 def locate_site(self,sitename):
353 for site in self.plc_spec['sites']:
354 if site['site_fields']['name'] == sitename:
356 if site['site_fields']['login_base'] == sitename:
358 raise Exception("Cannot locate site {}".format(sitename))
360 def locate_node(self, nodename):
361 for site in self.plc_spec['sites']:
362 for node in site['nodes']:
363 if node['name'] == nodename:
365 raise Exception("Cannot locate node {}".format(nodename))
367 def locate_hostname(self, hostname):
368 for site in self.plc_spec['sites']:
369 for node in site['nodes']:
370 if node['node_fields']['hostname'] == hostname:
372 raise Exception("Cannot locate hostname {}".format(hostname))
374 def locate_key(self, key_name):
375 for key in self.plc_spec['keys']:
376 if key['key_name'] == key_name:
378 raise Exception("Cannot locate key {}".format(key_name))
380 def locate_private_key_from_key_names(self, key_names):
381 # locate the first avail. key
383 for key_name in key_names:
384 key_spec = self.locate_key(key_name)
385 test_key = TestKey(self,key_spec)
386 publickey = test_key.publicpath()
387 privatekey = test_key.privatepath()
388 if os.path.isfile(publickey) and os.path.isfile(privatekey):
395 def locate_slice(self, slicename):
396 for slice in self.plc_spec['slices']:
397 if slice['slice_fields']['name'] == slicename:
399 raise Exception("Cannot locate slice {}".format(slicename))
401 def all_sliver_objs(self):
403 for slice_spec in self.plc_spec['slices']:
404 slicename = slice_spec['slice_fields']['name']
405 for nodename in slice_spec['nodenames']:
406 result.append(self.locate_sliver_obj(nodename, slicename))
409 def locate_sliver_obj(self, nodename, slicename):
410 site,node = self.locate_node(nodename)
411 slice = self.locate_slice(slicename)
413 test_site = TestSite(self, site)
414 test_node = TestNode(self, test_site, node)
415 # xxx the slice site is assumed to be the node site - mhh - probably harmless
416 test_slice = TestSlice(self, test_site, slice)
417 return TestSliver(self, test_node, test_slice)
419 def locate_first_node(self):
420 nodename = self.plc_spec['slices'][0]['nodenames'][0]
421 site,node = self.locate_node(nodename)
422 test_site = TestSite(self, site)
423 test_node = TestNode(self, test_site, node)
426 def locate_first_sliver(self):
427 slice_spec = self.plc_spec['slices'][0]
428 slicename = slice_spec['slice_fields']['name']
429 nodename = slice_spec['nodenames'][0]
430 return self.locate_sliver_obj(nodename,slicename)
432 # all different hostboxes used in this plc
433 def get_BoxNodes(self):
434 # maps on sites and nodes, return [ (host_box,test_node) ]
436 for site_spec in self.plc_spec['sites']:
437 test_site = TestSite(self,site_spec)
438 for node_spec in site_spec['nodes']:
439 test_node = TestNode(self, test_site, node_spec)
440 if not test_node.is_real():
441 tuples.append( (test_node.host_box(),test_node) )
442 # transform into a dict { 'host_box' -> [ test_node .. ] }
444 for (box,node) in tuples:
445 if box not in result:
448 result[box].append(node)
451 # a step for checking this stuff
452 def show_boxes(self):
453 'print summary of nodes location'
454 for box,nodes in self.get_BoxNodes().items():
455 print(box,":"," + ".join( [ node.name() for node in nodes ] ))
458 # make this a valid step
459 def qemu_kill_all(self):
460 'kill all qemu instances on the qemu boxes involved by this setup'
461 # this is the brute force version, kill all qemus on that host box
462 for (box,nodes) in self.get_BoxNodes().items():
463 # pass the first nodename, as we don't push template-qemu on testboxes
464 nodedir = nodes[0].nodedir()
465 TestBoxQemu(box, self.options.buildname).qemu_kill_all(nodedir)
468 # make this a valid step
469 def qemu_list_all(self):
470 'list all qemu instances on the qemu boxes involved by this setup'
471 for box,nodes in self.get_BoxNodes().items():
472 # this is the brute force version, kill all qemus on that host box
473 TestBoxQemu(box, self.options.buildname).qemu_list_all()
476 # kill only the qemus related to this test
477 def qemu_list_mine(self):
478 'list qemu instances for our nodes'
479 for (box,nodes) in self.get_BoxNodes().items():
480 # the fine-grain version
485 # kill only the qemus related to this test
486 def qemu_clean_mine(self):
487 'cleanup (rm -rf) qemu instances for our nodes'
488 for box,nodes in self.get_BoxNodes().items():
489 # the fine-grain version
494 # kill only the right qemus
495 def qemu_kill_mine(self):
496 'kill the qemu instances for our nodes'
497 for box,nodes in self.get_BoxNodes().items():
498 # the fine-grain version
503 #################### display config
505 "show test configuration after localization"
510 # uggly hack to make sure 'run export' only reports about the 1st plc
511 # to avoid confusion - also we use 'inri_slice1' in various aliases..
514 "print cut'n paste-able stuff to export env variables to your shell"
515 # guess local domain from hostname
516 if TestPlc.exported_id > 1:
517 print("export GUESTHOSTNAME{:d}={}".format(TestPlc.exported_id, self.plc_spec['vservername']))
519 TestPlc.exported_id += 1
520 domain = socket.gethostname().split('.',1)[1]
521 fqdn = "{}.{}".format(self.plc_spec['host_box'], domain)
522 print("export BUILD={}".format(self.options.buildname))
523 print("export PLCHOSTLXC={}".format(fqdn))
524 print("export GUESTNAME={}".format(self.vservername))
525 print("export GUESTHOSTNAME={}.{}".format(self.vplchostname, domain))
526 # find hostname of first node
527 hostname, qemubox = self.all_node_infos()[0]
528 print("export KVMHOST={}.{}".format(qemubox, domain))
529 print("export NODE={}".format(hostname))
533 always_display_keys=['PLC_WWW_HOST', 'nodes', 'sites']
534 def show_pass(self, passno):
535 for (key,val) in self.plc_spec.items():
536 if not self.options.verbose and key not in TestPlc.always_display_keys:
541 self.display_site_spec(site)
542 for node in site['nodes']:
543 self.display_node_spec(node)
544 elif key == 'initscripts':
545 for initscript in val:
546 self.display_initscript_spec(initscript)
547 elif key == 'slices':
549 self.display_slice_spec(slice)
552 self.display_key_spec(key)
554 if key not in ['sites', 'initscripts', 'slices', 'keys']:
555 print('+ ', key, ':', val)
557 def display_site_spec(self, site):
558 print('+ ======== site', site['site_fields']['name'])
559 for k,v in site.items():
560 if not self.options.verbose and k not in TestPlc.always_display_keys:
564 print('+ ','nodes : ', end=' ')
566 print(node['node_fields']['hostname'],'', end=' ')
570 print('+ users : ', end=' ')
572 print(user['name'],'', end=' ')
574 elif k == 'site_fields':
575 print('+ login_base', ':', v['login_base'])
576 elif k == 'address_fields':
582 def display_initscript_spec(self, initscript):
583 print('+ ======== initscript', initscript['initscript_fields']['name'])
585 def display_key_spec(self, key):
586 print('+ ======== key', key['key_name'])
588 def display_slice_spec(self, slice):
589 print('+ ======== slice', slice['slice_fields']['name'])
590 for k,v in slice.items():
593 print('+ nodes : ', end=' ')
595 print(nodename,'', end=' ')
597 elif k == 'usernames':
599 print('+ users : ', end=' ')
601 print(username,'', end=' ')
603 elif k == 'slice_fields':
604 print('+ fields',':', end=' ')
605 print('max_nodes=',v['max_nodes'], end=' ')
610 def display_node_spec(self, node):
611 print("+ node={} host_box={}".format(node['name'], node['host_box']), end=' ')
612 print("hostname=", node['node_fields']['hostname'], end=' ')
613 print("ip=", node['interface_fields']['ip'])
614 if self.options.verbose:
615 utils.pprint("node details", node, depth=3)
617 # another entry point for just showing the boxes involved
618 def display_mapping(self):
619 TestPlc.display_mapping_plc(self.plc_spec)
623 def display_mapping_plc(plc_spec):
624 print('+ MyPLC',plc_spec['name'])
625 # WARNING this would not be right for lxc-based PLC's - should be harmless though
626 print('+\tvserver address = root@{}:/vservers/{}'.format(plc_spec['host_box'], plc_spec['vservername']))
627 print('+\tIP = {}/{}'.format(plc_spec['settings']['PLC_API_HOST'], plc_spec['vserverip']))
628 for site_spec in plc_spec['sites']:
629 for node_spec in site_spec['nodes']:
630 TestPlc.display_mapping_node(node_spec)
633 def display_mapping_node(node_spec):
634 print('+ NODE {}'.format(node_spec['name']))
635 print('+\tqemu box {}'.format(node_spec['host_box']))
636 print('+\thostname={}'.format(node_spec['node_fields']['hostname']))
638 # write a timestamp in /vservers/<>.timestamp
639 # cannot be inside the vserver, that causes vserver .. build to cough
640 def plcvm_timestamp(self):
641 "Create a timestamp to remember creation date for this plc"
642 now = int(time.time())
643 # TODO-lxc check this one
644 # a first approx. is to store the timestamp close to the VM root like vs does
645 stamp_path = self.vm_timestamp_path()
646 stamp_dir = os.path.dirname(stamp_path)
647 utils.system(self.test_ssh.actual_command("mkdir -p {}".format(stamp_dir)))
648 return utils.system(self.test_ssh.actual_command("echo {:d} > {}".format(now, stamp_path))) == 0
650 # this is called inconditionnally at the beginning of the test sequence
651 # just in case this is a rerun, so if the vm is not running it's fine
652 def plcvm_delete(self):
653 "vserver delete the test myplc"
654 stamp_path = self.vm_timestamp_path()
655 self.run_in_host("rm -f {}".format(stamp_path))
656 self.run_in_host("virsh -c lxc:// destroy {}".format(self.vservername))
657 self.run_in_host("virsh -c lxc:// undefine {}".format(self.vservername))
658 self.run_in_host("rm -fr /vservers/{}".format(self.vservername))
662 # historically the build was being fetched by the tests
663 # now the build pushes itself as a subdir of the tests workdir
664 # so that the tests do not have to worry about extracting the build (svn, git, or whatever)
665 def plcvm_create(self):
666 "vserver creation (no install done)"
667 # push the local build/ dir to the testplc box
669 # a full path for the local calls
670 build_dir = os.path.dirname(sys.argv[0])
671 # sometimes this is empty - set to "." in such a case
674 build_dir += "/build"
676 # use a standard name - will be relative to remote buildname
678 # remove for safety; do *not* mkdir first, otherwise we end up with build/build/
679 self.test_ssh.rmdir(build_dir)
680 self.test_ssh.copy(build_dir, recursive=True)
681 # the repo url is taken from arch-rpms-url
682 # with the last step (i386) removed
683 repo_url = self.options.arch_rpms_url
684 for level in [ 'arch' ]:
685 repo_url = os.path.dirname(repo_url)
687 # invoke initvm (drop support for vs)
688 script = "lbuild-initvm.sh"
690 # pass the vbuild-nightly options to [lv]test-initvm
691 script_options += " -p {}".format(self.options.personality)
692 script_options += " -d {}".format(self.options.pldistro)
693 script_options += " -f {}".format(self.options.fcdistro)
694 script_options += " -r {}".format(repo_url)
695 vserver_name = self.vservername
697 vserver_hostname = socket.gethostbyaddr(self.vserverip)[0]
698 script_options += " -n {}".format(vserver_hostname)
700 print("Cannot reverse lookup {}".format(self.vserverip))
701 print("This is considered fatal, as this might pollute the test results")
703 create_vserver="{build_dir}/{script} {script_options} {vserver_name}".format(**locals())
704 return self.run_in_host(create_vserver) == 0
707 def plc_install(self):
709 yum install myplc, noderepo
710 plain bootstrapfs is not installed anymore
714 if self.options.personality == "linux32":
716 elif self.options.personality == "linux64":
719 raise Exception("Unsupported personality {}".format(self.options.personality))
720 nodefamily = "{}-{}-{}".format(self.options.pldistro, self.options.fcdistro, arch)
723 pkgs_list.append("slicerepo-{}".format(nodefamily))
724 pkgs_list.append("myplc")
725 pkgs_list.append("noderepo-{}".format(nodefamily))
726 pkgs_string=" ".join(pkgs_list)
727 return self.yum_install(pkgs_list)
730 def mod_python(self):
731 """yum install mod_python, useful on f18 and above so as to avoid broken wsgi"""
732 return self.yum_install( ['mod_python'] )
735 def plc_configure(self):
737 tmpname = '{}.plc-config-tty'.format(self.name())
738 with open(tmpname,'w') as fileconf:
739 for (var,value) in self.plc_spec['settings'].items():
740 fileconf.write('e {}\n{}\n'.format(var, value))
741 fileconf.write('w\n')
742 fileconf.write('q\n')
743 utils.system('cat {}'.format(tmpname))
744 self.run_in_guest_piped('cat {}'.format(tmpname), 'plc-config-tty')
745 utils.system('rm {}'.format(tmpname))
748 # f14 is a bit odd in this respect, although this worked fine in guests up to f18
749 # however using a vplc guest under f20 requires this trick
750 # the symptom is this: service plc start
751 # Starting plc (via systemctl): Failed to get D-Bus connection: \
752 # Failed to connect to socket /org/freedesktop/systemd1/private: Connection refused
753 # weird thing is the doc says f14 uses upstart by default and not systemd
754 # so this sounds kind of harmless
755 def start_service(self, service):
756 return self.start_stop_service(service, 'start')
757 def stop_service(self, service):
758 return self.start_stop_service(service, 'stop')
760 def start_stop_service(self, service, start_or_stop):
761 "utility to start/stop a service with the special trick for f14"
762 if self.options.fcdistro != 'f14':
763 return self.run_in_guest("service {} {}".format(service, start_or_stop)) == 0
765 # patch /sbin/service so it does not reset environment
766 self.run_in_guest('sed -i -e \\"s,env -i,env,\\" /sbin/service')
767 # this is because our own scripts in turn call service
768 return self.run_in_guest("SYSTEMCTL_SKIP_REDIRECT=true service {} {}"\
769 .format(service, start_or_stop)) == 0
773 return self.start_service('plc')
777 return self.stop_service('plc')
779 def plcvm_start(self):
780 "start the PLC vserver"
784 def plcvm_stop(self):
785 "stop the PLC vserver"
789 # stores the keys from the config for further use
790 def keys_store(self):
791 "stores test users ssh keys in keys/"
792 for key_spec in self.plc_spec['keys']:
793 TestKey(self,key_spec).store_key()
796 def keys_clean(self):
797 "removes keys cached in keys/"
798 utils.system("rm -rf ./keys")
801 # fetches the ssh keys in the plc's /etc/planetlab and stores them in keys/
802 # for later direct access to the nodes
803 def keys_fetch(self):
804 "gets ssh keys in /etc/planetlab/ and stores them locally in keys/"
806 if not os.path.isdir(dir):
808 vservername = self.vservername
809 vm_root = self.vm_root_in_host()
811 prefix = 'debug_ssh_key'
812 for ext in ['pub', 'rsa'] :
813 src = "{vm_root}/etc/planetlab/{prefix}.{ext}".format(**locals())
814 dst = "keys/{vservername}-debug.{ext}".format(**locals())
815 if self.test_ssh.fetch(src, dst) != 0:
820 "create sites with PLCAPI"
821 return self.do_sites()
823 def delete_sites(self):
824 "delete sites with PLCAPI"
825 return self.do_sites(action="delete")
827 def do_sites(self, action="add"):
828 for site_spec in self.plc_spec['sites']:
829 test_site = TestSite(self,site_spec)
830 if (action != "add"):
831 utils.header("Deleting site {} in {}".format(test_site.name(), self.name()))
832 test_site.delete_site()
833 # deleted with the site
834 #test_site.delete_users()
837 utils.header("Creating site {} & users in {}".format(test_site.name(), self.name()))
838 test_site.create_site()
839 test_site.create_users()
842 def delete_all_sites(self):
843 "Delete all sites in PLC, and related objects"
844 print('auth_root', self.auth_root())
845 sites = self.apiserver.GetSites(self.auth_root(), {}, ['site_id','login_base'])
847 # keep automatic site - otherwise we shoot in our own foot, root_auth is not valid anymore
848 if site['login_base'] == self.plc_spec['settings']['PLC_SLICE_PREFIX']:
850 site_id = site['site_id']
851 print('Deleting site_id', site_id)
852 self.apiserver.DeleteSite(self.auth_root(), site_id)
856 "create nodes with PLCAPI"
857 return self.do_nodes()
858 def delete_nodes(self):
859 "delete nodes with PLCAPI"
860 return self.do_nodes(action="delete")
862 def do_nodes(self, action="add"):
863 for site_spec in self.plc_spec['sites']:
864 test_site = TestSite(self, site_spec)
866 utils.header("Deleting nodes in site {}".format(test_site.name()))
867 for node_spec in site_spec['nodes']:
868 test_node = TestNode(self, test_site, node_spec)
869 utils.header("Deleting {}".format(test_node.name()))
870 test_node.delete_node()
872 utils.header("Creating nodes for site {} in {}".format(test_site.name(), self.name()))
873 for node_spec in site_spec['nodes']:
874 utils.pprint('Creating node {}'.format(node_spec), node_spec)
875 test_node = TestNode(self, test_site, node_spec)
876 test_node.create_node()
879 def nodegroups(self):
880 "create nodegroups with PLCAPI"
881 return self.do_nodegroups("add")
882 def delete_nodegroups(self):
883 "delete nodegroups with PLCAPI"
884 return self.do_nodegroups("delete")
888 def translate_timestamp(start, grain, timestamp):
889 if timestamp < TestPlc.YEAR:
890 return start + timestamp*grain
895 def timestamp_printable(timestamp):
896 return time.strftime('%m-%d %H:%M:%S UTC', time.gmtime(timestamp))
899 "create leases (on reservable nodes only, use e.g. run -c default -c resa)"
900 now = int(time.time())
901 grain = self.apiserver.GetLeaseGranularity(self.auth_root())
902 print('API answered grain=', grain)
903 start = (now//grain)*grain
905 # find out all nodes that are reservable
906 nodes = self.all_reservable_nodenames()
908 utils.header("No reservable node found - proceeding without leases")
911 # attach them to the leases as specified in plc_specs
912 # this is where the 'leases' field gets interpreted as relative of absolute
913 for lease_spec in self.plc_spec['leases']:
914 # skip the ones that come with a null slice id
915 if not lease_spec['slice']:
917 lease_spec['t_from'] = TestPlc.translate_timestamp(start, grain, lease_spec['t_from'])
918 lease_spec['t_until'] = TestPlc.translate_timestamp(start, grain, lease_spec['t_until'])
919 lease_addition = self.apiserver.AddLeases(self.auth_root(), nodes, lease_spec['slice'],
920 lease_spec['t_from'], lease_spec['t_until'])
921 if lease_addition['errors']:
922 utils.header("Cannot create leases, {}".format(lease_addition['errors']))
925 utils.header('Leases on nodes {} for {} from {:d} ({}) until {:d} ({})'\
926 .format(nodes, lease_spec['slice'],
927 lease_spec['t_from'], TestPlc.timestamp_printable(lease_spec['t_from']),
928 lease_spec['t_until'], TestPlc.timestamp_printable(lease_spec['t_until'])))
932 def delete_leases(self):
933 "remove all leases in the myplc side"
934 lease_ids = [ l['lease_id'] for l in self.apiserver.GetLeases(self.auth_root())]
935 utils.header("Cleaning leases {}".format(lease_ids))
936 self.apiserver.DeleteLeases(self.auth_root(), lease_ids)
939 def list_leases(self):
940 "list all leases known to the myplc"
941 leases = self.apiserver.GetLeases(self.auth_root())
942 now = int(time.time())
944 current = l['t_until'] >= now
945 if self.options.verbose or current:
946 utils.header("{} {} from {} until {}"\
947 .format(l['hostname'], l['name'],
948 TestPlc.timestamp_printable(l['t_from']),
949 TestPlc.timestamp_printable(l['t_until'])))
952 # create nodegroups if needed, and populate
953 def do_nodegroups(self, action="add"):
954 # 1st pass to scan contents
956 for site_spec in self.plc_spec['sites']:
957 test_site = TestSite(self,site_spec)
958 for node_spec in site_spec['nodes']:
959 test_node = TestNode(self, test_site, node_spec)
960 if 'nodegroups' in node_spec:
961 nodegroupnames = node_spec['nodegroups']
962 if isinstance(nodegroupnames, str):
963 nodegroupnames = [ nodegroupnames ]
964 for nodegroupname in nodegroupnames:
965 if nodegroupname not in groups_dict:
966 groups_dict[nodegroupname] = []
967 groups_dict[nodegroupname].append(test_node.name())
968 auth = self.auth_root()
970 for (nodegroupname,group_nodes) in groups_dict.items():
972 print('nodegroups:', 'dealing with nodegroup',\
973 nodegroupname, 'on nodes', group_nodes)
974 # first, check if the nodetagtype is here
975 tag_types = self.apiserver.GetTagTypes(auth, {'tagname':nodegroupname})
977 tag_type_id = tag_types[0]['tag_type_id']
979 tag_type_id = self.apiserver.AddTagType(auth,
980 {'tagname' : nodegroupname,
981 'description' : 'for nodegroup {}'.format(nodegroupname),
982 'category' : 'test'})
983 print('located tag (type)', nodegroupname, 'as', tag_type_id)
985 nodegroups = self.apiserver.GetNodeGroups(auth, {'groupname' : nodegroupname})
987 self.apiserver.AddNodeGroup(auth, nodegroupname, tag_type_id, 'yes')
988 print('created nodegroup', nodegroupname, \
989 'from tagname', nodegroupname, 'and value', 'yes')
990 # set node tag on all nodes, value='yes'
991 for nodename in group_nodes:
993 self.apiserver.AddNodeTag(auth, nodename, nodegroupname, "yes")
995 traceback.print_exc()
996 print('node', nodename, 'seems to already have tag', nodegroupname)
999 expect_yes = self.apiserver.GetNodeTags(auth,
1000 {'hostname' : nodename,
1001 'tagname' : nodegroupname},
1002 ['value'])[0]['value']
1003 if expect_yes != "yes":
1004 print('Mismatch node tag on node',nodename,'got',expect_yes)
1007 if not self.options.dry_run:
1008 print('Cannot find tag', nodegroupname, 'on node', nodename)
1012 print('cleaning nodegroup', nodegroupname)
1013 self.apiserver.DeleteNodeGroup(auth, nodegroupname)
1015 traceback.print_exc()
1019 # a list of TestNode objs
1020 def all_nodes(self):
1022 for site_spec in self.plc_spec['sites']:
1023 test_site = TestSite(self,site_spec)
1024 for node_spec in site_spec['nodes']:
1025 nodes.append(TestNode(self, test_site, node_spec))
1028 # return a list of tuples (nodename,qemuname)
1029 def all_node_infos(self) :
1031 for site_spec in self.plc_spec['sites']:
1032 node_infos += [ (node_spec['node_fields']['hostname'], node_spec['host_box']) \
1033 for node_spec in site_spec['nodes'] ]
1036 def all_nodenames(self):
1037 return [ x[0] for x in self.all_node_infos() ]
1038 def all_reservable_nodenames(self):
1040 for site_spec in self.plc_spec['sites']:
1041 for node_spec in site_spec['nodes']:
1042 node_fields = node_spec['node_fields']
1043 if 'node_type' in node_fields and node_fields['node_type'] == 'reservable':
1044 res.append(node_fields['hostname'])
1047 # silent_minutes : during the first <silent_minutes> minutes nothing gets printed
1048 def nodes_check_boot_state(self, target_boot_state, timeout_minutes,
1049 silent_minutes, period_seconds = 15):
1050 if self.options.dry_run:
1054 class CompleterTaskBootState(CompleterTask):
1055 def __init__(self, test_plc, hostname):
1056 self.test_plc = test_plc
1057 self.hostname = hostname
1058 self.last_boot_state = 'undef'
1059 def actual_run(self):
1061 node = self.test_plc.apiserver.GetNodes(self.test_plc.auth_root(),
1064 self.last_boot_state = node['boot_state']
1065 return self.last_boot_state == target_boot_state
1069 return "CompleterTaskBootState with node {}".format(self.hostname)
1070 def failure_epilogue(self):
1071 print("node {} in state {} - expected {}"\
1072 .format(self.hostname, self.last_boot_state, target_boot_state))
1074 timeout = timedelta(minutes=timeout_minutes)
1075 graceout = timedelta(minutes=silent_minutes)
1076 period = timedelta(seconds=period_seconds)
1077 # the nodes that haven't checked yet - start with a full list and shrink over time
1078 utils.header("checking nodes boot state (expected {})".format(target_boot_state))
1079 tasks = [ CompleterTaskBootState(self,hostname) \
1080 for (hostname,_) in self.all_node_infos() ]
1081 message = 'check_boot_state={}'.format(target_boot_state)
1082 return Completer(tasks, message=message).run(timeout, graceout, period)
1084 def nodes_booted(self):
1085 return self.nodes_check_boot_state('boot', timeout_minutes=30, silent_minutes=28)
1087 def probe_kvm_iptables(self):
1088 (_,kvmbox) = self.all_node_infos()[0]
1089 TestSsh(kvmbox).run("iptables-save")
1093 def check_nodes_ping(self, timeout_seconds=30, period_seconds=10):
1094 class CompleterTaskPingNode(CompleterTask):
1095 def __init__(self, hostname):
1096 self.hostname = hostname
1097 def run(self, silent):
1098 command="ping -c 1 -w 1 {} >& /dev/null".format(self.hostname)
1099 return utils.system(command, silent=silent) == 0
1100 def failure_epilogue(self):
1101 print("Cannot ping node with name {}".format(self.hostname))
1102 timeout = timedelta(seconds = timeout_seconds)
1104 period = timedelta(seconds = period_seconds)
1105 node_infos = self.all_node_infos()
1106 tasks = [ CompleterTaskPingNode(h) for (h,_) in node_infos ]
1107 return Completer(tasks, message='ping_node').run(timeout, graceout, period)
1109 # ping node before we try to reach ssh, helpful for troubleshooting failing bootCDs
1110 def ping_node(self):
1112 return self.check_nodes_ping()
1114 def check_nodes_ssh(self, debug, timeout_minutes, silent_minutes, period_seconds=15):
1116 timeout = timedelta(minutes=timeout_minutes)
1117 graceout = timedelta(minutes=silent_minutes)
1118 period = timedelta(seconds=period_seconds)
1119 vservername = self.vservername
1122 completer_message = 'ssh_node_debug'
1123 local_key = "keys/{vservername}-debug.rsa".format(**locals())
1126 completer_message = 'ssh_node_boot'
1127 local_key = "keys/key_admin.rsa"
1128 utils.header("checking ssh access to nodes (expected in {} mode)".format(message))
1129 node_infos = self.all_node_infos()
1130 tasks = [ CompleterTaskNodeSsh(nodename, qemuname, local_key,
1131 boot_state=message, dry_run=self.options.dry_run) \
1132 for (nodename, qemuname) in node_infos ]
1133 return Completer(tasks, message=completer_message).run(timeout, graceout, period)
1135 def ssh_node_debug(self):
1136 "Tries to ssh into nodes in debug mode with the debug ssh key"
1137 return self.check_nodes_ssh(debug = True,
1138 timeout_minutes = self.ssh_node_debug_timeout,
1139 silent_minutes = self.ssh_node_debug_silent)
1141 def ssh_node_boot(self):
1142 "Tries to ssh into nodes in production mode with the root ssh key"
1143 return self.check_nodes_ssh(debug = False,
1144 timeout_minutes = self.ssh_node_boot_timeout,
1145 silent_minutes = self.ssh_node_boot_silent)
1147 def node_bmlogs(self):
1148 "Checks that there's a non-empty dir. /var/log/bm/raw"
1149 return utils.system(self.actual_command_in_guest("ls /var/log/bm/raw")) == 0
1152 def qemu_local_init(self): pass
1154 def bootcd(self): pass
1156 def qemu_local_config(self): pass
1158 def nodestate_reinstall(self): pass
1160 def nodestate_safeboot(self): pass
1162 def nodestate_boot(self): pass
1164 def nodestate_show(self): pass
1166 def qemu_export(self): pass
1168 ### check hooks : invoke scripts from hooks/{node,slice}
1169 def check_hooks_node(self):
1170 return self.locate_first_node().check_hooks()
1171 def check_hooks_sliver(self) :
1172 return self.locate_first_sliver().check_hooks()
1174 def check_hooks(self):
1175 "runs unit tests in the node and slice contexts - see hooks/{node,slice}"
1176 return self.check_hooks_node() and self.check_hooks_sliver()
1179 def do_check_initscripts(self):
1180 class CompleterTaskInitscript(CompleterTask):
1181 def __init__(self, test_sliver, stamp):
1182 self.test_sliver = test_sliver
1184 def actual_run(self):
1185 return self.test_sliver.check_initscript_stamp(self.stamp)
1187 return "initscript checker for {}".format(self.test_sliver.name())
1188 def failure_epilogue(self):
1189 print("initscript stamp {} not found in sliver {}"\
1190 .format(self.stamp, self.test_sliver.name()))
1193 for slice_spec in self.plc_spec['slices']:
1194 if 'initscriptstamp' not in slice_spec:
1196 stamp = slice_spec['initscriptstamp']
1197 slicename = slice_spec['slice_fields']['name']
1198 for nodename in slice_spec['nodenames']:
1199 print('nodename', nodename, 'slicename', slicename, 'stamp', stamp)
1200 site,node = self.locate_node(nodename)
1201 # xxx - passing the wrong site - probably harmless
1202 test_site = TestSite(self, site)
1203 test_slice = TestSlice(self, test_site, slice_spec)
1204 test_node = TestNode(self, test_site, node)
1205 test_sliver = TestSliver(self, test_node, test_slice)
1206 tasks.append(CompleterTaskInitscript(test_sliver, stamp))
1207 return Completer(tasks, message='check_initscripts').\
1208 run (timedelta(minutes=5), timedelta(minutes=4), timedelta(seconds=10))
1210 def check_initscripts(self):
1211 "check that the initscripts have triggered"
1212 return self.do_check_initscripts()
1214 def initscripts(self):
1215 "create initscripts with PLCAPI"
1216 for initscript in self.plc_spec['initscripts']:
1217 utils.pprint('Adding Initscript in plc {}'.format(self.plc_spec['name']), initscript)
1218 self.apiserver.AddInitScript(self.auth_root(), initscript['initscript_fields'])
1221 def delete_initscripts(self):
1222 "delete initscripts with PLCAPI"
1223 for initscript in self.plc_spec['initscripts']:
1224 initscript_name = initscript['initscript_fields']['name']
1225 print(('Attempting to delete {} in plc {}'.format(initscript_name, self.plc_spec['name'])))
1227 self.apiserver.DeleteInitScript(self.auth_root(), initscript_name)
1228 print(initscript_name, 'deleted')
1230 print('deletion went wrong - probably did not exist')
1235 "create slices with PLCAPI"
1236 return self.do_slices(action="add")
1238 def delete_slices(self):
1239 "delete slices with PLCAPI"
1240 return self.do_slices(action="delete")
1242 def fill_slices(self):
1243 "add nodes in slices with PLCAPI"
1244 return self.do_slices(action="fill")
1246 def empty_slices(self):
1247 "remove nodes from slices with PLCAPI"
1248 return self.do_slices(action="empty")
1250 def do_slices(self, action="add"):
1251 for slice in self.plc_spec['slices']:
1252 site_spec = self.locate_site(slice['sitename'])
1253 test_site = TestSite(self,site_spec)
1254 test_slice=TestSlice(self,test_site,slice)
1255 if action == "delete":
1256 test_slice.delete_slice()
1257 elif action == "fill":
1258 test_slice.add_nodes()
1259 elif action == "empty":
1260 test_slice.delete_nodes()
1262 test_slice.create_slice()
1265 @slice_mapper__tasks(20, 10, 15)
1266 def ssh_slice(self): pass
1267 @slice_mapper__tasks(20, 19, 15)
1268 def ssh_slice_off(self): pass
1269 @slice_mapper__tasks(1, 1, 15)
1270 def slice_fs_present(self): pass
1271 @slice_mapper__tasks(1, 1, 15)
1272 def slice_fs_deleted(self): pass
1274 # use another name so we can exclude/ignore it from the tests on the nightly command line
1275 def ssh_slice_again(self): return self.ssh_slice()
1276 # note that simply doing ssh_slice_again=ssh_slice would kind of work too
1277 # but for some reason the ignore-wrapping thing would not
1280 def ssh_slice_basics(self): pass
1282 def check_vsys_defaults(self): pass
1285 def keys_clear_known_hosts(self): pass
1287 def plcapi_urls(self):
1289 attempts to reach the PLCAPI with various forms for the URL
1291 return PlcapiUrlScanner(self.auth_root(), ip=self.vserverip).scan()
1293 def speed_up_slices(self):
1294 "tweak nodemanager cycle (wait time) to 30+/-10 s"
1295 return self._speed_up_slices (30, 10)
1296 def super_speed_up_slices(self):
1297 "dev mode: tweak nodemanager cycle (wait time) to 5+/-1 s"
1298 return self._speed_up_slices(5, 1)
1300 def _speed_up_slices(self, p, r):
1301 # create the template on the server-side
1302 template = "{}.nodemanager".format(self.name())
1303 with open(template,"w") as template_file:
1304 template_file.write('OPTIONS="-p {} -r {} -d"\n'.format(p, r))
1305 in_vm = "/var/www/html/PlanetLabConf/nodemanager"
1306 remote = "{}/{}".format(self.vm_root_in_host(), in_vm)
1307 self.test_ssh.copy_abs(template, remote)
1309 if not self.apiserver.GetConfFiles(self.auth_root(),
1310 {'dest' : '/etc/sysconfig/nodemanager'}):
1311 self.apiserver.AddConfFile(self.auth_root(),
1312 {'dest' : '/etc/sysconfig/nodemanager',
1313 'source' : 'PlanetLabConf/nodemanager',
1314 'postinstall_cmd' : 'service nm restart',})
1317 def debug_nodemanager(self):
1318 "sets verbose mode for nodemanager, and speeds up cycle even more (needs speed_up_slices first)"
1319 template = "{}.nodemanager".format(self.name())
1320 with open(template,"w") as template_file:
1321 template_file.write('OPTIONS="-p 10 -r 6 -v -d"\n')
1322 in_vm = "/var/www/html/PlanetLabConf/nodemanager"
1323 remote = "{}/{}".format(self.vm_root_in_host(), in_vm)
1324 self.test_ssh.copy_abs(template, remote)
1328 def qemu_start(self) : pass
1331 def qemu_timestamp(self) : pass
1333 # when a spec refers to a node possibly on another plc
1334 def locate_sliver_obj_cross(self, nodename, slicename, other_plcs):
1335 for plc in [ self ] + other_plcs:
1337 return plc.locate_sliver_obj(nodename, slicename)
1340 raise Exception("Cannot locate sliver {}@{} among all PLCs".format(nodename, slicename))
1342 # implement this one as a cross step so that we can take advantage of different nodes
1343 # in multi-plcs mode
1344 def cross_check_tcp(self, other_plcs):
1345 "check TCP connectivity between 2 slices (or in loopback if only one is defined)"
1346 if 'tcp_specs' not in self.plc_spec or not self.plc_spec['tcp_specs']:
1347 utils.header("check_tcp: no/empty config found")
1349 specs = self.plc_spec['tcp_specs']
1352 # first wait for the network to be up and ready from the slices
1353 class CompleterTaskNetworkReadyInSliver(CompleterTask):
1354 def __init__(self, test_sliver):
1355 self.test_sliver = test_sliver
1356 def actual_run(self):
1357 return self.test_sliver.check_tcp_ready(port = 9999)
1359 return "network ready checker for {}".format(self.test_sliver.name())
1360 def failure_epilogue(self):
1361 print("could not bind port from sliver {}".format(self.test_sliver.name()))
1365 managed_sliver_names = set()
1367 # locate the TestSliver instances involved, and cache them in the spec instance
1368 spec['s_sliver'] = self.locate_sliver_obj_cross(spec['server_node'], spec['server_slice'], other_plcs)
1369 spec['c_sliver'] = self.locate_sliver_obj_cross(spec['client_node'], spec['client_slice'], other_plcs)
1370 message = "Will check TCP between s={} and c={}"\
1371 .format(spec['s_sliver'].name(), spec['c_sliver'].name())
1372 if 'client_connect' in spec:
1373 message += " (using {})".format(spec['client_connect'])
1374 utils.header(message)
1375 # we need to check network presence in both slivers, but also
1376 # avoid to insert a sliver several times
1377 for sliver in [ spec['s_sliver'], spec['c_sliver'] ]:
1378 if sliver.name() not in managed_sliver_names:
1379 tasks.append(CompleterTaskNetworkReadyInSliver(sliver))
1380 # add this sliver's name in the set
1381 managed_sliver_names .update( {sliver.name()} )
1383 # wait for the netork to be OK in all server sides
1384 if not Completer(tasks, message='check for network readiness in slivers').\
1385 run(timedelta(seconds=30), timedelta(seconds=24), period=timedelta(seconds=5)):
1388 # run server and client
1392 # the issue here is that we have the server run in background
1393 # and so we have no clue if it took off properly or not
1394 # looks like in some cases it does not
1395 if not spec['s_sliver'].run_tcp_server(port, timeout=20):
1399 # idem for the client side
1400 # use nodename from located sliver, unless 'client_connect' is set
1401 if 'client_connect' in spec:
1402 destination = spec['client_connect']
1404 destination = spec['s_sliver'].test_node.name()
1405 if not spec['c_sliver'].run_tcp_client(destination, port):
1409 # painfully enough, we need to allow for some time as netflow might show up last
1410 def check_system_slice(self):
1411 "all nodes: check that a system slice is alive"
1412 # netflow currently not working in the lxc distro
1413 # drl not built at all in the wtx distro
1414 # if we find either of them we're happy
1415 return self.check_netflow() or self.check_drl()
1418 def check_netflow(self): return self._check_system_slice('netflow')
1419 def check_drl(self): return self._check_system_slice('drl')
1421 # we have the slices up already here, so it should not take too long
1422 def _check_system_slice(self, slicename, timeout_minutes=5, period_seconds=15):
1423 class CompleterTaskSystemSlice(CompleterTask):
1424 def __init__(self, test_node, dry_run):
1425 self.test_node = test_node
1426 self.dry_run = dry_run
1427 def actual_run(self):
1428 return self.test_node._check_system_slice(slicename, dry_run=self.dry_run)
1430 return "System slice {} @ {}".format(slicename, self.test_node.name())
1431 def failure_epilogue(self):
1432 print("COULD not find system slice {} @ {}".format(slicename, self.test_node.name()))
1433 timeout = timedelta(minutes=timeout_minutes)
1434 silent = timedelta(0)
1435 period = timedelta(seconds=period_seconds)
1436 tasks = [ CompleterTaskSystemSlice(test_node, self.options.dry_run) \
1437 for test_node in self.all_nodes() ]
1438 return Completer(tasks, message='_check_system_slice').run(timeout, silent, period)
1440 def plcsh_stress_test(self):
1441 "runs PLCAPI stress test, that checks Add/Update/Delete on all types - preserves contents"
1442 # install the stress-test in the plc image
1443 location = "/usr/share/plc_api/plcsh_stress_test.py"
1444 remote = "{}/{}".format(self.vm_root_in_host(), location)
1445 self.test_ssh.copy_abs("plcsh_stress_test.py", remote)
1447 command += " -- --check"
1448 if self.options.size == 1:
1449 command += " --tiny"
1450 return self.run_in_guest(command) == 0
1452 # populate runs the same utility without slightly different options
1453 # in particular runs with --preserve (dont cleanup) and without --check
1454 # also it gets run twice, once with the --foreign option for creating fake foreign entries
1456 def sfa_install_all(self):
1457 "yum install sfa sfa-plc sfa-sfatables sfa-client"
1458 return self.yum_install("sfa sfa-plc sfa-sfatables sfa-client")
1460 def sfa_install_core(self):
1462 return self.yum_install("sfa")
1464 def sfa_install_plc(self):
1465 "yum install sfa-plc"
1466 return self.yum_install("sfa-plc")
1468 def sfa_install_sfatables(self):
1469 "yum install sfa-sfatables"
1470 return self.yum_install("sfa-sfatables")
1472 # for some very odd reason, this sometimes fails with the following symptom
1473 # # yum install sfa-client
1474 # Setting up Install Process
1476 # Downloading Packages:
1477 # Running rpm_check_debug
1478 # Running Transaction Test
1479 # Transaction Test Succeeded
1480 # Running Transaction
1481 # Transaction couldn't start:
1482 # installing package sfa-client-2.1-7.onelab.2012.05.23.i686 needs 68KB on the / filesystem
1483 # [('installing package sfa-client-2.1-7.onelab.2012.05.23.i686 needs 68KB on the / filesystem', (9, '/', 69632L))]
1484 # even though in the same context I have
1485 # [2012.05.23--f14-32-sfastd1-1-vplc07] / # df -h
1486 # Filesystem Size Used Avail Use% Mounted on
1487 # /dev/hdv1 806G 264G 501G 35% /
1488 # none 16M 36K 16M 1% /tmp
1490 # so as a workaround, we first try yum install, and then invoke rpm on the cached rpm...
1491 def sfa_install_client(self):
1492 "yum install sfa-client"
1493 first_try = self.yum_install("sfa-client")
1496 utils.header("********** Regular yum failed - special workaround in place, 2nd chance")
1497 code, cached_rpm_path = \
1498 utils.output_of(self.actual_command_in_guest('find /var/cache/yum -name sfa-client\*.rpm'))
1499 utils.header("rpm_path=<<{}>>".format(rpm_path))
1501 self.run_in_guest("rpm -i {}".format(cached_rpm_path))
1502 return self.yum_check_installed("sfa-client")
1504 def sfa_dbclean(self):
1505 "thoroughly wipes off the SFA database"
1506 return self.run_in_guest("sfaadmin reg nuke") == 0 or \
1507 self.run_in_guest("sfa-nuke.py") == 0 or \
1508 self.run_in_guest("sfa-nuke-plc.py") == 0 or \
1509 self.run_in_guest("sfaadmin registry nuke") == 0
1511 def sfa_fsclean(self):
1512 "cleanup /etc/sfa/trusted_roots and /var/lib/sfa"
1513 self.run_in_guest("rm -rf /etc/sfa/trusted_roots /var/lib/sfa/authorities")
1516 def sfa_plcclean(self):
1517 "cleans the PLC entries that were created as a side effect of running the script"
1519 sfa_spec = self.plc_spec['sfa']
1521 for auth_sfa_spec in sfa_spec['auth_sfa_specs']:
1522 login_base = auth_sfa_spec['login_base']
1524 self.apiserver.DeleteSite(self.auth_root(),login_base)
1526 print("Site {} already absent from PLC db".format(login_base))
1528 for spec_name in ['pi_spec','user_spec']:
1529 user_spec = auth_sfa_spec[spec_name]
1530 username = user_spec['email']
1532 self.apiserver.DeletePerson(self.auth_root(),username)
1534 # this in fact is expected as sites delete their members
1535 #print "User {} already absent from PLC db".format(username)
1538 print("REMEMBER TO RUN sfa_import AGAIN")
1541 def sfa_uninstall(self):
1542 "uses rpm to uninstall sfa - ignore result"
1543 self.run_in_guest("rpm -e sfa sfa-sfatables sfa-client sfa-plc")
1544 self.run_in_guest("rm -rf /var/lib/sfa")
1545 self.run_in_guest("rm -rf /etc/sfa")
1546 self.run_in_guest("rm -rf /var/log/sfa_access.log /var/log/sfa_import_plc.log /var/log/sfa.daemon")
1548 self.run_in_guest("rpm -e --noscripts sfa-plc")
1551 ### run unit tests for SFA
1552 # NOTE: for some reason on f14/i386, yum install sfa-tests fails for no reason
1553 # Running Transaction
1554 # Transaction couldn't start:
1555 # installing package sfa-tests-1.0-21.onelab.i686 needs 204KB on the / filesystem
1556 # [('installing package sfa-tests-1.0-21.onelab.i686 needs 204KB on the / filesystem', (9, '/', 208896L))]
1557 # no matter how many Gbs are available on the testplc
1558 # could not figure out what's wrong, so...
1559 # if the yum install phase fails, consider the test is successful
1560 # other combinations will eventually run it hopefully
1561 def sfa_utest(self):
1562 "yum install sfa-tests and run SFA unittests"
1563 self.run_in_guest("yum -y install sfa-tests")
1564 # failed to install - forget it
1565 if self.run_in_guest("rpm -q sfa-tests") != 0:
1566 utils.header("WARNING: SFA unit tests failed to install, ignoring")
1568 return self.run_in_guest("/usr/share/sfa/tests/testAll.py") == 0
1572 dirname = "conf.{}".format(self.plc_spec['name'])
1573 if not os.path.isdir(dirname):
1574 utils.system("mkdir -p {}".format(dirname))
1575 if not os.path.isdir(dirname):
1576 raise Exception("Cannot create config dir for plc {}".format(self.name()))
1579 def conffile(self, filename):
1580 return "{}/{}".format(self.confdir(), filename)
1581 def confsubdir(self, dirname, clean, dry_run=False):
1582 subdirname = "{}/{}".format(self.confdir(), dirname)
1584 utils.system("rm -rf {}".format(subdirname))
1585 if not os.path.isdir(subdirname):
1586 utils.system("mkdir -p {}".format(subdirname))
1587 if not dry_run and not os.path.isdir(subdirname):
1588 raise "Cannot create config subdir {} for plc {}".format(dirname, self.name())
1591 def conffile_clean(self, filename):
1592 filename=self.conffile(filename)
1593 return utils.system("rm -rf {}".format(filename))==0
1596 def sfa_configure(self):
1597 "run sfa-config-tty"
1598 tmpname = self.conffile("sfa-config-tty")
1599 with open(tmpname,'w') as fileconf:
1600 for (var,value) in self.plc_spec['sfa']['settings'].items():
1601 fileconf.write('e {}\n{}\n'.format(var, value))
1602 fileconf.write('w\n')
1603 fileconf.write('R\n')
1604 fileconf.write('q\n')
1605 utils.system('cat {}'.format(tmpname))
1606 self.run_in_guest_piped('cat {}'.format(tmpname), 'sfa-config-tty')
1609 def aggregate_xml_line(self):
1610 port = self.plc_spec['sfa']['neighbours-port']
1611 return '<aggregate addr="{}" hrn="{}" port="{}"/>'\
1612 .format(self.vserverip, self.plc_spec['sfa']['settings']['SFA_REGISTRY_ROOT_AUTH'], port)
1614 def registry_xml_line(self):
1615 return '<registry addr="{}" hrn="{}" port="12345"/>'\
1616 .format(self.vserverip, self.plc_spec['sfa']['settings']['SFA_REGISTRY_ROOT_AUTH'])
1619 # a cross step that takes all other plcs in argument
1620 def cross_sfa_configure(self, other_plcs):
1621 "writes aggregates.xml and registries.xml that point to all other PLCs in the test"
1622 # of course with a single plc, other_plcs is an empty list
1625 agg_fname = self.conffile("agg.xml")
1626 with open(agg_fname,"w") as out:
1627 out.write("<aggregates>{}</aggregates>\n"\
1628 .format(" ".join([ plc.aggregate_xml_line() for plc in other_plcs ])))
1629 utils.header("(Over)wrote {}".format(agg_fname))
1630 reg_fname=self.conffile("reg.xml")
1631 with open(reg_fname,"w") as out:
1632 out.write("<registries>{}</registries>\n"\
1633 .format(" ".join([ plc.registry_xml_line() for plc in other_plcs ])))
1634 utils.header("(Over)wrote {}".format(reg_fname))
1635 return self.test_ssh.copy_abs(agg_fname,
1636 '/{}/etc/sfa/aggregates.xml'.format(self.vm_root_in_host())) == 0 \
1637 and self.test_ssh.copy_abs(reg_fname,
1638 '/{}/etc/sfa/registries.xml'.format(self.vm_root_in_host())) == 0
1640 def sfa_import(self):
1641 "use sfaadmin to import from plc"
1642 auth = self.plc_spec['sfa']['settings']['SFA_REGISTRY_ROOT_AUTH']
1643 return self.run_in_guest('sfaadmin reg import_registry') == 0
1645 def sfa_start(self):
1647 return self.start_service('sfa')
1650 def sfi_configure(self):
1651 "Create /root/sfi on the plc side for sfi client configuration"
1652 if self.options.dry_run:
1653 utils.header("DRY RUN - skipping step")
1655 sfa_spec = self.plc_spec['sfa']
1656 # cannot use auth_sfa_mapper to pass dir_name
1657 for slice_spec in self.plc_spec['sfa']['auth_sfa_specs']:
1658 test_slice = TestAuthSfa(self, slice_spec)
1659 dir_basename = os.path.basename(test_slice.sfi_path())
1660 dir_name = self.confsubdir("dot-sfi/{}".format(dir_basename),
1661 clean=True, dry_run=self.options.dry_run)
1662 test_slice.sfi_configure(dir_name)
1663 # push into the remote /root/sfi area
1664 location = test_slice.sfi_path()
1665 remote = "{}/{}".format(self.vm_root_in_host(), location)
1666 self.test_ssh.mkdir(remote, abs=True)
1667 # need to strip last level or remote otherwise we get an extra dir level
1668 self.test_ssh.copy_abs(dir_name, os.path.dirname(remote), recursive=True)
1672 def sfi_clean(self):
1673 "clean up /root/sfi on the plc side"
1674 self.run_in_guest("rm -rf /root/sfi")
1677 def sfa_rspec_empty(self):
1678 "expose a static empty rspec (ships with the tests module) in the sfi directory"
1679 filename = "empty-rspec.xml"
1681 for slice_spec in self.plc_spec['sfa']['auth_sfa_specs']:
1682 test_slice = TestAuthSfa(self, slice_spec)
1683 in_vm = test_slice.sfi_path()
1684 remote = "{}/{}".format(self.vm_root_in_host(), in_vm)
1685 if self.test_ssh.copy_abs(filename, remote) !=0:
1690 def sfa_register_site(self): pass
1692 def sfa_register_pi(self): pass
1694 def sfa_register_user(self): pass
1696 def sfa_update_user(self): pass
1698 def sfa_register_slice(self): pass
1700 def sfa_renew_slice(self): pass
1702 def sfa_get_expires(self): pass
1704 def sfa_discover(self): pass
1706 def sfa_rspec(self): pass
1708 def sfa_allocate(self): pass
1710 def sfa_allocate_empty(self): pass
1712 def sfa_provision(self): pass
1714 def sfa_provision_empty(self): pass
1716 def sfa_check_slice_plc(self): pass
1718 def sfa_check_slice_plc_empty(self): pass
1720 def sfa_update_slice(self): pass
1722 def sfa_remove_user_from_slice(self): pass
1724 def sfa_insert_user_in_slice(self): pass
1726 def sfi_list(self): pass
1728 def sfi_show_site(self): pass
1730 def sfi_show_slice(self): pass
1732 def sfi_show_slice_researchers(self): pass
1734 def ssh_slice_sfa(self): pass
1736 def sfa_delete_user(self): pass
1738 def sfa_delete_slice(self): pass
1742 return self.stop_service('sfa')
1745 "creates random entries in the PLCAPI"
1746 # install the stress-test in the plc image
1747 location = "/usr/share/plc_api/plcsh_stress_test.py"
1748 remote = "{}/{}".format(self.vm_root_in_host(), location)
1749 self.test_ssh.copy_abs("plcsh_stress_test.py", remote)
1751 command += " -- --preserve --short-names"
1752 local = (self.run_in_guest(command) == 0);
1753 # second run with --foreign
1754 command += ' --foreign'
1755 remote = (self.run_in_guest(command) == 0);
1756 return local and remote
1759 ####################
1761 def bonding_init_partial(self): pass
1764 def bonding_add_yum(self): pass
1767 def bonding_install_rpms(self): pass
1769 ####################
1771 def gather_logs(self):
1772 "gets all possible logs from plc's/qemu node's/slice's for future reference"
1773 # (1.a) get the plc's /var/log/ and store it locally in logs/myplc.var-log.<plcname>/*
1774 # (1.b) get the plc's /var/lib/pgsql/data/pg_log/ -> logs/myplc.pgsql-log.<plcname>/*
1775 # (1.c) get the plc's /root/sfi -> logs/sfi.<plcname>/
1776 # (2) get all the nodes qemu log and store it as logs/node.qemu.<node>.log
1777 # (3) get the nodes /var/log and store is as logs/node.var-log.<node>/*
1778 # (4) as far as possible get the slice's /var/log as logs/sliver.var-log.<sliver>/*
1780 print("-------------------- TestPlc.gather_logs : PLC's /var/log")
1781 self.gather_var_logs()
1783 print("-------------------- TestPlc.gather_logs : PLC's /var/lib/psql/data/pg_log/")
1784 self.gather_pgsql_logs()
1786 print("-------------------- TestPlc.gather_logs : PLC's /root/sfi/")
1787 self.gather_root_sfi()
1789 print("-------------------- TestPlc.gather_logs : nodes's QEMU logs")
1790 for site_spec in self.plc_spec['sites']:
1791 test_site = TestSite(self,site_spec)
1792 for node_spec in site_spec['nodes']:
1793 test_node = TestNode(self, test_site, node_spec)
1794 test_node.gather_qemu_logs()
1796 print("-------------------- TestPlc.gather_logs : nodes's /var/log")
1797 self.gather_nodes_var_logs()
1799 print("-------------------- TestPlc.gather_logs : sample sliver's /var/log")
1800 self.gather_slivers_var_logs()
1803 def gather_slivers_var_logs(self):
1804 for test_sliver in self.all_sliver_objs():
1805 remote = test_sliver.tar_var_logs()
1806 utils.system("mkdir -p logs/sliver.var-log.{}".format(test_sliver.name()))
1807 command = remote + " | tar -C logs/sliver.var-log.{} -xf -".format(test_sliver.name())
1808 utils.system(command)
1811 def gather_var_logs(self):
1812 utils.system("mkdir -p logs/myplc.var-log.{}".format(self.name()))
1813 to_plc = self.actual_command_in_guest("tar -C /var/log/ -cf - .")
1814 command = to_plc + "| tar -C logs/myplc.var-log.{} -xf -".format(self.name())
1815 utils.system(command)
1816 command = "chmod a+r,a+x logs/myplc.var-log.{}/httpd".format(self.name())
1817 utils.system(command)
1819 def gather_pgsql_logs(self):
1820 utils.system("mkdir -p logs/myplc.pgsql-log.{}".format(self.name()))
1821 to_plc = self.actual_command_in_guest("tar -C /var/lib/pgsql/data/pg_log/ -cf - .")
1822 command = to_plc + "| tar -C logs/myplc.pgsql-log.{} -xf -".format(self.name())
1823 utils.system(command)
1825 def gather_root_sfi(self):
1826 utils.system("mkdir -p logs/sfi.{}".format(self.name()))
1827 to_plc = self.actual_command_in_guest("tar -C /root/sfi/ -cf - .")
1828 command = to_plc + "| tar -C logs/sfi.{} -xf -".format(self.name())
1829 utils.system(command)
1831 def gather_nodes_var_logs(self):
1832 for site_spec in self.plc_spec['sites']:
1833 test_site = TestSite(self, site_spec)
1834 for node_spec in site_spec['nodes']:
1835 test_node = TestNode(self, test_site, node_spec)
1836 test_ssh = TestSsh(test_node.name(), key="keys/key_admin.rsa")
1837 command = test_ssh.actual_command("tar -C /var/log -cf - .")
1838 command = command + "| tar -C logs/node.var-log.{} -xf -".format(test_node.name())
1839 utils.system("mkdir -p logs/node.var-log.{}".format(test_node.name()))
1840 utils.system(command)
1843 # returns the filename to use for sql dump/restore, using options.dbname if set
1844 def dbfile(self, database):
1845 # uses options.dbname if it is found
1847 name = self.options.dbname
1848 if not isinstance(name, str):
1854 return "/root/{}-{}.sql".format(database, name)
1856 def plc_db_dump(self):
1857 'dump the planetlab5 DB in /root in the PLC - filename has time'
1858 dump=self.dbfile("planetab5")
1859 self.run_in_guest('pg_dump -U pgsqluser planetlab5 -f '+ dump)
1860 utils.header('Dumped planetlab5 database in {}'.format(dump))
1863 def plc_db_restore(self):
1864 'restore the planetlab5 DB - looks broken, but run -n might help'
1865 dump = self.dbfile("planetab5")
1866 ##stop httpd service
1867 self.run_in_guest('service httpd stop')
1868 # xxx - need another wrapper
1869 self.run_in_guest_piped('echo drop database planetlab5', 'psql --user=pgsqluser template1')
1870 self.run_in_guest('createdb -U postgres --encoding=UNICODE --owner=pgsqluser planetlab5')
1871 self.run_in_guest('psql -U pgsqluser planetlab5 -f ' + dump)
1872 ##starting httpd service
1873 self.run_in_guest('service httpd start')
1875 utils.header('Database restored from ' + dump)
1878 def create_ignore_steps():
1879 for step in TestPlc.default_steps + TestPlc.other_steps:
1880 # default step can have a plc qualifier
1882 step, qualifier = step.split('@')
1883 # or be defined as forced or ignored by default
1884 for keyword in ['_ignore','_force']:
1885 if step.endswith(keyword):
1886 step=step.replace(keyword,'')
1887 if step == SEP or step == SEPSFA :
1889 method = getattr(TestPlc,step)
1890 name = step + '_ignore'
1891 wrapped = ignore_result(method)
1892 # wrapped.__doc__ = method.__doc__ + " (run in ignore-result mode)"
1893 setattr(TestPlc, name, wrapped)
1896 # def ssh_slice_again_ignore (self): pass
1898 # def check_initscripts_ignore (self): pass
1900 def standby_1_through_20(self):
1901 """convenience function to wait for a specified number of minutes"""
1904 def standby_1(): pass
1906 def standby_2(): pass
1908 def standby_3(): pass
1910 def standby_4(): pass
1912 def standby_5(): pass
1914 def standby_6(): pass
1916 def standby_7(): pass
1918 def standby_8(): pass
1920 def standby_9(): pass
1922 def standby_10(): pass
1924 def standby_11(): pass
1926 def standby_12(): pass
1928 def standby_13(): pass
1930 def standby_14(): pass
1932 def standby_15(): pass
1934 def standby_16(): pass
1936 def standby_17(): pass
1938 def standby_18(): pass
1940 def standby_19(): pass
1942 def standby_20(): pass
1944 # convenience for debugging the test logic
1945 def yes(self): return True
1946 def no(self): return False
1947 def fail(self): return False