|
22 | 22 | from simcore_service_dynamic_scheduler.services.generic_scheduler._core import ( |
23 | 23 | get_core, |
24 | 24 | ) |
| 25 | +from simcore_service_dynamic_scheduler.services.generic_scheduler._errors import ( |
| 26 | + CannotCancelWhileWaitingForManualInterventionError, |
| 27 | +) |
25 | 28 | from simcore_service_dynamic_scheduler.services.generic_scheduler._models import ( |
26 | 29 | OperationName, |
27 | 30 | ProvidedOperationContext, |
@@ -207,6 +210,12 @@ async def create( |
207 | 210 | await asyncio.sleep(1e10) |
208 | 211 |
|
209 | 212 |
|
| 213 | +class _WaitManualInerventionBS(_RevertBS): |
| 214 | + @classmethod |
| 215 | + def wait_for_manual_intervention(cls) -> bool: |
| 216 | + return True |
| 217 | + |
| 218 | + |
210 | 219 | class _BaseExpectedStepOrder: |
211 | 220 | def __init__(self, *steps: type[BaseStep]) -> None: |
212 | 221 | self.steps = steps |
@@ -412,6 +421,15 @@ class _SF1(_SleepsForeverBS): ... |
412 | 421 | class _SF2(_SleepsForeverBS): ... |
413 | 422 |
|
414 | 423 |
|
| 424 | +# Below will wait for manual intervention after it fails on create |
| 425 | + |
| 426 | + |
| 427 | +class _WMI1(_WaitManualInerventionBS): ... |
| 428 | + |
| 429 | + |
| 430 | +class _WMI2(_WaitManualInerventionBS): ... |
| 431 | + |
| 432 | + |
415 | 433 | @pytest.mark.parametrize("app_count", [10]) |
416 | 434 | @pytest.mark.parametrize( |
417 | 435 | "operation, expected_order", |
@@ -893,9 +911,94 @@ async def test_repeating_step( |
893 | 911 | await _ensure_keys_in_store(selected_app, expected_keys=set()) |
894 | 912 |
|
895 | 913 |
|
896 | | -# TODO: test manual intervention |
897 | | -# -> manual intervention an anction that raises an error and has the flag for manual intervention |
898 | | -# -> manual intervention & fail on REVERT (what should happen?)-> this should be an unexpected thing |
| 914 | +@pytest.mark.parametrize("app_count", [10]) |
| 915 | +@pytest.mark.parametrize( |
| 916 | + "operation, expected_order, expected_keys", |
| 917 | + [ |
| 918 | + pytest.param( |
| 919 | + [ |
| 920 | + SingleStepGroup(_S1), |
| 921 | + ParallelStepGroup(_S2, _S3, _S4), |
| 922 | + SingleStepGroup(_WMI1), |
| 923 | + ], |
| 924 | + [ |
| 925 | + _CreateSequence(_S1), |
| 926 | + _CreateRandom(_S2, _S3, _S4), |
| 927 | + _CreateSequence(_WMI1), |
| 928 | + ], |
| 929 | + { |
| 930 | + "SCH:{schedule_id}", |
| 931 | + "SCH:{schedule_id}:GROUPS:test_op:0S:C", |
| 932 | + "SCH:{schedule_id}:GROUPS:test_op:1P:C", |
| 933 | + "SCH:{schedule_id}:GROUPS:test_op:2S:C", |
| 934 | + "SCH:{schedule_id}:STEPS:test_op:0S:C:_S1", |
| 935 | + "SCH:{schedule_id}:STEPS:test_op:1P:C:_S2", |
| 936 | + "SCH:{schedule_id}:STEPS:test_op:1P:C:_S3", |
| 937 | + "SCH:{schedule_id}:STEPS:test_op:1P:C:_S4", |
| 938 | + "SCH:{schedule_id}:STEPS:test_op:2S:C:_WMI1", |
| 939 | + }, |
| 940 | + id="s1-p3-s1(1mi)", |
| 941 | + ), |
| 942 | + pytest.param( |
| 943 | + [ |
| 944 | + SingleStepGroup(_S1), |
| 945 | + ParallelStepGroup(_S2, _S3, _S4), |
| 946 | + ParallelStepGroup(_WMI1, _WMI2, _S5, _S6, _S7), |
| 947 | + SingleStepGroup(_S8), # will be ignored |
| 948 | + ParallelStepGroup(_S9, _S10), # will be ignored |
| 949 | + ], |
| 950 | + [ |
| 951 | + _CreateSequence(_S1), |
| 952 | + _CreateRandom(_S2, _S3, _S4), |
| 953 | + _CreateRandom(_WMI1, _WMI2, _S5, _S6, _S7), |
| 954 | + ], |
| 955 | + { |
| 956 | + "SCH:{schedule_id}", |
| 957 | + "SCH:{schedule_id}:GROUPS:test_op:0S:C", |
| 958 | + "SCH:{schedule_id}:GROUPS:test_op:1P:C", |
| 959 | + "SCH:{schedule_id}:GROUPS:test_op:2P:C", |
| 960 | + "SCH:{schedule_id}:STEPS:test_op:0S:C:_S1", |
| 961 | + "SCH:{schedule_id}:STEPS:test_op:1P:C:_S2", |
| 962 | + "SCH:{schedule_id}:STEPS:test_op:1P:C:_S3", |
| 963 | + "SCH:{schedule_id}:STEPS:test_op:1P:C:_S4", |
| 964 | + "SCH:{schedule_id}:STEPS:test_op:2P:C:_S5", |
| 965 | + "SCH:{schedule_id}:STEPS:test_op:2P:C:_S6", |
| 966 | + "SCH:{schedule_id}:STEPS:test_op:2P:C:_S7", |
| 967 | + "SCH:{schedule_id}:STEPS:test_op:2P:C:_WMI1", |
| 968 | + "SCH:{schedule_id}:STEPS:test_op:2P:C:_WMI2", |
| 969 | + }, |
| 970 | + id="s1-p3-p5(2mi)", |
| 971 | + ), |
| 972 | + ], |
| 973 | +) |
| 974 | +async def test_wait_for_manual_intervention( |
| 975 | + preserve_caplog_for_async_logging: None, |
| 976 | + steps_call_order: list[tuple[str, str]], |
| 977 | + selected_app: FastAPI, |
| 978 | + register_operation: Callable[[OperationName, Operation], None], |
| 979 | + operation: Operation, |
| 980 | + operation_name: OperationName, |
| 981 | + expected_order: list[_BaseExpectedStepOrder], |
| 982 | + expected_keys: set[str], |
| 983 | +): |
| 984 | + register_operation(operation_name, operation) |
| 985 | + |
| 986 | + core = get_core(selected_app) |
| 987 | + schedule_id = await core.create(operation_name, {}) |
| 988 | + assert isinstance(schedule_id, ScheduleId) |
| 989 | + |
| 990 | + formatted_expected_keys = {k.format(schedule_id=schedule_id) for k in expected_keys} |
| 991 | + |
| 992 | + await _ensure_expected_order(steps_call_order, expected_order) |
| 993 | + |
| 994 | + await _ensure_keys_in_store(selected_app, expected_keys=formatted_expected_keys) |
| 995 | + |
| 996 | + # even if cancelled, state of waiting for manual intervention remains the same |
| 997 | + with pytest.raises(CannotCancelWhileWaitingForManualInterventionError): |
| 998 | + await core.cancel_schedule(schedule_id) |
| 999 | + # give some time for a "possible cancellation" to be processed |
| 1000 | + await asyncio.sleep(0.1) |
| 1001 | + await _ensure_keys_in_store(selected_app, expected_keys=formatted_expected_keys) |
899 | 1002 |
|
900 | 1003 |
|
901 | 1004 | # TODO: test something with initial_operation_context that requires data from a previous step, |
|
0 commit comments