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_cleanlog', SEP,
166 'qemu_start', 'qemu_timestamp', 'qemu_nodefamily', SEP,
167 'sfa_install_all', 'sfa_configure', 'cross_sfa_configure', 'sfa_start', 'sfa_import', SEPSFA,
168 'sfi_configure@1', 'sfa_register_site@1','sfa_register_pi@1', SEPSFA,
169 'sfa_register_user@1', 'sfa_update_user@1', 'sfa_register_slice@1', 'sfa_renew_slice@1', SEPSFA,
170 'sfa_remove_user_from_slice@1','sfi_show_slice_researchers@1',
171 'sfa_insert_user_in_slice@1','sfi_show_slice_researchers@1', SEPSFA,
172 'sfa_discover@1', 'sfa_rspec@1', SEPSFA,
173 'sfa_allocate@1', 'sfa_provision@1', 'sfa_describe@1', SEPSFA,
174 'sfa_check_slice_plc@1', 'sfa_update_slice@1', SEPSFA,
175 'sfi_list@1', 'sfi_show_site@1', 'sfa_utest@1', SEPSFA,
176 # we used to run plcsh_stress_test, and then ssh_node_debug and ssh_node_boot
177 # but as the stress test might take a while, we sometimes missed the debug mode..
178 'probe_kvm_iptables',
179 'ping_node', 'ssh_node_debug', 'plcsh_stress_test@1', SEP,
180 'ssh_node_boot', 'node_bmlogs', 'ssh_slice', 'ssh_slice_basics', 'check_initscripts', SEP,
181 'ssh_slice_sfa@1', SEPSFA,
182 'sfa_rspec_empty@1', 'sfa_allocate_empty@1', 'sfa_provision_empty@1','sfa_check_slice_plc_empty@1', SEPSFA,
183 'sfa_delete_slice@1', 'sfa_delete_user@1', SEPSFA,
184 'cross_check_tcp@1', 'check_system_slice', SEP,
185 # for inspecting the slice while it runs the first time
187 # check slices are turned off properly
189 'empty_slices', 'ssh_slice_off', 'slice_fs_deleted_ignore', SEP,
190 # check they are properly re-created with the same name
191 'fill_slices', 'ssh_slice_again', SEP,
192 'gather_logs_force', SEP,
195 'export', 'show_boxes', 'super_speed_up_slices', SEP,
196 'check_hooks', 'plc_stop', 'plcvm_start', 'plcvm_stop', SEP,
197 'delete_initscripts', 'delete_nodegroups','delete_all_sites', SEP,
198 'delete_sites', 'delete_nodes', 'delete_slices', 'keys_clean', SEP,
199 'delete_leases', 'list_leases', SEP,
201 'nodestate_show','nodestate_safeboot','nodestate_boot', 'nodestate_upgrade', SEP,
202 'nodedistro_show','nodedistro_f14','nodedistro_f18', SEP,
203 'nodedistro_f20', 'nodedistro_f21','nodedistro_f22', SEP,
204 'qemu_list_all', 'qemu_list_mine', 'qemu_kill_all', SEP,
205 'sfa_install_core', 'sfa_install_sfatables', 'sfa_install_plc', 'sfa_install_client', SEPSFA,
206 'sfa_plcclean', 'sfa_dbclean', 'sfa_stop','sfa_uninstall', 'sfi_clean', SEPSFA,
207 'sfa_get_expires', SEPSFA,
208 'plc_db_dump' , 'plc_db_restore', SEP,
209 'check_netflow','check_drl', SEP,
210 'debug_nodemanager', 'slice_fs_present', SEP,
211 'standby_1_through_20','yes','no',SEP,
212 'install_syslinux6', 'bonding_builds', 'bonding_nodes', SEP,
214 default_bonding_steps = [
215 'bonding_init_partial',
217 'bonding_install_rpms', SEP,
221 def printable_steps(list):
222 single_line = " ".join(list) + " "
223 return single_line.replace(" "+SEP+" ", " \\\n").replace(" "+SEPSFA+" ", " \\\n")
225 def valid_step(step):
226 return step != SEP and step != SEPSFA
228 # turn off the sfa-related steps when build has skipped SFA
229 # this was originally for centos5 but is still valid
230 # for up to f12 as recent SFAs with sqlalchemy won't build before f14
232 def _has_sfa_cached(rpms_url):
233 if os.path.isfile(has_sfa_cache_filename):
234 with open(has_sfa_cache_filename) as cache:
235 cached = cache.read() == "yes"
236 utils.header("build provides SFA (cached):{}".format(cached))
238 # warning, we're now building 'sface' so let's be a bit more picky
239 # full builds are expected to return with 0 here
240 utils.header("Checking if build provides SFA package...")
241 retcod = utils.system("curl --silent {}/ | grep -q sfa-".format(rpms_url)) == 0
242 encoded = 'yes' if retcod else 'no'
243 with open(has_sfa_cache_filename,'w') as cache:
248 def check_whether_build_has_sfa(rpms_url):
249 has_sfa = TestPlc._has_sfa_cached(rpms_url)
251 utils.header("build does provide SFA")
253 # move all steps containing 'sfa' from default_steps to other_steps
254 utils.header("SFA package not found - removing steps with sfa or sfi")
255 sfa_steps = [ step for step in TestPlc.default_steps
256 if step.find('sfa') >= 0 or step.find("sfi") >= 0 ]
257 TestPlc.other_steps += sfa_steps
258 for step in sfa_steps:
259 TestPlc.default_steps.remove(step)
261 def __init__(self, plc_spec, options):
262 self.plc_spec = plc_spec
263 self.options = options
264 self.test_ssh = TestSsh(self.plc_spec['host_box'], self.options.buildname)
265 self.vserverip = plc_spec['vserverip']
266 self.vservername = plc_spec['vservername']
267 self.vplchostname = self.vservername.split('-')[-1]
268 self.url = "https://{}:443/PLCAPI/".format(plc_spec['vserverip'])
269 self.apiserver = TestApiserver(self.url, options.dry_run)
270 (self.ssh_node_boot_timeout, self.ssh_node_boot_silent) = plc_spec['ssh_node_boot_timers']
271 (self.ssh_node_debug_timeout, self.ssh_node_debug_silent) = plc_spec['ssh_node_debug_timers']
273 def has_addresses_api(self):
274 return self.apiserver.has_method('AddIpAddress')
277 name = self.plc_spec['name']
278 return "{}.{}".format(name,self.vservername)
281 return self.plc_spec['host_box']
284 return self.test_ssh.is_local()
286 # define the API methods on this object through xmlrpc
287 # would help, but not strictly necessary
291 def actual_command_in_guest(self,command, backslash=False):
292 raw1 = self.host_to_guest(command)
293 raw2 = self.test_ssh.actual_command(raw1, dry_run=self.options.dry_run, backslash=backslash)
296 def start_guest(self):
297 return utils.system(self.test_ssh.actual_command(self.start_guest_in_host(),
298 dry_run=self.options.dry_run))
300 def stop_guest(self):
301 return utils.system(self.test_ssh.actual_command(self.stop_guest_in_host(),
302 dry_run=self.options.dry_run))
304 def run_in_guest(self, command, backslash=False):
305 raw = self.actual_command_in_guest(command, backslash)
306 return utils.system(raw)
308 def run_in_host(self,command):
309 return self.test_ssh.run_in_buildname(command, dry_run=self.options.dry_run)
311 # backslashing turned out so awful at some point that I've turned off auto-backslashing
312 # see e.g. plc_start esp. the version for f14
313 #command gets run in the plc's vm
314 def host_to_guest(self, command):
315 ssh_leg = TestSsh(self.vplchostname)
316 return ssh_leg.actual_command(command, keep_stdin=True)
318 # this /vservers thing is legacy...
319 def vm_root_in_host(self):
320 return "/vservers/{}/".format(self.vservername)
322 def vm_timestamp_path(self):
323 return "/vservers/{}/{}.timestamp".format(self.vservername, self.vservername)
325 #start/stop the vserver
326 def start_guest_in_host(self):
327 return "virsh -c lxc:/// start {}".format(self.vservername)
329 def stop_guest_in_host(self):
330 return "virsh -c lxc:/// destroy {}".format(self.vservername)
333 def run_in_guest_piped(self,local,remote):
334 return utils.system(local+" | "+self.test_ssh.actual_command(self.host_to_guest(remote),
337 def yum_check_installed(self, rpms):
338 if isinstance(rpms, list):
340 return self.run_in_guest("rpm -q {}".format(rpms)) == 0
342 # does a yum install in the vs, ignore yum retcod, check with rpm
343 def yum_install(self, rpms):
344 if isinstance(rpms, list):
346 self.run_in_guest("yum -y install {}".format(rpms))
347 # yum-complete-transaction comes with yum-utils, that is in vtest.pkgs
348 self.run_in_guest("yum-complete-transaction -y")
349 return self.yum_check_installed(rpms)
352 return {'Username' : self.plc_spec['settings']['PLC_ROOT_USER'],
353 'AuthMethod' : 'password',
354 'AuthString' : self.plc_spec['settings']['PLC_ROOT_PASSWORD'],
355 'Role' : self.plc_spec['role'],
358 def locate_site(self,sitename):
359 for site in self.plc_spec['sites']:
360 if site['site_fields']['name'] == sitename:
362 if site['site_fields']['login_base'] == sitename:
364 raise Exception("Cannot locate site {}".format(sitename))
366 def locate_node(self, nodename):
367 for site in self.plc_spec['sites']:
368 for node in site['nodes']:
369 if node['name'] == nodename:
371 raise Exception("Cannot locate node {}".format(nodename))
373 def locate_hostname(self, hostname):
374 for site in self.plc_spec['sites']:
375 for node in site['nodes']:
376 if node['node_fields']['hostname'] == hostname:
378 raise Exception("Cannot locate hostname {}".format(hostname))
380 def locate_key(self, key_name):
381 for key in self.plc_spec['keys']:
382 if key['key_name'] == key_name:
384 raise Exception("Cannot locate key {}".format(key_name))
386 def locate_private_key_from_key_names(self, key_names):
387 # locate the first avail. key
389 for key_name in key_names:
390 key_spec = self.locate_key(key_name)
391 test_key = TestKey(self,key_spec)
392 publickey = test_key.publicpath()
393 privatekey = test_key.privatepath()
394 if os.path.isfile(publickey) and os.path.isfile(privatekey):
401 def locate_slice(self, slicename):
402 for slice in self.plc_spec['slices']:
403 if slice['slice_fields']['name'] == slicename:
405 raise Exception("Cannot locate slice {}".format(slicename))
407 def all_sliver_objs(self):
409 for slice_spec in self.plc_spec['slices']:
410 slicename = slice_spec['slice_fields']['name']
411 for nodename in slice_spec['nodenames']:
412 result.append(self.locate_sliver_obj(nodename, slicename))
415 def locate_sliver_obj(self, nodename, slicename):
416 site,node = self.locate_node(nodename)
417 slice = self.locate_slice(slicename)
419 test_site = TestSite(self, site)
420 test_node = TestNode(self, test_site, node)
421 # xxx the slice site is assumed to be the node site - mhh - probably harmless
422 test_slice = TestSlice(self, test_site, slice)
423 return TestSliver(self, test_node, test_slice)
425 def locate_first_node(self):
426 nodename = self.plc_spec['slices'][0]['nodenames'][0]
427 site,node = self.locate_node(nodename)
428 test_site = TestSite(self, site)
429 test_node = TestNode(self, test_site, node)
432 def locate_first_sliver(self):
433 slice_spec = self.plc_spec['slices'][0]
434 slicename = slice_spec['slice_fields']['name']
435 nodename = slice_spec['nodenames'][0]
436 return self.locate_sliver_obj(nodename,slicename)
438 # all different hostboxes used in this plc
439 def get_BoxNodes(self):
440 # maps on sites and nodes, return [ (host_box,test_node) ]
442 for site_spec in self.plc_spec['sites']:
443 test_site = TestSite(self,site_spec)
444 for node_spec in site_spec['nodes']:
445 test_node = TestNode(self, test_site, node_spec)
446 if not test_node.is_real():
447 tuples.append( (test_node.host_box(),test_node) )
448 # transform into a dict { 'host_box' -> [ test_node .. ] }
450 for (box,node) in tuples:
451 if box not in result:
454 result[box].append(node)
457 # a step for checking this stuff
458 def show_boxes(self):
459 'print summary of nodes location'
460 for box,nodes in self.get_BoxNodes().items():
461 print(box,":"," + ".join( [ node.name() for node in nodes ] ))
464 # make this a valid step
465 def qemu_kill_all(self):
466 'kill all qemu instances on the qemu boxes involved by this setup'
467 # this is the brute force version, kill all qemus on that host box
468 for (box,nodes) in self.get_BoxNodes().items():
469 # pass the first nodename, as we don't push template-qemu on testboxes
470 nodedir = nodes[0].nodedir()
471 TestBoxQemu(box, self.options.buildname).qemu_kill_all(nodedir)
474 # make this a valid step
475 def qemu_list_all(self):
476 'list all qemu instances on the qemu boxes involved by this setup'
477 for box,nodes in self.get_BoxNodes().items():
478 # this is the brute force version, kill all qemus on that host box
479 TestBoxQemu(box, self.options.buildname).qemu_list_all()
482 # kill only the qemus related to this test
483 def qemu_list_mine(self):
484 'list qemu instances for our nodes'
485 for (box,nodes) in self.get_BoxNodes().items():
486 # the fine-grain version
491 # kill only the qemus related to this test
492 def qemu_clean_mine(self):
493 'cleanup (rm -rf) qemu instances for our nodes'
494 for box,nodes in self.get_BoxNodes().items():
495 # the fine-grain version
500 # kill only the right qemus
501 def qemu_kill_mine(self):
502 'kill the qemu instances for our nodes'
503 for box,nodes in self.get_BoxNodes().items():
504 # the fine-grain version
509 #################### display config
511 "show test configuration after localization"
516 # uggly hack to make sure 'run export' only reports about the 1st plc
517 # to avoid confusion - also we use 'inri_slice1' in various aliases..
520 "print cut'n paste-able stuff to export env variables to your shell"
521 # guess local domain from hostname
522 if TestPlc.exported_id > 1:
523 print("export GUESTHOSTNAME{:d}={}".format(TestPlc.exported_id, self.plc_spec['vservername']))
525 TestPlc.exported_id += 1
526 domain = socket.gethostname().split('.',1)[1]
527 fqdn = "{}.{}".format(self.plc_spec['host_box'], domain)
528 print("export BUILD={}".format(self.options.buildname))
529 print("export PLCHOSTLXC={}".format(fqdn))
530 print("export GUESTNAME={}".format(self.vservername))
531 print("export GUESTHOSTNAME={}.{}".format(self.vplchostname, domain))
532 # find hostname of first node
533 hostname, qemubox = self.all_node_infos()[0]
534 print("export KVMHOST={}.{}".format(qemubox, domain))
535 print("export NODE={}".format(hostname))
539 always_display_keys=['PLC_WWW_HOST', 'nodes', 'sites']
540 def show_pass(self, passno):
541 for (key,val) in self.plc_spec.items():
542 if not self.options.verbose and key not in TestPlc.always_display_keys:
547 self.display_site_spec(site)
548 for node in site['nodes']:
549 self.display_node_spec(node)
550 elif key == 'initscripts':
551 for initscript in val:
552 self.display_initscript_spec(initscript)
553 elif key == 'slices':
555 self.display_slice_spec(slice)
558 self.display_key_spec(key)
560 if key not in ['sites', 'initscripts', 'slices', 'keys']:
561 print('+ ', key, ':', val)
563 def display_site_spec(self, site):
564 print('+ ======== site', site['site_fields']['name'])
565 for k,v in site.items():
566 if not self.options.verbose and k not in TestPlc.always_display_keys:
570 print('+ ','nodes : ', end=' ')
572 print(node['node_fields']['hostname'],'', end=' ')
576 print('+ users : ', end=' ')
578 print(user['name'],'', end=' ')
580 elif k == 'site_fields':
581 print('+ login_base', ':', v['login_base'])
582 elif k == 'address_fields':
588 def display_initscript_spec(self, initscript):
589 print('+ ======== initscript', initscript['initscript_fields']['name'])
591 def display_key_spec(self, key):
592 print('+ ======== key', key['key_name'])
594 def display_slice_spec(self, slice):
595 print('+ ======== slice', slice['slice_fields']['name'])
596 for k,v in slice.items():
599 print('+ nodes : ', end=' ')
601 print(nodename,'', end=' ')
603 elif k == 'usernames':
605 print('+ users : ', end=' ')
607 print(username,'', end=' ')
609 elif k == 'slice_fields':
610 print('+ fields',':', end=' ')
611 print('max_nodes=',v['max_nodes'], end=' ')
616 def display_node_spec(self, node):
617 print("+ node={} host_box={}".format(node['name'], node['host_box']), end=' ')
618 print("hostname=", node['node_fields']['hostname'], end=' ')
619 print("ip=", node['interface_fields']['ip'])
620 if self.options.verbose:
621 utils.pprint("node details", node, depth=3)
623 # another entry point for just showing the boxes involved
624 def display_mapping(self):
625 TestPlc.display_mapping_plc(self.plc_spec)
629 def display_mapping_plc(plc_spec):
630 print('+ MyPLC',plc_spec['name'])
631 # WARNING this would not be right for lxc-based PLC's - should be harmless though
632 print('+\tvserver address = root@{}:/vservers/{}'.format(plc_spec['host_box'], plc_spec['vservername']))
633 print('+\tIP = {}/{}'.format(plc_spec['settings']['PLC_API_HOST'], plc_spec['vserverip']))
634 for site_spec in plc_spec['sites']:
635 for node_spec in site_spec['nodes']:
636 TestPlc.display_mapping_node(node_spec)
639 def display_mapping_node(node_spec):
640 print('+ NODE {}'.format(node_spec['name']))
641 print('+\tqemu box {}'.format(node_spec['host_box']))
642 print('+\thostname={}'.format(node_spec['node_fields']['hostname']))
644 # write a timestamp in /vservers/<>.timestamp
645 # cannot be inside the vserver, that causes vserver .. build to cough
646 def plcvm_timestamp(self):
647 "Create a timestamp to remember creation date for this plc"
648 now = int(time.time())
649 # TODO-lxc check this one
650 # a first approx. is to store the timestamp close to the VM root like vs does
651 stamp_path = self.vm_timestamp_path()
652 stamp_dir = os.path.dirname(stamp_path)
653 utils.system(self.test_ssh.actual_command("mkdir -p {}".format(stamp_dir)))
654 return utils.system(self.test_ssh.actual_command("echo {:d} > {}".format(now, stamp_path))) == 0
656 # this is called inconditionnally at the beginning of the test sequence
657 # just in case this is a rerun, so if the vm is not running it's fine
658 def plcvm_delete(self):
659 "vserver delete the test myplc"
660 stamp_path = self.vm_timestamp_path()
661 self.run_in_host("rm -f {}".format(stamp_path))
662 self.run_in_host("virsh -c lxc:// destroy {}".format(self.vservername))
663 self.run_in_host("virsh -c lxc:// undefine {}".format(self.vservername))
664 self.run_in_host("rm -fr /vservers/{}".format(self.vservername))
668 # historically the build was being fetched by the tests
669 # now the build pushes itself as a subdir of the tests workdir
670 # so that the tests do not have to worry about extracting the build (svn, git, or whatever)
671 def plcvm_create(self):
672 "vserver creation (no install done)"
673 # push the local build/ dir to the testplc box
675 # a full path for the local calls
676 build_dir = os.path.dirname(sys.argv[0])
677 # sometimes this is empty - set to "." in such a case
680 build_dir += "/build"
682 # use a standard name - will be relative to remote buildname
684 # remove for safety; do *not* mkdir first, otherwise we end up with build/build/
685 self.test_ssh.rmdir(build_dir)
686 self.test_ssh.copy(build_dir, recursive=True)
687 # the repo url is taken from arch-rpms-url
688 # with the last step (i386) removed
689 repo_url = self.options.arch_rpms_url
690 for level in [ 'arch' ]:
691 repo_url = os.path.dirname(repo_url)
693 # invoke initvm (drop support for vs)
694 script = "lbuild-initvm.sh"
696 # pass the vbuild-nightly options to [lv]test-initvm
697 script_options += " -p {}".format(self.options.personality)
698 script_options += " -d {}".format(self.options.pldistro)
699 script_options += " -f {}".format(self.options.fcdistro)
700 script_options += " -r {}".format(repo_url)
701 vserver_name = self.vservername
703 vserver_hostname = socket.gethostbyaddr(self.vserverip)[0]
704 script_options += " -n {}".format(vserver_hostname)
706 print("Cannot reverse lookup {}".format(self.vserverip))
707 print("This is considered fatal, as this might pollute the test results")
709 create_vserver="{build_dir}/{script} {script_options} {vserver_name}".format(**locals())
710 return self.run_in_host(create_vserver) == 0
713 def plc_install(self):
715 yum install myplc, noderepo
719 if self.options.personality == "linux32":
721 elif self.options.personality == "linux64":
724 raise Exception("Unsupported personality {}".format(self.options.personality))
725 nodefamily = "{}-{}-{}".format(self.options.pldistro, self.options.fcdistro, arch)
728 pkgs_list.append("slicerepo-{}".format(nodefamily))
729 pkgs_list.append("myplc")
730 pkgs_list.append("noderepo-{}".format(nodefamily))
731 pkgs_string=" ".join(pkgs_list)
732 return self.yum_install(pkgs_list)
734 def install_syslinux6(self):
736 install syslinux6 from the fedora21 release
738 key = 'http://mirror.onelab.eu/keys/RPM-GPG-KEY-fedora-21-primary'
741 'http://mirror.onelab.eu/fedora/releases/21/Everything/x86_64/os/Packages/s/syslinux-6.03-1.fc21.x86_64.rpm',
742 'http://mirror.onelab.eu/fedora/releases/21/Everything/x86_64/os/Packages/s/syslinux-nonlinux-6.03-1.fc21.noarch.rpm',
743 'http://mirror.onelab.eu/fedora/releases/21/Everything/x86_64/os/Packages/s/syslinux-perl-6.03-1.fc21.x86_64.rpm',
745 # this can be done several times
746 self.run_in_guest("rpm --import {key}".format(**locals()))
747 return self.run_in_guest("yum -y localinstall {}".format(" ".join(rpms))) == 0
749 def bonding_builds(self):
751 list /etc/yum.repos.d on the myplc side
753 self.run_in_guest("ls /etc/yum.repos.d/*partial.repo")
756 def bonding_nodes(self):
758 List nodes known to the myplc together with their nodefamiliy
760 print("---------------------------------------- nodes")
761 for node in self.apiserver.GetNodes(self.auth_root()):
762 print("{} -> {}".format(node['hostname'],
763 self.apiserver.GetNodeFlavour(self.auth_root(),node['hostname'])['nodefamily']))
764 print("---------------------------------------- nodes")
768 def mod_python(self):
769 """yum install mod_python, useful on f18 and above so as to avoid broken wsgi"""
770 return self.yum_install( ['mod_python'] )
773 def plc_configure(self):
775 tmpname = '{}.plc-config-tty'.format(self.name())
776 with open(tmpname,'w') as fileconf:
777 for (var,value) in self.plc_spec['settings'].items():
778 fileconf.write('e {}\n{}\n'.format(var, value))
779 fileconf.write('w\n')
780 fileconf.write('q\n')
781 utils.system('cat {}'.format(tmpname))
782 self.run_in_guest_piped('cat {}'.format(tmpname), 'plc-config-tty')
783 utils.system('rm {}'.format(tmpname))
786 # f14 is a bit odd in this respect, although this worked fine in guests up to f18
787 # however using a vplc guest under f20 requires this trick
788 # the symptom is this: service plc start
789 # Starting plc (via systemctl): Failed to get D-Bus connection: \
790 # Failed to connect to socket /org/freedesktop/systemd1/private: Connection refused
791 # weird thing is the doc says f14 uses upstart by default and not systemd
792 # so this sounds kind of harmless
793 def start_service(self, service):
794 return self.start_stop_service(service, 'start')
795 def stop_service(self, service):
796 return self.start_stop_service(service, 'stop')
798 def start_stop_service(self, service, start_or_stop):
799 "utility to start/stop a service with the special trick for f14"
800 if self.options.fcdistro != 'f14':
801 return self.run_in_guest("service {} {}".format(service, start_or_stop)) == 0
803 # patch /sbin/service so it does not reset environment
804 self.run_in_guest('sed -i -e \\"s,env -i,env,\\" /sbin/service')
805 # this is because our own scripts in turn call service
806 return self.run_in_guest("SYSTEMCTL_SKIP_REDIRECT=true service {} {}"\
807 .format(service, start_or_stop)) == 0
811 return self.start_service('plc')
815 return self.stop_service('plc')
817 def plcvm_start(self):
818 "start the PLC vserver"
822 def plcvm_stop(self):
823 "stop the PLC vserver"
827 # stores the keys from the config for further use
828 def keys_store(self):
829 "stores test users ssh keys in keys/"
830 for key_spec in self.plc_spec['keys']:
831 TestKey(self,key_spec).store_key()
834 def keys_clean(self):
835 "removes keys cached in keys/"
836 utils.system("rm -rf ./keys")
839 # fetches the ssh keys in the plc's /etc/planetlab and stores them in keys/
840 # for later direct access to the nodes
841 def keys_fetch(self):
842 "gets ssh keys in /etc/planetlab/ and stores them locally in keys/"
844 if not os.path.isdir(dir):
846 vservername = self.vservername
847 vm_root = self.vm_root_in_host()
849 prefix = 'debug_ssh_key'
850 for ext in ['pub', 'rsa'] :
851 src = "{vm_root}/etc/planetlab/{prefix}.{ext}".format(**locals())
852 dst = "keys/{vservername}-debug.{ext}".format(**locals())
853 if self.test_ssh.fetch(src, dst) != 0:
858 "create sites with PLCAPI"
859 return self.do_sites()
861 def delete_sites(self):
862 "delete sites with PLCAPI"
863 return self.do_sites(action="delete")
865 def do_sites(self, action="add"):
866 for site_spec in self.plc_spec['sites']:
867 test_site = TestSite(self,site_spec)
868 if (action != "add"):
869 utils.header("Deleting site {} in {}".format(test_site.name(), self.name()))
870 test_site.delete_site()
871 # deleted with the site
872 #test_site.delete_users()
875 utils.header("Creating site {} & users in {}".format(test_site.name(), self.name()))
876 test_site.create_site()
877 test_site.create_users()
880 def delete_all_sites(self):
881 "Delete all sites in PLC, and related objects"
882 print('auth_root', self.auth_root())
883 sites = self.apiserver.GetSites(self.auth_root(), {}, ['site_id','login_base'])
885 # keep automatic site - otherwise we shoot in our own foot, root_auth is not valid anymore
886 if site['login_base'] == self.plc_spec['settings']['PLC_SLICE_PREFIX']:
888 site_id = site['site_id']
889 print('Deleting site_id', site_id)
890 self.apiserver.DeleteSite(self.auth_root(), site_id)
894 "create nodes with PLCAPI"
895 return self.do_nodes()
896 def delete_nodes(self):
897 "delete nodes with PLCAPI"
898 return self.do_nodes(action="delete")
900 def do_nodes(self, action="add"):
901 for site_spec in self.plc_spec['sites']:
902 test_site = TestSite(self, site_spec)
904 utils.header("Deleting nodes in site {}".format(test_site.name()))
905 for node_spec in site_spec['nodes']:
906 test_node = TestNode(self, test_site, node_spec)
907 utils.header("Deleting {}".format(test_node.name()))
908 test_node.delete_node()
910 utils.header("Creating nodes for site {} in {}".format(test_site.name(), self.name()))
911 for node_spec in site_spec['nodes']:
912 utils.pprint('Creating node {}'.format(node_spec), node_spec)
913 test_node = TestNode(self, test_site, node_spec)
914 test_node.create_node()
917 def nodegroups(self):
918 "create nodegroups with PLCAPI"
919 return self.do_nodegroups("add")
920 def delete_nodegroups(self):
921 "delete nodegroups with PLCAPI"
922 return self.do_nodegroups("delete")
926 def translate_timestamp(start, grain, timestamp):
927 if timestamp < TestPlc.YEAR:
928 return start + timestamp*grain
933 def timestamp_printable(timestamp):
934 return time.strftime('%m-%d %H:%M:%S UTC', time.gmtime(timestamp))
937 "create leases (on reservable nodes only, use e.g. run -c default -c resa)"
938 now = int(time.time())
939 grain = self.apiserver.GetLeaseGranularity(self.auth_root())
940 print('API answered grain=', grain)
941 start = (now//grain)*grain
943 # find out all nodes that are reservable
944 nodes = self.all_reservable_nodenames()
946 utils.header("No reservable node found - proceeding without leases")
949 # attach them to the leases as specified in plc_specs
950 # this is where the 'leases' field gets interpreted as relative of absolute
951 for lease_spec in self.plc_spec['leases']:
952 # skip the ones that come with a null slice id
953 if not lease_spec['slice']:
955 lease_spec['t_from'] = TestPlc.translate_timestamp(start, grain, lease_spec['t_from'])
956 lease_spec['t_until'] = TestPlc.translate_timestamp(start, grain, lease_spec['t_until'])
957 lease_addition = self.apiserver.AddLeases(self.auth_root(), nodes, lease_spec['slice'],
958 lease_spec['t_from'], lease_spec['t_until'])
959 if lease_addition['errors']:
960 utils.header("Cannot create leases, {}".format(lease_addition['errors']))
963 utils.header('Leases on nodes {} for {} from {:d} ({}) until {:d} ({})'\
964 .format(nodes, lease_spec['slice'],
965 lease_spec['t_from'], TestPlc.timestamp_printable(lease_spec['t_from']),
966 lease_spec['t_until'], TestPlc.timestamp_printable(lease_spec['t_until'])))
970 def delete_leases(self):
971 "remove all leases in the myplc side"
972 lease_ids = [ l['lease_id'] for l in self.apiserver.GetLeases(self.auth_root())]
973 utils.header("Cleaning leases {}".format(lease_ids))
974 self.apiserver.DeleteLeases(self.auth_root(), lease_ids)
977 def list_leases(self):
978 "list all leases known to the myplc"
979 leases = self.apiserver.GetLeases(self.auth_root())
980 now = int(time.time())
982 current = l['t_until'] >= now
983 if self.options.verbose or current:
984 utils.header("{} {} from {} until {}"\
985 .format(l['hostname'], l['name'],
986 TestPlc.timestamp_printable(l['t_from']),
987 TestPlc.timestamp_printable(l['t_until'])))
990 # create nodegroups if needed, and populate
991 def do_nodegroups(self, action="add"):
992 # 1st pass to scan contents
994 for site_spec in self.plc_spec['sites']:
995 test_site = TestSite(self,site_spec)
996 for node_spec in site_spec['nodes']:
997 test_node = TestNode(self, test_site, node_spec)
998 if 'nodegroups' in node_spec:
999 nodegroupnames = node_spec['nodegroups']
1000 if isinstance(nodegroupnames, str):
1001 nodegroupnames = [ nodegroupnames ]
1002 for nodegroupname in nodegroupnames:
1003 if nodegroupname not in groups_dict:
1004 groups_dict[nodegroupname] = []
1005 groups_dict[nodegroupname].append(test_node.name())
1006 auth = self.auth_root()
1008 for (nodegroupname,group_nodes) in groups_dict.items():
1010 print('nodegroups:', 'dealing with nodegroup',\
1011 nodegroupname, 'on nodes', group_nodes)
1012 # first, check if the nodetagtype is here
1013 tag_types = self.apiserver.GetTagTypes(auth, {'tagname':nodegroupname})
1015 tag_type_id = tag_types[0]['tag_type_id']
1017 tag_type_id = self.apiserver.AddTagType(auth,
1018 {'tagname' : nodegroupname,
1019 'description' : 'for nodegroup {}'.format(nodegroupname),
1020 'category' : 'test'})
1021 print('located tag (type)', nodegroupname, 'as', tag_type_id)
1023 nodegroups = self.apiserver.GetNodeGroups(auth, {'groupname' : nodegroupname})
1025 self.apiserver.AddNodeGroup(auth, nodegroupname, tag_type_id, 'yes')
1026 print('created nodegroup', nodegroupname, \
1027 'from tagname', nodegroupname, 'and value', 'yes')
1028 # set node tag on all nodes, value='yes'
1029 for nodename in group_nodes:
1031 self.apiserver.AddNodeTag(auth, nodename, nodegroupname, "yes")
1033 traceback.print_exc()
1034 print('node', nodename, 'seems to already have tag', nodegroupname)
1037 expect_yes = self.apiserver.GetNodeTags(auth,
1038 {'hostname' : nodename,
1039 'tagname' : nodegroupname},
1040 ['value'])[0]['value']
1041 if expect_yes != "yes":
1042 print('Mismatch node tag on node',nodename,'got',expect_yes)
1045 if not self.options.dry_run:
1046 print('Cannot find tag', nodegroupname, 'on node', nodename)
1050 print('cleaning nodegroup', nodegroupname)
1051 self.apiserver.DeleteNodeGroup(auth, nodegroupname)
1053 traceback.print_exc()
1057 # a list of TestNode objs
1058 def all_nodes(self):
1060 for site_spec in self.plc_spec['sites']:
1061 test_site = TestSite(self,site_spec)
1062 for node_spec in site_spec['nodes']:
1063 nodes.append(TestNode(self, test_site, node_spec))
1066 # return a list of tuples (nodename,qemuname)
1067 def all_node_infos(self) :
1069 for site_spec in self.plc_spec['sites']:
1070 node_infos += [ (node_spec['node_fields']['hostname'], node_spec['host_box']) \
1071 for node_spec in site_spec['nodes'] ]
1074 def all_nodenames(self):
1075 return [ x[0] for x in self.all_node_infos() ]
1076 def all_reservable_nodenames(self):
1078 for site_spec in self.plc_spec['sites']:
1079 for node_spec in site_spec['nodes']:
1080 node_fields = node_spec['node_fields']
1081 if 'node_type' in node_fields and node_fields['node_type'] == 'reservable':
1082 res.append(node_fields['hostname'])
1085 # silent_minutes : during the first <silent_minutes> minutes nothing gets printed
1086 def nodes_check_boot_state(self, target_boot_state, timeout_minutes,
1087 silent_minutes, period_seconds = 15):
1088 if self.options.dry_run:
1092 class CompleterTaskBootState(CompleterTask):
1093 def __init__(self, test_plc, hostname):
1094 self.test_plc = test_plc
1095 self.hostname = hostname
1096 self.last_boot_state = 'undef'
1097 def actual_run(self):
1099 node = self.test_plc.apiserver.GetNodes(self.test_plc.auth_root(),
1102 self.last_boot_state = node['boot_state']
1103 return self.last_boot_state == target_boot_state
1107 return "CompleterTaskBootState with node {}".format(self.hostname)
1108 def failure_epilogue(self):
1109 print("node {} in state {} - expected {}"\
1110 .format(self.hostname, self.last_boot_state, target_boot_state))
1112 timeout = timedelta(minutes=timeout_minutes)
1113 graceout = timedelta(minutes=silent_minutes)
1114 period = timedelta(seconds=period_seconds)
1115 # the nodes that haven't checked yet - start with a full list and shrink over time
1116 utils.header("checking nodes boot state (expected {})".format(target_boot_state))
1117 tasks = [ CompleterTaskBootState(self,hostname) \
1118 for (hostname,_) in self.all_node_infos() ]
1119 message = 'check_boot_state={}'.format(target_boot_state)
1120 return Completer(tasks, message=message).run(timeout, graceout, period)
1122 def nodes_booted(self):
1123 return self.nodes_check_boot_state('boot', timeout_minutes=30, silent_minutes=28)
1125 def probe_kvm_iptables(self):
1126 (_,kvmbox) = self.all_node_infos()[0]
1127 TestSsh(kvmbox).run("iptables-save")
1131 def check_nodes_ping(self, timeout_seconds=60, period_seconds=10):
1132 class CompleterTaskPingNode(CompleterTask):
1133 def __init__(self, hostname):
1134 self.hostname = hostname
1135 def run(self, silent):
1136 command="ping -c 1 -w 1 {} >& /dev/null".format(self.hostname)
1137 return utils.system(command, silent=silent) == 0
1138 def failure_epilogue(self):
1139 print("Cannot ping node with name {}".format(self.hostname))
1140 timeout = timedelta(seconds = timeout_seconds)
1142 period = timedelta(seconds = period_seconds)
1143 node_infos = self.all_node_infos()
1144 tasks = [ CompleterTaskPingNode(h) for (h,_) in node_infos ]
1145 return Completer(tasks, message='ping_node').run(timeout, graceout, period)
1147 # ping node before we try to reach ssh, helpful for troubleshooting failing bootCDs
1148 def ping_node(self):
1150 return self.check_nodes_ping()
1152 def check_nodes_ssh(self, debug, timeout_minutes, silent_minutes, period_seconds=15):
1154 timeout = timedelta(minutes=timeout_minutes)
1155 graceout = timedelta(minutes=silent_minutes)
1156 period = timedelta(seconds=period_seconds)
1157 vservername = self.vservername
1160 completer_message = 'ssh_node_debug'
1161 local_key = "keys/{vservername}-debug.rsa".format(**locals())
1164 completer_message = 'ssh_node_boot'
1165 local_key = "keys/key_admin.rsa"
1166 utils.header("checking ssh access to nodes (expected in {} mode)".format(message))
1167 node_infos = self.all_node_infos()
1168 tasks = [ CompleterTaskNodeSsh(nodename, qemuname, local_key,
1169 boot_state=message, dry_run=self.options.dry_run) \
1170 for (nodename, qemuname) in node_infos ]
1171 return Completer(tasks, message=completer_message).run(timeout, graceout, period)
1173 def ssh_node_debug(self):
1174 "Tries to ssh into nodes in debug mode with the debug ssh key"
1175 return self.check_nodes_ssh(debug = True,
1176 timeout_minutes = self.ssh_node_debug_timeout,
1177 silent_minutes = self.ssh_node_debug_silent)
1179 def ssh_node_boot(self):
1180 "Tries to ssh into nodes in production mode with the root ssh key"
1181 return self.check_nodes_ssh(debug = False,
1182 timeout_minutes = self.ssh_node_boot_timeout,
1183 silent_minutes = self.ssh_node_boot_silent)
1185 def node_bmlogs(self):
1186 "Checks that there's a non-empty dir. /var/log/bm/raw"
1187 return utils.system(self.actual_command_in_guest("ls /var/log/bm/raw")) == 0
1190 def qemu_local_init(self): pass
1192 def bootcd(self): pass
1194 def qemu_local_config(self): pass
1196 def qemu_export(self): pass
1198 def qemu_cleanlog(self): pass
1200 def nodestate_reinstall(self): pass
1202 def nodestate_upgrade(self): pass
1204 def nodestate_safeboot(self): pass
1206 def nodestate_boot(self): pass
1208 def nodestate_show(self): pass
1210 def nodedistro_f14(self): pass
1212 def nodedistro_f18(self): pass
1214 def nodedistro_f20(self): pass
1216 def nodedistro_f21(self): pass
1218 def nodedistro_f22(self): pass
1220 def nodedistro_show(self): pass
1222 ### check hooks : invoke scripts from hooks/{node,slice}
1223 def check_hooks_node(self):
1224 return self.locate_first_node().check_hooks()
1225 def check_hooks_sliver(self) :
1226 return self.locate_first_sliver().check_hooks()
1228 def check_hooks(self):
1229 "runs unit tests in the node and slice contexts - see hooks/{node,slice}"
1230 return self.check_hooks_node() and self.check_hooks_sliver()
1233 def do_check_initscripts(self):
1234 class CompleterTaskInitscript(CompleterTask):
1235 def __init__(self, test_sliver, stamp):
1236 self.test_sliver = test_sliver
1238 def actual_run(self):
1239 return self.test_sliver.check_initscript_stamp(self.stamp)
1241 return "initscript checker for {}".format(self.test_sliver.name())
1242 def failure_epilogue(self):
1243 print("initscript stamp {} not found in sliver {}"\
1244 .format(self.stamp, self.test_sliver.name()))
1247 for slice_spec in self.plc_spec['slices']:
1248 if 'initscriptstamp' not in slice_spec:
1250 stamp = slice_spec['initscriptstamp']
1251 slicename = slice_spec['slice_fields']['name']
1252 for nodename in slice_spec['nodenames']:
1253 print('nodename', nodename, 'slicename', slicename, 'stamp', stamp)
1254 site,node = self.locate_node(nodename)
1255 # xxx - passing the wrong site - probably harmless
1256 test_site = TestSite(self, site)
1257 test_slice = TestSlice(self, test_site, slice_spec)
1258 test_node = TestNode(self, test_site, node)
1259 test_sliver = TestSliver(self, test_node, test_slice)
1260 tasks.append(CompleterTaskInitscript(test_sliver, stamp))
1261 return Completer(tasks, message='check_initscripts').\
1262 run (timedelta(minutes=5), timedelta(minutes=4), timedelta(seconds=10))
1264 def check_initscripts(self):
1265 "check that the initscripts have triggered"
1266 return self.do_check_initscripts()
1268 def initscripts(self):
1269 "create initscripts with PLCAPI"
1270 for initscript in self.plc_spec['initscripts']:
1271 utils.pprint('Adding Initscript in plc {}'.format(self.plc_spec['name']), initscript)
1272 self.apiserver.AddInitScript(self.auth_root(), initscript['initscript_fields'])
1275 def delete_initscripts(self):
1276 "delete initscripts with PLCAPI"
1277 for initscript in self.plc_spec['initscripts']:
1278 initscript_name = initscript['initscript_fields']['name']
1279 print(('Attempting to delete {} in plc {}'.format(initscript_name, self.plc_spec['name'])))
1281 self.apiserver.DeleteInitScript(self.auth_root(), initscript_name)
1282 print(initscript_name, 'deleted')
1284 print('deletion went wrong - probably did not exist')
1289 "create slices with PLCAPI"
1290 return self.do_slices(action="add")
1292 def delete_slices(self):
1293 "delete slices with PLCAPI"
1294 return self.do_slices(action="delete")
1296 def fill_slices(self):
1297 "add nodes in slices with PLCAPI"
1298 return self.do_slices(action="fill")
1300 def empty_slices(self):
1301 "remove nodes from slices with PLCAPI"
1302 return self.do_slices(action="empty")
1304 def do_slices(self, action="add"):
1305 for slice in self.plc_spec['slices']:
1306 site_spec = self.locate_site(slice['sitename'])
1307 test_site = TestSite(self,site_spec)
1308 test_slice=TestSlice(self,test_site,slice)
1309 if action == "delete":
1310 test_slice.delete_slice()
1311 elif action == "fill":
1312 test_slice.add_nodes()
1313 elif action == "empty":
1314 test_slice.delete_nodes()
1316 test_slice.create_slice()
1319 @slice_mapper__tasks(20, 10, 15)
1320 def ssh_slice(self): pass
1321 @slice_mapper__tasks(20, 19, 15)
1322 def ssh_slice_off(self): pass
1323 @slice_mapper__tasks(1, 1, 15)
1324 def slice_fs_present(self): pass
1325 @slice_mapper__tasks(1, 1, 15)
1326 def slice_fs_deleted(self): pass
1328 # use another name so we can exclude/ignore it from the tests on the nightly command line
1329 def ssh_slice_again(self): return self.ssh_slice()
1330 # note that simply doing ssh_slice_again=ssh_slice would kind of work too
1331 # but for some reason the ignore-wrapping thing would not
1334 def ssh_slice_basics(self): pass
1336 def check_vsys_defaults(self): pass
1339 def keys_clear_known_hosts(self): pass
1341 def plcapi_urls(self):
1343 attempts to reach the PLCAPI with various forms for the URL
1345 return PlcapiUrlScanner(self.auth_root(), ip=self.vserverip).scan()
1347 def speed_up_slices(self):
1348 "tweak nodemanager cycle (wait time) to 30+/-10 s"
1349 return self._speed_up_slices (30, 10)
1350 def super_speed_up_slices(self):
1351 "dev mode: tweak nodemanager cycle (wait time) to 5+/-1 s"
1352 return self._speed_up_slices(5, 1)
1354 def _speed_up_slices(self, p, r):
1355 # create the template on the server-side
1356 template = "{}.nodemanager".format(self.name())
1357 with open(template,"w") as template_file:
1358 template_file.write('OPTIONS="-p {} -r {} -d"\n'.format(p, r))
1359 in_vm = "/var/www/html/PlanetLabConf/nodemanager"
1360 remote = "{}/{}".format(self.vm_root_in_host(), in_vm)
1361 self.test_ssh.copy_abs(template, remote)
1363 if not self.apiserver.GetConfFiles(self.auth_root(),
1364 {'dest' : '/etc/sysconfig/nodemanager'}):
1365 self.apiserver.AddConfFile(self.auth_root(),
1366 {'dest' : '/etc/sysconfig/nodemanager',
1367 'source' : 'PlanetLabConf/nodemanager',
1368 'postinstall_cmd' : 'service nm restart',})
1371 def debug_nodemanager(self):
1372 "sets verbose mode for nodemanager, and speeds up cycle even more (needs speed_up_slices first)"
1373 template = "{}.nodemanager".format(self.name())
1374 with open(template,"w") as template_file:
1375 template_file.write('OPTIONS="-p 10 -r 6 -v -d"\n')
1376 in_vm = "/var/www/html/PlanetLabConf/nodemanager"
1377 remote = "{}/{}".format(self.vm_root_in_host(), in_vm)
1378 self.test_ssh.copy_abs(template, remote)
1382 def qemu_start(self) : pass
1385 def qemu_timestamp(self) : pass
1388 def qemu_nodefamily(self): pass
1390 # when a spec refers to a node possibly on another plc
1391 def locate_sliver_obj_cross(self, nodename, slicename, other_plcs):
1392 for plc in [ self ] + other_plcs:
1394 return plc.locate_sliver_obj(nodename, slicename)
1397 raise Exception("Cannot locate sliver {}@{} among all PLCs".format(nodename, slicename))
1399 # implement this one as a cross step so that we can take advantage of different nodes
1400 # in multi-plcs mode
1401 def cross_check_tcp(self, other_plcs):
1402 "check TCP connectivity between 2 slices (or in loopback if only one is defined)"
1403 if 'tcp_specs' not in self.plc_spec or not self.plc_spec['tcp_specs']:
1404 utils.header("check_tcp: no/empty config found")
1406 specs = self.plc_spec['tcp_specs']
1409 # first wait for the network to be up and ready from the slices
1410 class CompleterTaskNetworkReadyInSliver(CompleterTask):
1411 def __init__(self, test_sliver):
1412 self.test_sliver = test_sliver
1413 def actual_run(self):
1414 return self.test_sliver.check_tcp_ready(port = 9999)
1416 return "network ready checker for {}".format(self.test_sliver.name())
1417 def failure_epilogue(self):
1418 print("could not bind port from sliver {}".format(self.test_sliver.name()))
1422 managed_sliver_names = set()
1424 # locate the TestSliver instances involved, and cache them in the spec instance
1425 spec['s_sliver'] = self.locate_sliver_obj_cross(spec['server_node'], spec['server_slice'], other_plcs)
1426 spec['c_sliver'] = self.locate_sliver_obj_cross(spec['client_node'], spec['client_slice'], other_plcs)
1427 message = "Will check TCP between s={} and c={}"\
1428 .format(spec['s_sliver'].name(), spec['c_sliver'].name())
1429 if 'client_connect' in spec:
1430 message += " (using {})".format(spec['client_connect'])
1431 utils.header(message)
1432 # we need to check network presence in both slivers, but also
1433 # avoid to insert a sliver several times
1434 for sliver in [ spec['s_sliver'], spec['c_sliver'] ]:
1435 if sliver.name() not in managed_sliver_names:
1436 tasks.append(CompleterTaskNetworkReadyInSliver(sliver))
1437 # add this sliver's name in the set
1438 managed_sliver_names .update( {sliver.name()} )
1440 # wait for the netork to be OK in all server sides
1441 if not Completer(tasks, message='check for network readiness in slivers').\
1442 run(timedelta(seconds=30), timedelta(seconds=24), period=timedelta(seconds=5)):
1445 # run server and client
1449 # the issue here is that we have the server run in background
1450 # and so we have no clue if it took off properly or not
1451 # looks like in some cases it does not
1452 if not spec['s_sliver'].run_tcp_server(port, timeout=20):
1456 # idem for the client side
1457 # use nodename from located sliver, unless 'client_connect' is set
1458 if 'client_connect' in spec:
1459 destination = spec['client_connect']
1461 destination = spec['s_sliver'].test_node.name()
1462 if not spec['c_sliver'].run_tcp_client(destination, port):
1466 # painfully enough, we need to allow for some time as netflow might show up last
1467 def check_system_slice(self):
1468 "all nodes: check that a system slice is alive"
1469 # netflow currently not working in the lxc distro
1470 # drl not built at all in the wtx distro
1471 # if we find either of them we're happy
1472 return self.check_netflow() or self.check_drl()
1475 def check_netflow(self): return self._check_system_slice('netflow')
1476 def check_drl(self): return self._check_system_slice('drl')
1478 # we have the slices up already here, so it should not take too long
1479 def _check_system_slice(self, slicename, timeout_minutes=5, period_seconds=15):
1480 class CompleterTaskSystemSlice(CompleterTask):
1481 def __init__(self, test_node, dry_run):
1482 self.test_node = test_node
1483 self.dry_run = dry_run
1484 def actual_run(self):
1485 return self.test_node._check_system_slice(slicename, dry_run=self.dry_run)
1487 return "System slice {} @ {}".format(slicename, self.test_node.name())
1488 def failure_epilogue(self):
1489 print("COULD not find system slice {} @ {}".format(slicename, self.test_node.name()))
1490 timeout = timedelta(minutes=timeout_minutes)
1491 silent = timedelta(0)
1492 period = timedelta(seconds=period_seconds)
1493 tasks = [ CompleterTaskSystemSlice(test_node, self.options.dry_run) \
1494 for test_node in self.all_nodes() ]
1495 return Completer(tasks, message='_check_system_slice').run(timeout, silent, period)
1497 def plcsh_stress_test(self):
1498 "runs PLCAPI stress test, that checks Add/Update/Delete on all types - preserves contents"
1499 # install the stress-test in the plc image
1500 location = "/usr/share/plc_api/plcsh_stress_test.py"
1501 remote = "{}/{}".format(self.vm_root_in_host(), location)
1502 self.test_ssh.copy_abs("plcsh_stress_test.py", remote)
1504 command += " -- --check"
1505 if self.options.size == 1:
1506 command += " --tiny"
1507 return self.run_in_guest(command) == 0
1509 # populate runs the same utility without slightly different options
1510 # in particular runs with --preserve (dont cleanup) and without --check
1511 # also it gets run twice, once with the --foreign option for creating fake foreign entries
1513 def sfa_install_all(self):
1514 "yum install sfa sfa-plc sfa-sfatables sfa-client"
1515 return self.yum_install("sfa sfa-plc sfa-sfatables sfa-client")
1517 def sfa_install_core(self):
1519 return self.yum_install("sfa")
1521 def sfa_install_plc(self):
1522 "yum install sfa-plc"
1523 return self.yum_install("sfa-plc")
1525 def sfa_install_sfatables(self):
1526 "yum install sfa-sfatables"
1527 return self.yum_install("sfa-sfatables")
1529 # for some very odd reason, this sometimes fails with the following symptom
1530 # # yum install sfa-client
1531 # Setting up Install Process
1533 # Downloading Packages:
1534 # Running rpm_check_debug
1535 # Running Transaction Test
1536 # Transaction Test Succeeded
1537 # Running Transaction
1538 # Transaction couldn't start:
1539 # installing package sfa-client-2.1-7.onelab.2012.05.23.i686 needs 68KB on the / filesystem
1540 # [('installing package sfa-client-2.1-7.onelab.2012.05.23.i686 needs 68KB on the / filesystem', (9, '/', 69632L))]
1541 # even though in the same context I have
1542 # [2012.05.23--f14-32-sfastd1-1-vplc07] / # df -h
1543 # Filesystem Size Used Avail Use% Mounted on
1544 # /dev/hdv1 806G 264G 501G 35% /
1545 # none 16M 36K 16M 1% /tmp
1547 # so as a workaround, we first try yum install, and then invoke rpm on the cached rpm...
1548 def sfa_install_client(self):
1549 "yum install sfa-client"
1550 first_try = self.yum_install("sfa-client")
1553 utils.header("********** Regular yum failed - special workaround in place, 2nd chance")
1554 code, cached_rpm_path = \
1555 utils.output_of(self.actual_command_in_guest('find /var/cache/yum -name sfa-client\*.rpm'))
1556 utils.header("rpm_path=<<{}>>".format(rpm_path))
1558 self.run_in_guest("rpm -i {}".format(cached_rpm_path))
1559 return self.yum_check_installed("sfa-client")
1561 def sfa_dbclean(self):
1562 "thoroughly wipes off the SFA database"
1563 return self.run_in_guest("sfaadmin reg nuke") == 0 or \
1564 self.run_in_guest("sfa-nuke.py") == 0 or \
1565 self.run_in_guest("sfa-nuke-plc.py") == 0 or \
1566 self.run_in_guest("sfaadmin registry nuke") == 0
1568 def sfa_fsclean(self):
1569 "cleanup /etc/sfa/trusted_roots and /var/lib/sfa"
1570 self.run_in_guest("rm -rf /etc/sfa/trusted_roots /var/lib/sfa/authorities")
1573 def sfa_plcclean(self):
1574 "cleans the PLC entries that were created as a side effect of running the script"
1576 sfa_spec = self.plc_spec['sfa']
1578 for auth_sfa_spec in sfa_spec['auth_sfa_specs']:
1579 login_base = auth_sfa_spec['login_base']
1581 self.apiserver.DeleteSite(self.auth_root(),login_base)
1583 print("Site {} already absent from PLC db".format(login_base))
1585 for spec_name in ['pi_spec','user_spec']:
1586 user_spec = auth_sfa_spec[spec_name]
1587 username = user_spec['email']
1589 self.apiserver.DeletePerson(self.auth_root(),username)
1591 # this in fact is expected as sites delete their members
1592 #print "User {} already absent from PLC db".format(username)
1595 print("REMEMBER TO RUN sfa_import AGAIN")
1598 def sfa_uninstall(self):
1599 "uses rpm to uninstall sfa - ignore result"
1600 self.run_in_guest("rpm -e sfa sfa-sfatables sfa-client sfa-plc")
1601 self.run_in_guest("rm -rf /var/lib/sfa")
1602 self.run_in_guest("rm -rf /etc/sfa")
1603 self.run_in_guest("rm -rf /var/log/sfa_access.log /var/log/sfa_import_plc.log /var/log/sfa.daemon")
1605 self.run_in_guest("rpm -e --noscripts sfa-plc")
1608 ### run unit tests for SFA
1609 # NOTE: for some reason on f14/i386, yum install sfa-tests fails for no reason
1610 # Running Transaction
1611 # Transaction couldn't start:
1612 # installing package sfa-tests-1.0-21.onelab.i686 needs 204KB on the / filesystem
1613 # [('installing package sfa-tests-1.0-21.onelab.i686 needs 204KB on the / filesystem', (9, '/', 208896L))]
1614 # no matter how many Gbs are available on the testplc
1615 # could not figure out what's wrong, so...
1616 # if the yum install phase fails, consider the test is successful
1617 # other combinations will eventually run it hopefully
1618 def sfa_utest(self):
1619 "yum install sfa-tests and run SFA unittests"
1620 self.run_in_guest("yum -y install sfa-tests")
1621 # failed to install - forget it
1622 if self.run_in_guest("rpm -q sfa-tests") != 0:
1623 utils.header("WARNING: SFA unit tests failed to install, ignoring")
1625 return self.run_in_guest("/usr/share/sfa/tests/testAll.py") == 0
1629 dirname = "conf.{}".format(self.plc_spec['name'])
1630 if not os.path.isdir(dirname):
1631 utils.system("mkdir -p {}".format(dirname))
1632 if not os.path.isdir(dirname):
1633 raise Exception("Cannot create config dir for plc {}".format(self.name()))
1636 def conffile(self, filename):
1637 return "{}/{}".format(self.confdir(), filename)
1638 def confsubdir(self, dirname, clean, dry_run=False):
1639 subdirname = "{}/{}".format(self.confdir(), dirname)
1641 utils.system("rm -rf {}".format(subdirname))
1642 if not os.path.isdir(subdirname):
1643 utils.system("mkdir -p {}".format(subdirname))
1644 if not dry_run and not os.path.isdir(subdirname):
1645 raise "Cannot create config subdir {} for plc {}".format(dirname, self.name())
1648 def conffile_clean(self, filename):
1649 filename=self.conffile(filename)
1650 return utils.system("rm -rf {}".format(filename))==0
1653 def sfa_configure(self):
1654 "run sfa-config-tty"
1655 tmpname = self.conffile("sfa-config-tty")
1656 with open(tmpname,'w') as fileconf:
1657 for (var,value) in self.plc_spec['sfa']['settings'].items():
1658 fileconf.write('e {}\n{}\n'.format(var, value))
1659 fileconf.write('w\n')
1660 fileconf.write('R\n')
1661 fileconf.write('q\n')
1662 utils.system('cat {}'.format(tmpname))
1663 self.run_in_guest_piped('cat {}'.format(tmpname), 'sfa-config-tty')
1666 def aggregate_xml_line(self):
1667 port = self.plc_spec['sfa']['neighbours-port']
1668 return '<aggregate addr="{}" hrn="{}" port="{}"/>'\
1669 .format(self.vserverip, self.plc_spec['sfa']['settings']['SFA_REGISTRY_ROOT_AUTH'], port)
1671 def registry_xml_line(self):
1672 return '<registry addr="{}" hrn="{}" port="12345"/>'\
1673 .format(self.vserverip, self.plc_spec['sfa']['settings']['SFA_REGISTRY_ROOT_AUTH'])
1676 # a cross step that takes all other plcs in argument
1677 def cross_sfa_configure(self, other_plcs):
1678 "writes aggregates.xml and registries.xml that point to all other PLCs in the test"
1679 # of course with a single plc, other_plcs is an empty list
1682 agg_fname = self.conffile("agg.xml")
1683 with open(agg_fname,"w") as out:
1684 out.write("<aggregates>{}</aggregates>\n"\
1685 .format(" ".join([ plc.aggregate_xml_line() for plc in other_plcs ])))
1686 utils.header("(Over)wrote {}".format(agg_fname))
1687 reg_fname=self.conffile("reg.xml")
1688 with open(reg_fname,"w") as out:
1689 out.write("<registries>{}</registries>\n"\
1690 .format(" ".join([ plc.registry_xml_line() for plc in other_plcs ])))
1691 utils.header("(Over)wrote {}".format(reg_fname))
1692 return self.test_ssh.copy_abs(agg_fname,
1693 '/{}/etc/sfa/aggregates.xml'.format(self.vm_root_in_host())) == 0 \
1694 and self.test_ssh.copy_abs(reg_fname,
1695 '/{}/etc/sfa/registries.xml'.format(self.vm_root_in_host())) == 0
1697 def sfa_import(self):
1698 "use sfaadmin to import from plc"
1699 auth = self.plc_spec['sfa']['settings']['SFA_REGISTRY_ROOT_AUTH']
1700 return self.run_in_guest('sfaadmin reg import_registry') == 0
1702 def sfa_start(self):
1704 return self.start_service('sfa')
1707 def sfi_configure(self):
1708 "Create /root/sfi on the plc side for sfi client configuration"
1709 if self.options.dry_run:
1710 utils.header("DRY RUN - skipping step")
1712 sfa_spec = self.plc_spec['sfa']
1713 # cannot use auth_sfa_mapper to pass dir_name
1714 for slice_spec in self.plc_spec['sfa']['auth_sfa_specs']:
1715 test_slice = TestAuthSfa(self, slice_spec)
1716 dir_basename = os.path.basename(test_slice.sfi_path())
1717 dir_name = self.confsubdir("dot-sfi/{}".format(dir_basename),
1718 clean=True, dry_run=self.options.dry_run)
1719 test_slice.sfi_configure(dir_name)
1720 # push into the remote /root/sfi area
1721 location = test_slice.sfi_path()
1722 remote = "{}/{}".format(self.vm_root_in_host(), location)
1723 self.test_ssh.mkdir(remote, abs=True)
1724 # need to strip last level or remote otherwise we get an extra dir level
1725 self.test_ssh.copy_abs(dir_name, os.path.dirname(remote), recursive=True)
1729 def sfi_clean(self):
1730 "clean up /root/sfi on the plc side"
1731 self.run_in_guest("rm -rf /root/sfi")
1734 def sfa_rspec_empty(self):
1735 "expose a static empty rspec (ships with the tests module) in the sfi directory"
1736 filename = "empty-rspec.xml"
1738 for slice_spec in self.plc_spec['sfa']['auth_sfa_specs']:
1739 test_slice = TestAuthSfa(self, slice_spec)
1740 in_vm = test_slice.sfi_path()
1741 remote = "{}/{}".format(self.vm_root_in_host(), in_vm)
1742 if self.test_ssh.copy_abs(filename, remote) !=0:
1747 def sfa_register_site(self): pass
1749 def sfa_register_pi(self): pass
1751 def sfa_register_user(self): pass
1753 def sfa_update_user(self): pass
1755 def sfa_register_slice(self): pass
1757 def sfa_renew_slice(self): pass
1759 def sfa_get_expires(self): pass
1761 def sfa_discover(self): pass
1763 def sfa_rspec(self): pass
1765 def sfa_allocate(self): pass
1767 def sfa_allocate_empty(self): pass
1769 def sfa_provision(self): pass
1771 def sfa_provision_empty(self): pass
1773 def sfa_describe(self): pass
1775 def sfa_check_slice_plc(self): pass
1777 def sfa_check_slice_plc_empty(self): pass
1779 def sfa_update_slice(self): pass
1781 def sfa_remove_user_from_slice(self): pass
1783 def sfa_insert_user_in_slice(self): pass
1785 def sfi_list(self): pass
1787 def sfi_show_site(self): pass
1789 def sfi_show_slice(self): pass
1791 def sfi_show_slice_researchers(self): pass
1793 def ssh_slice_sfa(self): pass
1795 def sfa_delete_user(self): pass
1797 def sfa_delete_slice(self): pass
1801 return self.stop_service('sfa')
1804 "creates random entries in the PLCAPI"
1805 # install the stress-test in the plc image
1806 location = "/usr/share/plc_api/plcsh_stress_test.py"
1807 remote = "{}/{}".format(self.vm_root_in_host(), location)
1808 self.test_ssh.copy_abs("plcsh_stress_test.py", remote)
1810 command += " -- --preserve --short-names"
1811 local = (self.run_in_guest(command) == 0);
1812 # second run with --foreign
1813 command += ' --foreign'
1814 remote = (self.run_in_guest(command) == 0);
1815 return local and remote
1818 ####################
1820 def bonding_init_partial(self): pass
1823 def bonding_add_yum(self): pass
1826 def bonding_install_rpms(self): pass
1828 ####################
1830 def gather_logs(self):
1831 "gets all possible logs from plc's/qemu node's/slice's for future reference"
1832 # (1.a) get the plc's /var/log/ and store it locally in logs/myplc.var-log.<plcname>/*
1833 # (1.b) get the plc's /var/lib/pgsql/data/pg_log/ -> logs/myplc.pgsql-log.<plcname>/*
1834 # (1.c) get the plc's /root/sfi -> logs/sfi.<plcname>/
1835 # (2) get all the nodes qemu log and store it as logs/node.qemu.<node>.log
1836 # (3) get the nodes /var/log and store is as logs/node.var-log.<node>/*
1837 # (4) as far as possible get the slice's /var/log as logs/sliver.var-log.<sliver>/*
1839 print("-------------------- TestPlc.gather_logs : PLC's /var/log")
1840 self.gather_var_logs()
1842 print("-------------------- TestPlc.gather_logs : PLC's /var/lib/psql/data/pg_log/")
1843 self.gather_pgsql_logs()
1845 print("-------------------- TestPlc.gather_logs : PLC's /root/sfi/")
1846 self.gather_root_sfi()
1848 print("-------------------- TestPlc.gather_logs : nodes's QEMU logs")
1849 for site_spec in self.plc_spec['sites']:
1850 test_site = TestSite(self,site_spec)
1851 for node_spec in site_spec['nodes']:
1852 test_node = TestNode(self, test_site, node_spec)
1853 test_node.gather_qemu_logs()
1855 print("-------------------- TestPlc.gather_logs : nodes's /var/log")
1856 self.gather_nodes_var_logs()
1858 print("-------------------- TestPlc.gather_logs : sample sliver's /var/log")
1859 self.gather_slivers_var_logs()
1862 def gather_slivers_var_logs(self):
1863 for test_sliver in self.all_sliver_objs():
1864 remote = test_sliver.tar_var_logs()
1865 utils.system("mkdir -p logs/sliver.var-log.{}".format(test_sliver.name()))
1866 command = remote + " | tar -C logs/sliver.var-log.{} -xf -".format(test_sliver.name())
1867 utils.system(command)
1870 def gather_var_logs(self):
1871 utils.system("mkdir -p logs/myplc.var-log.{}".format(self.name()))
1872 to_plc = self.actual_command_in_guest("tar -C /var/log/ -cf - .")
1873 command = to_plc + "| tar -C logs/myplc.var-log.{} -xf -".format(self.name())
1874 utils.system(command)
1875 command = "chmod a+r,a+x logs/myplc.var-log.{}/httpd".format(self.name())
1876 utils.system(command)
1878 def gather_pgsql_logs(self):
1879 utils.system("mkdir -p logs/myplc.pgsql-log.{}".format(self.name()))
1880 to_plc = self.actual_command_in_guest("tar -C /var/lib/pgsql/data/pg_log/ -cf - .")
1881 command = to_plc + "| tar -C logs/myplc.pgsql-log.{} -xf -".format(self.name())
1882 utils.system(command)
1884 def gather_root_sfi(self):
1885 utils.system("mkdir -p logs/sfi.{}".format(self.name()))
1886 to_plc = self.actual_command_in_guest("tar -C /root/sfi/ -cf - .")
1887 command = to_plc + "| tar -C logs/sfi.{} -xf -".format(self.name())
1888 utils.system(command)
1890 def gather_nodes_var_logs(self):
1891 for site_spec in self.plc_spec['sites']:
1892 test_site = TestSite(self, site_spec)
1893 for node_spec in site_spec['nodes']:
1894 test_node = TestNode(self, test_site, node_spec)
1895 test_ssh = TestSsh(test_node.name(), key="keys/key_admin.rsa")
1896 command = test_ssh.actual_command("tar -C /var/log -cf - .")
1897 command = command + "| tar -C logs/node.var-log.{} -xf -".format(test_node.name())
1898 utils.system("mkdir -p logs/node.var-log.{}".format(test_node.name()))
1899 utils.system(command)
1902 # returns the filename to use for sql dump/restore, using options.dbname if set
1903 def dbfile(self, database):
1904 # uses options.dbname if it is found
1906 name = self.options.dbname
1907 if not isinstance(name, str):
1913 return "/root/{}-{}.sql".format(database, name)
1915 def plc_db_dump(self):
1916 'dump the planetlab5 DB in /root in the PLC - filename has time'
1917 dump=self.dbfile("planetab5")
1918 self.run_in_guest('pg_dump -U pgsqluser planetlab5 -f '+ dump)
1919 utils.header('Dumped planetlab5 database in {}'.format(dump))
1922 def plc_db_restore(self):
1923 'restore the planetlab5 DB - looks broken, but run -n might help'
1924 dump = self.dbfile("planetab5")
1925 ##stop httpd service
1926 self.run_in_guest('service httpd stop')
1927 # xxx - need another wrapper
1928 self.run_in_guest_piped('echo drop database planetlab5', 'psql --user=pgsqluser template1')
1929 self.run_in_guest('createdb -U postgres --encoding=UNICODE --owner=pgsqluser planetlab5')
1930 self.run_in_guest('psql -U pgsqluser planetlab5 -f ' + dump)
1931 ##starting httpd service
1932 self.run_in_guest('service httpd start')
1934 utils.header('Database restored from ' + dump)
1937 def create_ignore_steps():
1938 for step in TestPlc.default_steps + TestPlc.other_steps:
1939 # default step can have a plc qualifier
1941 step, qualifier = step.split('@')
1942 # or be defined as forced or ignored by default
1943 for keyword in ['_ignore','_force']:
1944 if step.endswith(keyword):
1945 step=step.replace(keyword,'')
1946 if step == SEP or step == SEPSFA :
1948 method = getattr(TestPlc,step)
1949 name = step + '_ignore'
1950 wrapped = ignore_result(method)
1951 # wrapped.__doc__ = method.__doc__ + " (run in ignore-result mode)"
1952 setattr(TestPlc, name, wrapped)
1955 # def ssh_slice_again_ignore (self): pass
1957 # def check_initscripts_ignore (self): pass
1959 def standby_1_through_20(self):
1960 """convenience function to wait for a specified number of minutes"""
1963 def standby_1(): pass
1965 def standby_2(): pass
1967 def standby_3(): pass
1969 def standby_4(): pass
1971 def standby_5(): pass
1973 def standby_6(): pass
1975 def standby_7(): pass
1977 def standby_8(): pass
1979 def standby_9(): pass
1981 def standby_10(): pass
1983 def standby_11(): pass
1985 def standby_12(): pass
1987 def standby_13(): pass
1989 def standby_14(): pass
1991 def standby_15(): pass
1993 def standby_16(): pass
1995 def standby_17(): pass
1997 def standby_18(): pass
1999 def standby_19(): pass
2001 def standby_20(): pass
2003 # convenience for debugging the test logic
2004 def yes(self): return True
2005 def no(self): return False
2006 def fail(self): return False