@@ -272,6 +272,11 @@ def test_process_error_handling_has_error(self, mock_get_lock):
272272 lv_ac_db = lv_db_access .LiveAction .get_by_id (str (lv_ac_db .id ))
273273 self .assertEqual (lv_ac_db .status , action_constants .LIVEACTION_STATUS_CANCELED )
274274
275+ @mock .patch .object (
276+ coordination_service .NoOpDriver ,
277+ "get_members" ,
278+ mock .MagicMock (return_value = coordination_service .NoOpAsyncResult ("" )),
279+ )
275280 def test_workflow_engine_shutdown (self ):
276281 cfg .CONF .set_override (
277282 name = "service_registry" , override = True , group = "coordination"
@@ -289,8 +294,6 @@ def test_workflow_engine_shutdown(self):
289294 self .assertEqual (wf_ex_db .status , action_constants .LIVEACTION_STATUS_RUNNING )
290295 workflow_engine = workflows .get_engine ()
291296
292- # Manually add running workflow
293- workflow_engine ._handling_workflows = [str (ac_ex_db .id )]
294297 eventlet .spawn (workflow_engine .shutdown )
295298
296299 # Sleep for few seconds to ensure execution transitions to pausing.
@@ -318,7 +321,9 @@ def test_workflow_engine_shutdown(self):
318321 self .assertEqual (lv_ac_db .status , action_constants .LIVEACTION_STATUS_PAUSED )
319322
320323 workflow_engine = workflows .get_engine ()
321- eventlet .sleep (workflow_engine ._delay )
324+ workflow_engine ._delay = 0
325+ workflow_engine .start (False )
326+ eventlet .sleep (workflow_engine ._delay + 5 )
322327 lv_ac_db = lv_db_access .LiveAction .get_by_id (str (lv_ac_db .id ))
323328 self .assertTrue (
324329 lv_ac_db .status
@@ -328,3 +333,139 @@ def test_workflow_engine_shutdown(self):
328333 action_constants .LIVEACTION_STATUS_SUCCEEDED ,
329334 ]
330335 )
336+
337+ @mock .patch .object (
338+ coordination_service .NoOpDriver ,
339+ "get_members" ,
340+ mock .MagicMock (return_value = coordination_service .NoOpAsyncResult ("member-1" )),
341+ )
342+ def test_workflow_engine_shutdown_with_multiple_members (self ):
343+ cfg .CONF .set_override (
344+ name = "service_registry" , override = True , group = "coordination"
345+ )
346+ wf_meta = self .get_wf_fixture_meta_data (TEST_PACK_PATH , "sequential.yaml" )
347+ lv_ac_db = lv_db_models .LiveActionDB (action = wf_meta ["name" ])
348+ lv_ac_db , ac_ex_db = action_service .request (lv_ac_db )
349+
350+ # Assert action execution is running.
351+ lv_ac_db = lv_db_access .LiveAction .get_by_id (str (lv_ac_db .id ))
352+ self .assertEqual (lv_ac_db .status , action_constants .LIVEACTION_STATUS_RUNNING )
353+ wf_ex_db = wf_db_access .WorkflowExecution .query (
354+ action_execution = str (ac_ex_db .id )
355+ )[0 ]
356+ self .assertEqual (wf_ex_db .status , action_constants .LIVEACTION_STATUS_RUNNING )
357+ workflow_engine = workflows .get_engine ()
358+
359+ eventlet .spawn (workflow_engine .shutdown )
360+
361+ # Sleep for few seconds to ensure shutdown sequence completes.
362+ eventlet .sleep (5 )
363+
364+ lv_ac_db = lv_db_access .LiveAction .get_by_id (str (lv_ac_db .id ))
365+ self .assertEqual (lv_ac_db .status , action_constants .LIVEACTION_STATUS_RUNNING )
366+
367+ # Process task1.
368+ query_filters = {"workflow_execution" : str (wf_ex_db .id ), "task_id" : "task1" }
369+ t1_ex_db = wf_db_access .TaskExecution .query (** query_filters )[0 ]
370+ t1_ac_ex_db = ex_db_access .ActionExecution .query (
371+ task_execution = str (t1_ex_db .id )
372+ )[0 ]
373+
374+ workflows .get_engine ().process (t1_ac_ex_db )
375+ t1_ac_ex_db = ex_db_access .ActionExecution .query (
376+ task_execution = str (t1_ex_db .id )
377+ )[0 ]
378+ self .assertEqual (
379+ t1_ac_ex_db .status , action_constants .LIVEACTION_STATUS_SUCCEEDED
380+ )
381+
382+ lv_ac_db = lv_db_access .LiveAction .get_by_id (str (lv_ac_db .id ))
383+ self .assertEqual (lv_ac_db .status , action_constants .LIVEACTION_STATUS_RUNNING )
384+
385+ def test_workflow_engine_shutdown_with_service_registry_disabled (self ):
386+ cfg .CONF .set_override (
387+ name = "service_registry" , override = False , group = "coordination"
388+ )
389+ wf_meta = self .get_wf_fixture_meta_data (TEST_PACK_PATH , "sequential.yaml" )
390+ lv_ac_db = lv_db_models .LiveActionDB (action = wf_meta ["name" ])
391+ lv_ac_db , ac_ex_db = action_service .request (lv_ac_db )
392+
393+ # Assert action execution is running.
394+ lv_ac_db = lv_db_access .LiveAction .get_by_id (str (lv_ac_db .id ))
395+ self .assertEqual (lv_ac_db .status , action_constants .LIVEACTION_STATUS_RUNNING )
396+ wf_ex_db = wf_db_access .WorkflowExecution .query (
397+ action_execution = str (ac_ex_db .id )
398+ )[0 ]
399+ self .assertEqual (wf_ex_db .status , action_constants .LIVEACTION_STATUS_RUNNING )
400+ workflow_engine = workflows .get_engine ()
401+
402+ eventlet .spawn (workflow_engine .shutdown )
403+
404+ # Sleep for few seconds to ensure shutdown sequence completes.
405+ eventlet .sleep (5 )
406+
407+ # WFE doesn't pause the workflow, since service registry is disabled.
408+ lv_ac_db = lv_db_access .LiveAction .get_by_id (str (lv_ac_db .id ))
409+ self .assertEqual (lv_ac_db .status , action_constants .LIVEACTION_STATUS_RUNNING )
410+
411+ @mock .patch .object (
412+ coordination_service .NoOpDriver ,
413+ "get_lock" ,
414+ mock .MagicMock (return_value = coordination_service .NoOpLock (name = "noop" )),
415+ )
416+ def test_workflow_engine_concurrent_shutdown_and_start (self ):
417+ cfg .CONF .set_override (
418+ name = "service_registry" , override = True , group = "coordination"
419+ )
420+ wf_meta = self .get_wf_fixture_meta_data (TEST_PACK_PATH , "sequential.yaml" )
421+ lv_ac_db = lv_db_models .LiveActionDB (action = wf_meta ["name" ])
422+ lv_ac_db , ac_ex_db = action_service .request (lv_ac_db )
423+
424+ # Assert action execution is running.
425+ lv_ac_db = lv_db_access .LiveAction .get_by_id (str (lv_ac_db .id ))
426+ self .assertEqual (lv_ac_db .status , action_constants .LIVEACTION_STATUS_RUNNING )
427+ wf_ex_db = wf_db_access .WorkflowExecution .query (
428+ action_execution = str (ac_ex_db .id )
429+ )[0 ]
430+ self .assertEqual (wf_ex_db .status , action_constants .LIVEACTION_STATUS_RUNNING )
431+ workflow_engine = workflows .get_engine ()
432+
433+ workflow_engine ._delay = 0
434+ # Start and stop WFE simultaneously.
435+ eventlet .spawn_after (1 , workflow_engine .shutdown )
436+ eventlet .spawn_after (1 , workflow_engine .start , True )
437+
438+ lv_ac_db = lv_db_access .LiveAction .get_by_id (str (lv_ac_db .id ))
439+
440+ # Case 1: Shutdown routine acquires the lock first
441+ # Case 2: Startup routine acquires the lock first
442+ if lv_ac_db .status == action_constants .LIVEACTION_STATUS_PAUSING :
443+ # Process task1
444+ query_filters = {"workflow_execution" : str (wf_ex_db .id ), "task_id" : "task1" }
445+ t1_ex_db = wf_db_access .TaskExecution .query (** query_filters )[0 ]
446+ t1_ac_ex_db = ex_db_access .ActionExecution .query (
447+ task_execution = str (t1_ex_db .id )
448+ )[0 ]
449+
450+ workflows .get_engine ().process (t1_ac_ex_db )
451+ # Startup sequence won't proceed until shutdown routine completes.
452+ # Assuming shutdown sequence is complete, start up sequence will resume the workflow.
453+ eventlet .sleep (workflow_engine ._delay + 5 )
454+ lv_ac_db = lv_db_access .LiveAction .get_by_id (str (lv_ac_db .id ))
455+ self .assertTrue (
456+ lv_ac_db .status
457+ in [
458+ action_constants .LIVEACTION_STATUS_RESUMING ,
459+ action_constants .LIVEACTION_STATUS_RUNNING ,
460+ action_constants .LIVEACTION_STATUS_SUCCEEDED ,
461+ ]
462+ )
463+ else :
464+ coordination_service .NoOpDriver .get_members = mock .MagicMock (
465+ return_value = coordination_service .NoOpAsyncResult ("member-1" )
466+ )
467+ eventlet .sleep (workflow_engine ._delay + 5 )
468+ lv_ac_db = lv_db_access .LiveAction .get_by_id (str (lv_ac_db .id ))
469+ self .assertEqual (
470+ lv_ac_db .status , action_constants .LIVEACTION_STATUS_RUNNING
471+ )
0 commit comments