Skip to content

Commit

Permalink
[feature] Added connection notification logic from openwisp-monitoring
Browse files Browse the repository at this point in the history
…#269

Moves code from openwisp-monitoring to openwisp-controller:
openwisp/openwisp-monitoring#226

Closes #269
  • Loading branch information
NoumbissiValere committed Oct 15, 2020
1 parent 53da00c commit 8f9f543
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 0 deletions.
55 changes: 55 additions & 0 deletions openwisp_controller/connection/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
from django.db import transaction
from django.db.models.signals import post_save
from django.utils.translation import ugettext_lazy as _
from openwisp_notifications.signals import notify
from openwisp_notifications.types import register_notification_type
from swapper import load_model

from ..config.signals import config_modified
from .signals import is_working_changed

_TASK_NAME = 'openwisp_controller.connection.tasks.update_config'

Expand All @@ -21,6 +24,7 @@ def ready(self):
to the ``update_config`` celery task
which will be executed in the background
"""
self.register_notification_types()
config_modified.connect(
self.config_modified_receiver, dispatch_uid='connection.update_config'
)
Expand All @@ -31,6 +35,11 @@ def ready(self):
sender=Config,
dispatch_uid='connection.auto_add_credentials',
)
is_working_changed.connect(
self.is_working_changed_receiver,
sender=load_model('connection', 'DeviceConnection'),
dispatch_uid='is_working_changed_receiver',
)

@classmethod
def config_modified_receiver(cls, **kwargs):
Expand Down Expand Up @@ -64,3 +73,49 @@ def _is_update_in_progress(cls, device_pk):
if task['name'] == _TASK_NAME and str(device_pk) in task['args']:
return True
return False

@classmethod
def is_working_changed_receiver(cls, instance, is_working, **kwargs):
device = instance.device
notification_opts = dict(sender=instance, target=device)
if not is_working:
notification_opts['type'] = 'connection_is_not_working'
else:
notification_opts['type'] = 'connection_is_working'
notify.send(**notification_opts)

def register_notification_types(self):
register_notification_type(
'connection_is_not_working',
{
'verbose_name': 'Device Connection PROBLEM',
'verb': 'not working',
'level': 'error',
'email_subject': (
'[{site.name}] PROBLEM: Connection to '
'device {notification.target}'
),
'message': (
'{notification.actor.credentials} connection to '
'device [{notification.target}]({notification.target_link}) '
'is {notification.verb}. {notification.actor.failure_reason}'
),
},
)
register_notification_type(
'connection_is_working',
{
'verbose_name': 'Device Connection RECOVERY',
'verb': 'working',
'level': 'info',
'email_subject': (
'[{site.name}] RECOVERY: Connection to '
'device {notification.target}'
),
'message': (
'{notification.actor.credentials} connection to '
'device [{notification.target}]({notification.target_link}) '
'is {notification.verb}. {notification.actor.failure_reason}'
),
},
)
124 changes: 124 additions & 0 deletions openwisp_controller/connection/tests/test_notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
from django.apps.registry import apps
from django.core import mail
from django.urls import reverse
from django.utils.html import strip_tags
from openwisp_notifications.types import unregister_notification_type
from swapper import load_model

from .utils import CreateConnectionsMixin

Notification = load_model('openwisp_notifications', 'Notification')
Credentials = load_model('connection', 'Credentials')
DeviceConnection = load_model('connection', 'DeviceConnection')


class TestNotifications(CreateConnectionsMixin):
app_label = 'connection'

def setUp(self):
self._create_admin()
self.d = self._create_device()
self.creds = Credentials.objects.create(
connector='openwisp_controller.connection.connectors.ssh.Ssh'
)
self.dc = DeviceConnection.objects.create(credentials=self.creds, device=self.d)

def _generic_notification_test(
self, exp_level, exp_type, exp_verb, exp_message, exp_email_subject
):
n = Notification.objects.first()
url_path = reverse('notifications:notification_read_redirect', args=[n.pk])
exp_email_link = f'https://example.com{url_path}'
exp_target_link = f'https://example.com/admin/config/device/{self.d.id}/change/'
exp_email_body = '{message}' f'\n\nFor more information see {exp_email_link}.'

email = mail.outbox.pop()
html_message, _ = email.alternatives.pop()
self.assertEqual(n.type, exp_type)
self.assertEqual(n.level, exp_level)
self.assertEqual(n.verb, exp_verb)
self.assertEqual(n.actor, self.dc)
self.assertEqual(n.target, self.d)
self.assertEqual(
n.message, exp_message.format(n=n, target_link=exp_target_link)
)
self.assertEqual(
n.email_subject, exp_email_subject.format(n=n),
)
self.assertEqual(email.subject, n.email_subject)
self.assertEqual(
email.body, exp_email_body.format(message=strip_tags(n.message))
)
self.assertIn(
f'<a href="{exp_email_link}">'
'For further information see "device: default.test.device".</a>',
html_message,
)

def test_connection_working_notification(self):
self.assertEqual(Notification.objects.count(), 0)
self.dc = DeviceConnection.objects.create(
credentials=self.creds, device=self.d, is_working=False
)
self.dc.is_working = True
self.dc.save()
self.assertEqual(Notification.objects.count(), 1)
self._generic_notification_test(
exp_level='info',
exp_type='connection_is_working',
exp_verb='working',
exp_message=(
'<p>(SSH) connection to device <a href="{target_link}">'
'{n.target}</a> is {n.verb}. </p>'
),
exp_email_subject='[example.com] RECOVERY: Connection to device {n.target}',
)

def test_connection_not_working_notification(self):
self.assertEqual(Notification.objects.count(), 0)
self.dc.is_working = False
self.dc.save()
self.assertEqual(Notification.objects.count(), 1)
self._generic_notification_test(
exp_level='error',
exp_type='connection_is_not_working',
exp_verb='not working',
exp_message=(
'<p>(SSH) connection to device <a href="{target_link}">'
'{n.target}</a> is {n.verb}. </p>'
),
exp_email_subject='[example.com] PROBLEM: Connection to device {n.target}',
)

def test_unreachable_after_upgrade_notification(self):
self.assertEqual(Notification.objects.count(), 0)
self.dc.is_working = False
self.dc.failure_reason = 'Giving up, device not reachable anymore after upgrade'
self.dc.save()
self.assertEqual(Notification.objects.count(), 1)
self._generic_notification_test(
exp_level='error',
exp_type='connection_is_not_working',
exp_verb='not working',
exp_message=(
'<p>(SSH) connection to device <a href="{target_link}">'
'{n.target}</a> is {n.verb}. '
'Giving up, device not reachable anymore after upgrade</p>'
),
exp_email_subject='[example.com] PROBLEM: Connection to device {n.target}',
)

def test_default_notification_type_already_unregistered(self):
# Simulates if 'default notification type is already unregistered
# by some other module

# Unregister "config_error" and "device_registered" notification
# types to avoid getting rasing ImproperlyConfigured exceptions
unregister_notification_type('connection_is_not_working')
unregister_notification_type('connection_is_working')

# This will try to unregister 'default' notification type
# which is already got unregistered when Django loaded.
# No exception should be raised as the exception is already handled.
app = apps.get_app_config(self.app_label)
app.register_notification_types()
8 changes: 8 additions & 0 deletions tests/openwisp2/sample_connection/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
from openwisp_controller.connection.tests.test_models import (
TestModelsTransaction as BaseTestModelsTransaction,
)
from openwisp_controller.connection.tests.test_notifications import (
TestNotifications as BaseTestNotifications,
)
from openwisp_controller.connection.tests.test_ssh import TestSsh as BaseTestSsh


Expand All @@ -25,7 +28,12 @@ class TestSsh(BaseTestSsh):
pass


class TestNotifications(BaseTestNotifications):
app_label = 'sample_connection'


del BaseTestAdmin
del BaseTestModels
del BaseTestModelsTransaction
del BaseTestSsh
del BaseTestNotifications

0 comments on commit 8f9f543

Please sign in to comment.