def augment_experiment(experiment, extension, prefix=None): # XXX: prefixing is not complete, # all references to tasklists / targets within the # extension experiment should also be prefixed. res = experiment.xpath('/experiment/targets') if not res: raise ExperimentSyntaxError("Element 'targets' missing in experiment") targets = res[0] res = extension.xpath('/experiment/targets') if not res: raise ExperimentSyntaxError("Element 'targets' missing in experiment") ext_targets = res[0] if prefix is not None: for t in ext_targets: t.set('name', "{0}.{1}".format(prefix, t.get('name', '_unknown'))) targets.extend(list(ext_targets)) del targets, ext_targets res = experiment.xpath('/experiment/tasklists') if not res: raise ExperimentSyntaxError("Element 'tasklists' missing in experiment") tasklists = res[0] res = extension.xpath('/experiment/tasklists') if not res: raise ExperimentSyntaxError("Element 'tasklists' missing in experiment") ext_tasklists = res[0] if prefix is not None: for t in ext_tasklists: t.set('name', "{0}.{1}".format(prefix, t.get('name', '_unknown'))) tasklists.extend(list(ext_tasklists)) if extension.find('/experiment/steps'): logging.warn("Extension tasklist has 'steps'. These steps will not be executed")
def process_includes(experiment_xml, parent_filename, env=None, memo=None): includes = experiment_xml.xpath('/experiment/include') for el in includes: filename = el.get('file') if filename is None: raise ExperimentSyntaxError("Attribute 'file' missing in include") # XXX: Implement defaulting, shell style? filename = os.path.expandvars(filename) # Search relative paths relative to parent document if not os.path.isabs(filename): parent_dir = os.path.dirname(os.path.realpath(parent_filename)) filename = os.path.join(parent_dir, filename) filename = os.path.realpath(filename) if memo is not None and filename in memo: raise ExperimentSyntaxError("recursive include detected") xml_parser = lxml.etree.XMLParser(remove_blank_text=True) # XXX: we only try one filename, but we may want to specify include # locations, like compilers do. extension_xml = lxml.etree.parse(filename, parser=xml_parser) # XXX: Maybe we want to keep the comments? # XXX: If that is then case, our processing logic needs to be more careful. lxml.etree.strip_elements(extension_xml, [lxml.etree.Comment]) # XXX: some validation wouldn't hurt here new_memo = {filename} if memo is not None: new_memo.update(memo) establish_names(extension_xml) process_includes(extension_xml, filename, memo=new_memo) prefix = el.get('prefix') augment_experiment(experiment_xml, extension_xml, prefix)
def run_cleanup(self, tasklist_xml, tasklists_env, var_env): cleanup_task = None cleanup_name = tasklist_xml.get('cleanup') if cleanup_name is not None: cleanup_task = tasklists_env.get(cleanup_name) if cleanup_task is None: raise ExperimentSyntaxError("cleanup task %s not found\n" % (cleanup_name,)) if cleanup_task is None: return #coro = self._run_list(cleanup_task, self.testbed, tasklists_env, var_env) coro = self.run_tasklist(cleanup_task, tasklists_env, var_env, None) try: yield from asyncio.async(coro) except asyncio.TimeoutError: # XXX: be more verbose! logging.warning( "Cleanup tasklist %s on node %s timed out", cleanup_name, self.name) except StopExperimentException as e: logging.warning( "Cleanup tasklist %s on node %s stopped (%s)", cleanup_name, self.name, e.scope) except ExperimentExecutionError as e: logging.warning( "Cleanup tasklist %s on node %s failed (%s)", cleanup_name, self.name, e.message)
def _step_tasklist(self, step_xml, tasklists_env, var_env={}): targets_def = step_xml.get("targets") if targets_def is None: logging.warn("step has no targets, skipping") return tasklist_name = step_xml.get("tasklist") if tasklist_name is None: logging.warn("step has no tasklist, skipping") return tasklist = tasklists_env.get(tasklist_name) if tasklist is None: raise ExperimentSyntaxError("Tasklist '%s' not found" % (tasklist_name,)) background = False bg_str = step_xml.get('background') if bg_str is not None and bg_str.lower() == 'true': background = True delay = get_delay_attr(step_xml, 'start') stop = get_delay_attr(step_xml, 'stop') logging.info("delay for step with tl %s is %s", tasklist_name, delay) composedEnv = {} composedEnv.update(var_env) composedEnv.update(helper.exportEnv(step_xml)) self.schedule_tasklist(targets_def, tasklist, tasklists_env, background, delay, composedEnv, stop)
def _process_pl_slice(self, el): api_url = find_text(el, 'apiurl') if not api_url: raise ExperimentSyntaxError("Planetlab slice requires 'apiurl'") slicename = find_text(el, "slicename") if not slicename: raise ExperimentSyntaxError("Planetlab slice requires 'slicename'") groupname = el.get("name") if groupname is None: groupname = slicename server = xmlrpc.client.ServerProxy(api_url) user = find_text(el, 'user') if user is None: raise ExperimentSyntaxError("Planetlab slice requires 'user'") pw = find_text(el, 'password') if pw is None: # XXX: check if interaction is allowed pw = getpass.getpass("Planetlab Password: "******"password" logging.info("Making RPC call to planetlab") try: node_ids = server.GetSlices(auth, [slicename], ['node_ids'])[0]['node_ids'] node_hostnames = [node['hostname'] for node in server.GetNodes(auth, node_ids, ['hostname'])] except Exception as e: raise ExperimentSetupError("PlanetLab API call failed") logging.info("Got response from planetlab") members = [] for num, hostname in enumerate(node_hostnames): name = "_pl_" + slicename + "." + str(num) cfg = E.target( {"type": "ssh", "name": name}, E.host(hostname), E.user(slicename)) self.nodes[name] = SSHNode(cfg, testbed=self) members.append(name) self.groups[groupname] = members
def __init__(self, node_xml, testbed): super().__init__(node_xml, testbed) if self.name is None: raise ExperimentSyntaxError("Node name must be given") self.host = find_text(node_xml, 'host') if self.host is None: raise ExperimentSyntaxError("SSH target requires host") self.user = find_text(node_xml, 'user') if self.user is None: raise ExperimentSyntaxError("SSH target requires user") self.port = find_text(node_xml, 'port') extra = find_text(node_xml, 'extra-args') if extra is None: self.extra = [] else: self.extra = shlex.split(extra) if self.port is None: self.port = 22 self.target = "%s@%s" % (self.user, self.host)
def __init__(self, experiment_xml, settings): self.experiment_xml = experiment_xml self.settings = settings self.targets = self.experiment_xml.findall('/targets/target') self.steps = self.experiment_xml.find("steps") if self.steps is None: raise ExperimentSyntaxError("Element 'steps' missing. Did you try to execute an extension library?") self.tasklists_env = {} for x in experiment_xml.xpath("/experiment/tasklists/tasklist[@name]"): self.tasklists_env[x.get('name')] = x
def _process_group(self, els): members = [] for el in els: refname = el.get('ref') if refname is not None: members.append(refname) continue name = el.get('name') if name is None: raise ExperimentSyntaxError("target must have ref or name") members.append(name) # XXX: pass environment of group down to the peers of the group self._process_declaration(el) self.groups[els.get('name')] = members
def _step_teardown(self, step_xml, tasklists_env, var_env): targets_def = step_xml.get("targets") if targets_def is None: logging.warn("register-teardown has no targets, skipping") return tasklist_name = step_xml.get("tasklist") if tasklist_name is None: logging.warn("register-teardown has no tasklist, skipping") return tasklist = tasklists_env.get(tasklist_name) if tasklist is None: raise ExperimentSyntaxError("Tasklist '%s' not found" % (tasklist_name,)) logging.info("Registering teardown for '%s' on '%s'", tasklist_name, targets_def) composedEnv = {} composedEnv.update(var_env) composedEnv.update(helper.exportEnv(step_xml)) self.testbed.teardowns.append((targets_def, tasklist, composedEnv))
def run_tasklist(self, tasklist_xml, tasklists_env, var_env, stop_time): list_name = tasklist_xml.get('name', '(unnamed)') logging.info("running tasklist '%s'", list_name) # actual tasks, with declarations stripped error_policy = tasklist_xml.get('on-error') if error_policy is None: error_policy = 'stop-tasklist' timeout_str = tasklist_xml.get('timeout') timeout = stop_time if timeout_str is not None: timeout_tl = isodate.parse_duration(timeout_str).total_seconds() if timeout is None: timeout = timeout_tl else: times = [timeout_tl, timeout] timeout = min(times) coro = self._run_list(tasklist_xml, self.testbed, tasklists_env, var_env) try: logging.info( "Running tasklist %s with timeout of %s.", list_name, timeout) yield from asyncio.wait_for(asyncio.async(coro), timeout) except asyncio.TimeoutError: # XXX: be more verbose! logging.warning( "Tasklist %s on node %s timed out", list_name, self.name) # XXX: cleanup! except StopExperimentException as e: if e.scope == 'stop-experiment': raise # otherwise, we ignore the exception except ExperimentExecutionError as e: logging.error("Tasklist execution (%s on %s) raised exception (%s)", list_name, self.name, e.message) if error_policy in ('stop-experiment', 'stop-step', 'stop-tasklist'): yield from self.run_cleanup(tasklist_xml, tasklists_env, var_env) raise StopExperimentException(error_policy) else: raise ExperimentSyntaxError("Unexpected error policy '%s'" % (error_policy,)) yield from self.run_cleanup(tasklist_xml, tasklists_env, var_env)
def run_loop_listing(self, loop_xml, tasklists_env, listing, listParam, var_env): nested_ec = ExecutionContext(self.testbed) if ":" in listing: rangeGiven = listing.split(":") if len(rangeGiven) == 2 and helper.isInt(rangeGiven[0]) and helper.isInt(rangeGiven[1]): loopList = range(int(rangeGiven[0]), int(rangeGiven[1])+1) loopList = map(str, loopList) else: raise ExperimentSyntaxError("Invalid Range declaration '%s'" % (loop_xml.tag,)) else: loopList = listing.split(" ") for x in loopList: loop_env = {} loop_env[listParam] = x composedEnv = {} composedEnv.update(var_env) composedEnv.update(loop_env) nested_ec.var = composedEnv for step in list(loop_xml): yield from nested_ec.run_step(step, tasklists_env, composedEnv) yield from nested_ec.join()
def _run_task(self, task_xml, testbed, tasklists_env, var_env): name = task_xml.get('name', '(unnamed-task)') if task_xml.get('enabled', 'true').lower() == 'false': logging.info("Task %s disabled", name) return if task_xml.tag == 'run': yield from self._run_task_run(task_xml, var_env) return if task_xml.tag == 'get': source = find_text(task_xml, 'source') destination = find_text(task_xml, 'destination') # XXX: Just replace all environment variables source = source.replace("$GPLMT_TARGET", self.name) destination = destination.replace("$GPLMT_TARGET", self.name) yield from self.get(source, destination) return if task_xml.tag == 'put': source = find_text(task_xml, 'source') destination = find_text(task_xml, 'destination') kp_str = task_xml.attrib.get("keep") # XXX: Just replace all environment variables source = source.replace("$GPLMT_TARGET", self.name) destination = destination.replace("$GPLMT_TARGET", self.name) if kp_str is None or kp_str.lower() == 'false': #Check for invalid characters, whitelisting valid = re.compile("^([\.a-zA-Z][\-\.a-zA-Z]+)$") if valid.match(destination): composedEnv = [] tasklist = lxml.etree.Element("tasklist", name="cleanup")#on error? child1 = lxml.etree.SubElement(tasklist, "seq") child2 = lxml.etree.SubElement(child1, "run", name="_anon2") child2.text = ("rm " + destination) self.testbed.teardowns.append((self.name, tasklist, composedEnv)) else: logging.warning("no automated removal, invalid characters in destination: %s", destination) yield from self.put(source, destination) return if task_xml.tag in ('sequence', 'seq'): for child_task in task_xml: yield from self._run_task(child_task, testbed, tasklists_env, var_env) return if task_xml.tag == 'fail': raise ExperimentExecutionError("user-requested fail") if task_xml.tag == 'call': tl = task_xml.get('tasklist') if tl is None: raise ExperimentSyntaxError("no tasklist name in 'call'") tasklist_xml = tasklists_env.get(tl) if tasklist_xml is None: raise ExperimentSyntaxError("Tasklist '%s' not defined" % (tl,)) yield from self.run_tasklist(tasklist_xml, tasklists_env, var_env) if task_xml.tag in ('par', 'parallel'): parallel_tasks = [] for child_task in task_xml: coro = self._run_task(child_task, testbed, tasklists_env, var_env) task = asyncio.async(coro) parallel_tasks.append(task) done, pending = yield from asyncio.wait(parallel_tasks) for task in done: task.result() return
def run_step(self, step_xml, tasklists_env, var_env={}): if step_xml.tag not in self._step_table: raise ExperimentSyntaxError("Invalid step '%s'" % (step_xml.tag,)) step_method = self._step_table[step_xml.tag] yield from step_method(self, step_xml, tasklists_env, var_env)