Skip to content

Commit 487641b

Browse files
authored
[fix] Ignore "connection not working" notifications #314
Added functionality to ignore "connection not working" notifications generated due to connectivity issues. Fixes #314
1 parent 75d7ed1 commit 487641b

File tree

6 files changed

+81
-2
lines changed

6 files changed

+81
-2
lines changed

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2526,6 +2526,7 @@ it is not sent if the response was not successful.
25262526
- ``old_is_working``: previous value of ``DeviceConnection.is_working``,
25272527
either ``None`` (for new connections), ``True`` or ``False``
25282528
- ``failure_reason``: error message explaining reason for failure in establishing connection
2529+
- ``old_failure_reason``: previous value of ``DeviceConnection.failure_reason``
25292530

25302531
This signal is emitted every time ``DeviceConnection.is_working`` changes.
25312532

openwisp_controller/connection/apps.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ class ConnectionConfig(AppConfig):
2121
name = 'openwisp_controller.connection'
2222
label = 'connection'
2323
verbose_name = _('Network Device Credentials')
24+
# List of reasons for which notifications should
25+
# not be generated if a device connection errors out.
26+
# Intended to be used internally by OpenWISP to
27+
# ignore notifications generated due to connectivity issues.
28+
_ignore_connection_notification_reasons = []
2429

2530
def ready(self):
2631
"""
@@ -106,12 +111,22 @@ def _is_update_in_progress(cls, device_pk):
106111

107112
@classmethod
108113
def is_working_changed_receiver(
109-
cls, instance, is_working, old_is_working, **kwargs
114+
cls,
115+
instance,
116+
is_working,
117+
old_is_working,
118+
failure_reason,
119+
old_failure_reason,
120+
**kwargs,
110121
):
111122
# if old_is_working is None, it's a new device connection which wasn't
112123
# used yet, so nothing is really changing and we won't notify the user
113124
if old_is_working is None:
114125
return
126+
# don't send notification if error occurred due to connectivity issues
127+
for ignore_reason in cls._ignore_connection_notification_reasons:
128+
if ignore_reason in failure_reason or ignore_reason in old_failure_reason:
129+
return
115130
device = instance.device
116131
notification_opts = dict(sender=instance, target=device)
117132
if not is_working:

openwisp_controller/connection/base/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ class Meta:
248248
def __init__(self, *args, **kwargs):
249249
super().__init__(*args, **kwargs)
250250
self._initial_is_working = self.is_working
251+
self._initial_failure_reason = self.failure_reason
251252

252253
def clean(self):
253254
cred_org = self.credentials.organization
@@ -340,13 +341,15 @@ def save(self, *args, **kwargs):
340341
if self.is_working != self._initial_is_working:
341342
self.send_is_working_changed_signal()
342343
self._initial_is_working = self.is_working
344+
self._initial_failure_reason = self.failure_reason
343345

344346
def send_is_working_changed_signal(self):
345347
is_working_changed.send(
346348
sender=self.__class__,
347349
is_working=self.is_working,
348350
old_is_working=self._initial_is_working,
349351
failure_reason=self.failure_reason,
352+
old_failure_reason=self._initial_failure_reason,
350353
instance=self,
351354
)
352355

openwisp_controller/connection/signals.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,11 @@
22

33
is_working_changed = Signal()
44
is_working_changed.__doc__ = """
5-
Providing araguments: ['is_working', 'old_is_working', 'instance', 'failure_reason']
5+
Providing araguments: [
6+
'is_working',
7+
'old_is_working',
8+
'instance',
9+
'failure_reason',
10+
'old_failure_reason'
11+
]
612
"""

openwisp_controller/connection/tests/test_models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ def test_is_working_change_signal_emitted(self):
334334
dc.save()
335335
handler.assert_called_once_with(
336336
failure_reason='',
337+
old_failure_reason='',
337338
instance=dc,
338339
is_working=True,
339340
old_is_working=None,

openwisp_controller/connection/tests/test_notifications.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import os
2+
from unittest.mock import patch
23

34
from django.apps.registry import apps
45
from django.test import TestCase, TransactionTestCase
56
from django.urls import reverse
7+
from openwisp_notifications.signals import notify
68
from openwisp_notifications.types import unregister_notification_type
79
from swapper import load_model
810

@@ -142,6 +144,57 @@ def test_default_notification_type_already_unregistered(self):
142144
app = apps.get_app_config(self.app_label)
143145
app.register_notification_types()
144146

147+
@patch(
148+
'openwisp_controller.connection.apps.ConnectionConfig'
149+
'._ignore_connection_notification_reasons',
150+
['timed out'],
151+
)
152+
@patch.object(notify, 'send')
153+
def test_connection_is_working_changed_timed_out(self, notify_send, *args):
154+
credentials = self._create_credentials_with_key(port=self.ssh_server.port)
155+
self._create_config(device=self.d)
156+
device_conn = self._create_device_connection(
157+
credentials=credentials, device=self.d, is_working=True
158+
)
159+
self.assertEqual(device_conn.is_working, True)
160+
device_conn.is_working = False
161+
device_conn.failure_reason = 'timed out'
162+
device_conn.full_clean()
163+
device_conn.save()
164+
notify_send.assert_not_called()
165+
# Connection recovers, device is reachable again
166+
device_conn.is_working = True
167+
device_conn.failure_reason = ''
168+
device_conn.full_clean()
169+
device_conn.save()
170+
notify_send.assert_not_called()
171+
172+
@patch(
173+
'openwisp_controller.connection.apps.ConnectionConfig'
174+
'._ignore_connection_notification_reasons',
175+
['Unable to connect'],
176+
)
177+
@patch.object(notify, 'send')
178+
def test_connection_is_working_changed_unable_to_connect(self, notify_send, *args):
179+
credentials = self._create_credentials_with_key(port=self.ssh_server.port)
180+
self._create_config(device=self.d)
181+
device_conn = self._create_device_connection(
182+
credentials=credentials, device=self.d, is_working=True
183+
)
184+
device_conn.failure_reason = (
185+
'[Errno None] Unable to connect to port 5555 on 127.0.0.1'
186+
)
187+
device_conn.is_working = False
188+
device_conn.full_clean()
189+
device_conn.save()
190+
notify_send.assert_not_called()
191+
# Connection makes recovery.
192+
device_conn.failure_reason = ''
193+
device_conn.is_working = True
194+
device_conn.full_clean()
195+
device_conn.save()
196+
notify_send.assert_not_called()
197+
145198

146199
class TestNotificationTransaction(
147200
CreateConnectionsMixin, BaseTestNotification, TransactionTestCase

0 commit comments

Comments
 (0)