2121)
2222from ..exceptions import NoWorkingDeviceConnectionError
2323from ..signals import is_working_changed
24- from ..tasks import _TASK_NAME , _is_update_in_progress , update_config
24+ from ..tasks import (
25+ _acquire_update_config_lock ,
26+ _release_update_config_lock ,
27+ update_config ,
28+ )
2529from .utils import CreateConnectionsMixin
2630
2731Config = load_model ("config" , "Config" )
@@ -1026,26 +1030,19 @@ def _assert_applying_conf_test_command(mocked_exec):
10261030 @mock .patch .object (DeviceConnection , "update_config" )
10271031 @mock .patch .object (DeviceConnection , "get_working_connection" )
10281032 def test_device_update_config_in_progress (
1029- self , mocked_get_working_connection , update_config , mocked_sleep
1033+ self , mocked_get_working_connection , mocked_update_config , mocked_sleep
10301034 ):
10311035 conf = self ._prepare_conf_object ()
10321036
1033- with mock .patch ("celery.app.control.Inspect.active" ) as mocked_active :
1034- mocked_active .return_value = {
1035- "task" : [
1036- {
1037- "name" : _TASK_NAME ,
1038- "args" : [str (conf .device .pk )],
1039- "id" : "other-task-id" ,
1040- }
1041- ]
1042- }
1037+ with mock .patch (
1038+ "openwisp_controller.connection.tasks._acquire_update_config_lock" ,
1039+ return_value = False ,
1040+ ):
10431041 conf .config = {"general" : {"timezone" : "UTC" }}
10441042 conf .full_clean ()
10451043 conf .save ()
1046- mocked_active .assert_called_once ()
10471044 mocked_get_working_connection .assert_not_called ()
1048- update_config .assert_not_called ()
1045+ mocked_update_config .assert_not_called ()
10491046
10501047 @mock .patch ("time.sleep" )
10511048 @mock .patch .object (DeviceConnection , "update_config" )
@@ -1058,53 +1055,30 @@ def test_device_update_config_not_in_progress(
10581055 conf .device .deviceconnection_set .first ()
10591056 )
10601057
1061- with mock .patch ("celery.app.control.Inspect.active" ) as mocked_active :
1062- mocked_active .return_value = {
1063- "task" : [{"name" : _TASK_NAME , "args" : ["..." ], "id" : "other-task-id" }]
1064- }
1058+ with mock .patch (
1059+ "openwisp_controller.connection.tasks._acquire_update_config_lock" ,
1060+ return_value = True ,
1061+ ), mock .patch (
1062+ "openwisp_controller.connection.tasks._release_update_config_lock" ,
1063+ ):
10651064 conf .config = {"general" : {"timezone" : "UTC" }}
10661065 conf .full_clean ()
10671066 conf .save ()
1068- mocked_active .assert_called_once ()
10691067 mocked_get_working_connection .assert_called_once ()
10701068 mocked_update_config .assert_called_once ()
10711069
1072- def test_is_update_in_progress_ignores_current_task (self ):
1073- """Regression test: _is_update_in_progress must not count
1074- the calling task itself as a duplicate."""
1070+ def test_acquire_update_config_lock (self ):
1071+ """Test that the lock can be acquired and prevents duplicate acquisition."""
10751072 device_id = "test-device-id"
1076- current_task_id = "current-task-id"
1077- with mock .patch ("celery.app.control.Inspect.active" ) as mocked_active :
1078- mocked_active .return_value = {
1079- "worker" : [
1080- {
1081- "name" : _TASK_NAME ,
1082- "args" : [device_id ],
1083- "id" : current_task_id ,
1084- }
1085- ]
1086- }
1087- result = _is_update_in_progress (device_id , current_task_id = current_task_id )
1088- self .assertFalse (result )
1089-
1090- def test_is_update_in_progress_detects_other_task (self ):
1091- """_is_update_in_progress returns True when another task
1092- for the same device is active."""
1093- device_id = "test-device-id"
1094- with mock .patch ("celery.app.control.Inspect.active" ) as mocked_active :
1095- mocked_active .return_value = {
1096- "worker" : [
1097- {
1098- "name" : _TASK_NAME ,
1099- "args" : [device_id ],
1100- "id" : "other-task-id" ,
1101- }
1102- ]
1103- }
1104- result = _is_update_in_progress (
1105- device_id , current_task_id = "current-task-id"
1106- )
1107- self .assertTrue (result )
1073+ # First acquisition should succeed
1074+ self .assertTrue (_acquire_update_config_lock (device_id ))
1075+ # Second acquisition should fail (lock already held)
1076+ self .assertFalse (_acquire_update_config_lock (device_id ))
1077+ # After releasing, acquisition should succeed again
1078+ _release_update_config_lock (device_id )
1079+ self .assertTrue (_acquire_update_config_lock (device_id ))
1080+ # Cleanup
1081+ _release_update_config_lock (device_id )
11081082
11091083 @mock .patch (_connect_path )
11101084 def test_schedule_command_called (self , connect_mocked ):
0 commit comments