|
52 | 52 | RunResponse, |
53 | 53 | SuccessfulRunResponse, |
54 | 54 | ) |
| 55 | +from .workflow_progress import WorkflowProgressDisplay |
| 56 | + |
55 | 57 |
|
56 | 58 | if TYPE_CHECKING: |
57 | 59 | from planemo.cli import PlanemoCliContext |
@@ -788,25 +790,140 @@ def wait_for_invocation_and_jobs( |
788 | 790 | msg = f"Failed to run workflow, at least one job is in [{job_state}] state." |
789 | 791 | error_message = msg if not error_message else f"{error_message}. {msg}" |
790 | 792 | else: |
791 | | - # wait for possible subworkflow invocations |
792 | | - invocation = user_gi.invocations.show_invocation(invocation_id) |
793 | | - for step in invocation["steps"]: |
794 | | - if step.get("subworkflow_invocation_id") is not None: |
795 | | - final_invocation_state, job_state, error_message = wait_for_invocation_and_jobs( |
796 | | - ctx, |
797 | | - invocation_id=step["subworkflow_invocation_id"], |
798 | | - history_id=history_id, |
799 | | - user_gi=user_gi, |
800 | | - no_wait=no_wait, |
801 | | - polling_backoff=polling_backoff, |
802 | | - ) |
803 | | - if final_invocation_state != "scheduled" or job_state not in ("ok", "skipped"): |
804 | | - return final_invocation_state, job_state, error_message |
| 793 | + for subworkflow_invocation_id in subworkflow_invocation_ids(user_gi, invocation_id): |
| 794 | + final_invocation_state, job_state, error_message = wait_for_invocation_and_jobs( |
| 795 | + ctx, |
| 796 | + invocation_id=subworkflow_invocation_id, |
| 797 | + history_id=history_id, |
| 798 | + user_gi=user_gi, |
| 799 | + no_wait=no_wait, |
| 800 | + polling_backoff=polling_backoff, |
| 801 | + ) |
| 802 | + if final_invocation_state != "scheduled" or job_state not in ("ok", "skipped"): |
| 803 | + return final_invocation_state, job_state, error_message |
805 | 804 |
|
806 | 805 | ctx.vlog(f"The final state of all jobs and subworkflow invocations for invocation [{invocation_id}] is 'ok'") |
807 | 806 | return final_invocation_state, job_state, error_message |
808 | 807 |
|
809 | 808 |
|
| 809 | +def wait_for_invocation_and_jobs_tracked( |
| 810 | + ctx, |
| 811 | + invocation_id: str, |
| 812 | + history_id: str, |
| 813 | + user_gi: GalaxyInstance, |
| 814 | + no_wait: bool, |
| 815 | + polling_backoff: int, |
| 816 | + workflow_progress_display: WorkflowProgressDisplay, |
| 817 | +): |
| 818 | + |
| 819 | + def summary_job_state(job_states_summary: Optional[Dict]): |
| 820 | + states = (job_states_summary or {}).get("states").copy() |
| 821 | + states.pop("ok").pop("skipped") |
| 822 | + if states: |
| 823 | + return next(states.keys()) |
| 824 | + else: |
| 825 | + return "ok" |
| 826 | + |
| 827 | + def summarize(invocation_id: str): |
| 828 | + invocation = _retry_on_timeouts(ctx, user_gi, lambda gi: gi.invocations.show_invocation(invocation_id)) |
| 829 | + invocation_jobs = _retry_on_timeouts( |
| 830 | + ctx, user_gi, lambda gi: gi.invocations.get_invocation_summary(invocation_id) |
| 831 | + ) |
| 832 | + return invocation, invocation_jobs |
| 833 | + |
| 834 | + ctx.vlog("Waiting for invocation [%s]" % invocation_id) |
| 835 | + done_polling = False |
| 836 | + last_invocation = None |
| 837 | + last_invocation_jobs = None |
| 838 | + last_exception = None |
| 839 | + while not done_polling: |
| 840 | + try: |
| 841 | + last_invocation, last_invocation_jobs = summarize(invocation_id) |
| 842 | + workflow_progress_display.handle_invocation(last_invocation, last_invocation_jobs) |
| 843 | + final_invocation_state = workflow_progress_display.workflow_progress.invocation_state |
| 844 | + if workflow_progress_display.workflow_progress.invocation_scheduling_terminal and no_wait: |
| 845 | + done_polling = True |
| 846 | + break |
| 847 | + if not no_wait and workflow_progress_display.workflow_progress.jobs_terminal: |
| 848 | + done_polling = True |
| 849 | + break |
| 850 | + time.sleep(1) |
| 851 | + except Exception as e: |
| 852 | + last_exception = e |
| 853 | + done_polling = True |
| 854 | + |
| 855 | + final_invocation_state = "new" if not last_invocation else last_invocation["state"] |
| 856 | + job_state = summary_job_state(last_invocation_jobs) |
| 857 | + ctx.vlog(f"Final state of invocation {invocation_id} is [{final_invocation_state}]") |
| 858 | + |
| 859 | + def workflow_in_error() -> str: |
| 860 | + error_message = None |
| 861 | + if last_exception: |
| 862 | + ctx.vlog(f"Problem waiting on invocation: {str(last_exception)}") |
| 863 | + error_message = f"Final state of invocation {invocation_id} is [{final_invocation_state}]" |
| 864 | + |
| 865 | + if final_invocation_state != "scheduled": |
| 866 | + msg = f"Failed to run workflow, invocation ended in [{final_invocation_state}] state." |
| 867 | + ctx.vlog(msg) |
| 868 | + error_message = msg if not error_message else f"{error_message}. {msg}" |
| 869 | + |
| 870 | + if job_state != "ok": |
| 871 | + msg = f"Failed to run workflow, at least one job is in [{job_state}] state." |
| 872 | + ctx.vlog(msg) |
| 873 | + error_message = msg if not error_message else f"{error_message}. {msg}" |
| 874 | + |
| 875 | + return error_message |
| 876 | + |
| 877 | + error_message = workflow_in_error() |
| 878 | + if error_message: |
| 879 | + summarize_history(ctx, user_gi, history_id) |
| 880 | + return final_invocation_state, job_state, error_message |
| 881 | + |
| 882 | + if not no_wait: |
| 883 | + subworkflow_ids = subworkflow_invocation_ids(user_gi, invocation_id) |
| 884 | + workflow_progress_display.register_subworkflow_invocation_ids(subworkflow_ids) |
| 885 | + done_polling = workflow_progress_display.all_subworkflows_complete() |
| 886 | + while not done_polling: |
| 887 | + try: |
| 888 | + a_subworkflow_invocation_id = workflow_progress_display.an_incomplete_subworkflow_id() |
| 889 | + subworkflow_subworkflow_invocation_ids = subworkflow_invocation_ids( |
| 890 | + user_gi, a_subworkflow_invocation_id |
| 891 | + ) |
| 892 | + workflow_progress_display.register_subworkflow_invocation_ids(subworkflow_subworkflow_invocation_ids) |
| 893 | + |
| 894 | + last_invocation, last_invocation_jobs = summarize(invocation_id) |
| 895 | + workflow_progress_display.handle_subworkflow_invocation(last_invocation, last_invocation_jobs) |
| 896 | + |
| 897 | + scheduling_terminal = workflow_progress_display.subworkflow_progress.invocation_scheduling_terminal |
| 898 | + jobs_terminal = workflow_progress_display.subworkflow_progress.jobs_terminal |
| 899 | + if scheduling_terminal and jobs_terminal: |
| 900 | + workflow_progress_display.complete_subworkflow(a_subworkflow_invocation_id) |
| 901 | + done_polling = workflow_progress_display.all_subworkflows_complete() |
| 902 | + except Exception as e: |
| 903 | + last_exception = e |
| 904 | + done_polling = True |
| 905 | + |
| 906 | + final_invocation_state = "new" if not last_invocation else last_invocation["state"] |
| 907 | + job_state = summary_job_state(last_invocation_jobs) |
| 908 | + ctx.vlog(f"Final state of invocation {invocation_id} is [{final_invocation_state}]") |
| 909 | + |
| 910 | + error_message = workflow_in_error() |
| 911 | + if error_message: |
| 912 | + summarize_history(ctx, user_gi, history_id) |
| 913 | + return final_invocation_state, job_state, error_message |
| 914 | + |
| 915 | + return final_invocation_state, job_state, error_message |
| 916 | + |
| 917 | + |
| 918 | +def subworkflow_invocation_ids(user_gi, invocation_id: str): |
| 919 | + invocation = user_gi.invocations.show_invocation(invocation_id) |
| 920 | + subworkflow_invocation_ids = [] |
| 921 | + for step in invocation["steps"]: |
| 922 | + if step.get("subworkflow_invocation_id") is not None: |
| 923 | + subworkflow_invocation_ids.append(step["subworkflow_invocation_id"]) |
| 924 | + return subworkflow_invocation_ids |
| 925 | + |
| 926 | + |
810 | 927 | def _wait_for_invocation(ctx, gi, invocation_id, polling_backoff=0): |
811 | 928 | def state_func(): |
812 | 929 | return _retry_on_timeouts(ctx, gi, lambda gi: gi.invocations.show_invocation(invocation_id)) |
|
0 commit comments