def _write_task_text(self, fd, attr, indent, view, dependency, time, alarm_name, manual_dependency=None): assert (view is not None) path = '.'.join(view.path[1:]) space = self.__spacing fd.write(f'{space*indent}<task name="{path}"{attr}') if alarm_name: self.__alarms_used.add(alarm_name) fd.write(f' cycledefs="{alarm_name}"') more_attr = view.get('Rocoto_attr', '') if more_attr: fd.write(f' {more_attr}') fd.write('>\n') dep = self._as_rocoto_dep(simplify(dependency), view.path) dep_count = (dep != TRUE_DEPENDENCY) + (time > timedelta.min) if 'Rocoto' in view: for line in view.Rocoto.splitlines(): fd.write(f'{space*(indent+1)}{line}\n') else: _logger.warning( f'{view.task_path_var}: no Rocoto item; <task> will be invalid.' ) if manual_dependency is not None: for line in manual_dependency.splitlines(): fd.write(f'{space*(indent+1)}{line}\n') fd.write(space * indent + '</task>\n') return if not dep_count: fd.write(space * (indent + 1) + '<!-- no dependencies -->\n') if dep_count: fd.write(space * (indent + 1) + '<dependency>\n') if dep_count > 1: fd.write(space * (indent + 2) + '<and>\n') if dep is not TRUE_DEPENDENCY: _to_rocoto_dep_impl(dep, fd, indent + 1 + dep_count) if time > timedelta.min: _to_rocoto_time_dep(time, fd, indent + 1 + dep_count) if dep_count > 1: fd.write(space * (indent + 2) + '</and>\n') if dep_count: fd.write(space * (indent + 1) + '</dependency>\n') fd.write(space * indent + '</task>\n')
def _convert_item(self, fd, indent, view, trigger, complete, time, alarm_name): if view.get('Disable', False): return if view.get('Dummy', False): return trigger = trigger & view.get_trigger_dep() complete = complete | view.get_complete_dep() time = max(time, view.get_time_dep()) space = self.__spacing if 'AlarmName' in view: if alarm_name: raise ValueError( '{view.task_path_var}: nested alarms are not supported in crow.metascheduler.to_rocoto()' ) else: alarm_name = view.AlarmName dep = trigger & ~complete dep = simplify(dep) if view.is_task(): maxtries = int( view.get('max_tries', self.suite.Rocoto.get('max_tries', 0))) attr = f' maxtries="{maxtries}"' if maxtries else '' self._write_task_text(fd, attr, indent, view, dep, time, alarm_name) return self.__dummy_var_count += 1 dummy_var = "dummy_var_" + str(self.__dummy_var_count) path = _xml_quote('.'.join(view.path[1:])) if not isinstance(view, Suite): fd.write(f'''{space*indent}<metatask name="{path}"''') more_attr = view.get('Rocoto_attr', '') if more_attr: fd.write(f' {more_attr}') fd.write(f'''> {space*indent} <var name="{dummy_var}">DUMMY_VALUE</var> ''') for key, child in view.items(): if key in ['up', 'this']: continue if not isinstance(child, SuiteView): continue if child.path[1:] == ['final']: if not child.is_task(): raise RocotoConfigError( 'The "final" task must be a Task, not a ' + type(child.viewed).__name__) self.__final_task = child else: self._convert_item(fd, indent + 1, child, trigger, complete, time, alarm_name) if not isinstance(view, Suite): fd.write(f'{space*indent}</metatask>\n')
def test_simp_extended_expr(self): self.assertEqual(ag.simplify((self.DEP1 | self.DEP2 | self.DEP4) & \ (self.DEP1 | self.DEP3 | self.DEP4)), \ self.DEP1 | self.DEP2 & self.DEP3 | self.DEP4)
def test_simp_gobbledygook(self): self.assertEqual(ag.simplify(~self.DEP2 & ~(~self.DEP1 | ~self.DEP2)), FALSE_DEPENDENCY)
def test_simp_not_not_a_or_not_b(self): self.assertEqual(ag.simplify(~(~self.DEP1 | ~self.DEP2)), self.DEP1 & self.DEP2)
def test_simp_a_and_not_a(self): self.assertEqual(ag.simplify(~self.DEP1 & self.DEP1), FALSE_DEPENDENCY)
def test_simp_a_or_not_a(self): self.assertEqual(ag.simplify(~self.DEP1 | self.DEP1), TRUE_DEPENDENCY)
def _handle_final_task(self, fd, indent): # Find and validate the "final" task: final = None if 'final' in self.suite: final = self.suite.final if not final.is_task(): raise RocotoConfigError( 'For a workflow suite to be expressed in Rocoto, it ' 'must have a "final" task') for elem in ['Trigger', 'Complete', 'Time']: if elem in final: raise RocotoConfigError( f'{elem}: In a Rocoto workflow, the "final" task ' 'must have no dependencies.') if final is None: if self.__completes: raise RocotoConfigError( 'If a workflow suite has any "complete" conditions, ' 'then it must have a "final" task with no dependencies.') else: return # No final task, and no need to generate one if len(self.__alarms_used) < 2: # There are no alarms in use, so there is only one final task. # Generate dependency for it: fd.write( f'\n{self.__spacing*indent}<!-- The final task dependencies are automatically generated to handle Complate and Trigger conditions. -->\n\n' ) dep = self._final_task_deps_no_alarms(self.suite) self._write_task_text(fd, ' final="true"', indent, final, dep, timedelta.min, '') return fd.write( f'\n{self.__spacing*indent}<!-- These final tasks are automatically generated to handle Complate and Trigger conditions, and alarms. -->\n\n' ) # There are alarms, so things get... complicated. manual_dependency = f'''<dependency> {self.__spacing*indent}<and> {self.__spacing*(indent+1)}<!-- All tasks must be complete or invalid for this cycle -->\n''' alarms = set(self.__alarms_used) alarms.add('') # Reverse-sort the alarm names so the final tasks show up in # the same order each time, with the task "final" at the end. for alarm_name in reversed(sorted(alarms)): #print(f'find final for {alarm_name}') dep = self._final_task_deps_for_alarm(self.suite, alarm_name) dep = simplify(dep) task_name = f'final_for_{alarm_name}' if alarm_name else 'final_no_alarm' new_task = copy(self.suite.final.viewed) new_task['_is_final_'] = True new_task['AlarmName'] = alarm_name invalidate_cache(self.suite, recurse=True) self.suite.viewed[task_name] = new_task new_task_view = self.suite[task_name] del new_task self.__all_defined.add( SuitePath([_ZERO_DT] + new_task_view.path[1:])) assert (dep is not FALSE_DEPENDENCY) self._write_task_text(fd, '', indent, new_task_view, dep, timedelta.min, alarm_name) manual_dependency += f'''{self.__spacing*(indent+1)}<or> {self.__spacing*(indent+2)}<taskdep task="{task_name}"/> {self.__spacing*(indent+2)}<not><taskvalid task="{task_name}"/></not> {self.__spacing*(indent+1)}</or>\n''' manual_dependency += f'{self.__spacing*indent}</and>\n</dependency>\n' self._write_task_text(fd, ' final="true"', indent, final, TRUE_DEPENDENCY, timedelta.min, '', manual_dependency=manual_dependency)
def _as_rocoto_dep(self, dep, defining_path): dep = dep.copy_dependencies() dep = self.remove_undefined_tasks(dep) dep = self._rocotoify_dep(dep, defining_path) dep = simplify(dep) return dep