Skip to content

Commit

Permalink
[feature] Added check for WiFi Clients
Browse files Browse the repository at this point in the history
  • Loading branch information
pandafy committed Dec 11, 2024
1 parent 13285e3 commit 085383d
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 1 deletion.
9 changes: 9 additions & 0 deletions openwisp_monitoring/check/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,12 @@ def _connect_signals(self):
sender=load_model('config', 'Device'),
dispatch_uid='auto_iperf3_check',
)

if app_settings.AUTO_WIFI_CLIENT_CHECK:
from .base.models import auto_wifi_client_check_receiver

post_save.connect(
auto_wifi_client_check_receiver,
sender=load_model('config', 'Device'),
dispatch_uid='auto_wifi_clients_check',
)
19 changes: 19 additions & 0 deletions openwisp_monitoring/check/base/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
auto_create_config_check,
auto_create_iperf3_check,
auto_create_ping,
auto_create_wifi_client_check,
)
from openwisp_utils.base import TimeStampedEditableModel

Expand Down Expand Up @@ -160,3 +161,21 @@ def auto_iperf3_check_receiver(sender, instance, created, **kwargs):
object_id=str(instance.pk),
)
)


def auto_wifi_client_check_receiver(sender, instance, created, **kwargs):
"""Implements OPENWISP_MONITORING_AUTO_WIFI_CLIENT_CHECK.
The creation step is executed in the background.
"""
# we need to skip this otherwise this task will be executed
# every time the configuration is requested via checksum
if not created:
return
transaction_on_commit(
lambda: auto_create_wifi_client_check.delay(
model=sender.__name__.lower(),
app_label=sender._meta.app_label,
object_id=str(instance.pk),
)
)
1 change: 1 addition & 0 deletions openwisp_monitoring/check/classes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .config_applied import ConfigApplied # noqa
from .iperf3 import Iperf3 # noqa
from .ping import Ping # noqa
from .wifi_client import WifiClient # noqa
43 changes: 43 additions & 0 deletions openwisp_monitoring/check/classes/wifi_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from swapper import load_model

from ...db import timeseries_db
from .base import BaseCheck

AlertSettings = load_model('monitoring', 'AlertSettings')


class WifiClient(BaseCheck):
def check(self, store=True):
values = timeseries_db.read(
key='wifi_clients',
fields='COUNT(DISTINCT(clients))',
tags={
'content_type': self.related_object._meta.label_lower,
'object_id': str(self.related_object.pk),
},
since='now() - 5m',
)
if not values:
result = 0
else:
result = values[0]['count']
if store:
self.store_result(result)
return result

def store_result(self, result):
max_metric = self._get_metric('max_wifi_clients')
max_metric.write(result)
min_metric = self._get_metric('min_wifi_clients')
min_metric.write(result)

def _get_metric(self, configuration):
metric, created = self._get_or_create_metric(configuration=configuration)
if created:
self._create_alert_setting(metric)
return metric

def _create_alert_setting(self, metric):
alert_s = AlertSettings(metric=metric)
alert_s.full_clean()
alert_s.save()
5 changes: 5 additions & 0 deletions openwisp_monitoring/check/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
('openwisp_monitoring.check.classes.Ping', 'Ping'),
('openwisp_monitoring.check.classes.ConfigApplied', 'Configuration Applied'),
('openwisp_monitoring.check.classes.Iperf3', 'Iperf3'),
('openwisp_monitoring.check.classes.WifiClient', 'Wifi Client'),
),
)
AUTO_PING = get_settings_value('AUTO_PING', True)
Expand All @@ -19,6 +20,10 @@
getattr(settings, 'OPENWISP_CONTROLLER_MANAGEMENT_IP_ONLY', True),
)
PING_CHECK_CONFIG = get_settings_value('PING_CHECK_CONFIG', {})
AUTO_WIFI_CLIENT_CHECK = get_settings_value('AUTO_WIFI_CLIENT_CHECK', False)
WIFI_CLIENT_CHECK_SNOOZE_SCHEDULE = get_settings_value(
'WIFI_CLIENT_CHECK_SNOOZE_SCHEDULE', []
)
AUTO_IPERF3 = get_settings_value('AUTO_IPERF3', False)
IPERF3_CHECK_CONFIG = get_settings_value('IPERF3_CHECK_CONFIG', {})
IPERF3_CHECK_LOCK_EXPIRE = get_settings_value(
Expand Down
52 changes: 51 additions & 1 deletion openwisp_monitoring/check/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.utils import timezone
from swapper import load_model

from openwisp_utils.tasks import OpenwispCeleryTask

from .settings import CHECKS_LIST
from .settings import CHECKS_LIST, WIFI_CLIENT_CHECK_SNOOZE_SCHEDULE

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -52,6 +53,26 @@ def run_checks(checks=None):
perform_check.delay(check['id'])


@shared_task(time_limit=2 * 60 * 60)
def run_wifi_client_checks():
if WIFI_CLIENT_CHECK_SNOOZE_SCHEDULE:
today = timezone.localdate()
# Format as MM-DD
today_month_day = today.strftime("%m-%d")
for start_date, end_date in WIFI_CLIENT_CHECK_SNOOZE_SCHEDULE:
# Check if the date range wraps around the new year
if start_date <= end_date:
# Normal range within the same year
if start_date <= today_month_day <= end_date:
return
else:
# Wrap-around range spanning across years
if today_month_day >= start_date or today_month_day <= end_date:
return

run_checks(checks=['openwisp_monitoring.check.classes.WifiClient'])


@shared_task(time_limit=30 * 60)
def perform_check(uuid):
"""Performs check with specified uuid.
Expand Down Expand Up @@ -150,3 +171,32 @@ def auto_create_iperf3_check(
)
check.full_clean()
check.save()


@shared_task(base=OpenwispCeleryTask)
def auto_create_wifi_client_check(
model, app_label, object_id, check_model=None, content_type_model=None
):
"""Implements the auto creation of the wifi_clients check.
Called by the
openwisp_monitoring.check.models.auto_wifi_client_check_receiver.
"""
Check = check_model or get_check_model()
check_path = 'openwisp_monitoring.check.classes.WifiClient'
has_check = Check.objects.filter(
object_id=object_id, content_type__model='device', check_type=check_path
).exists()
# create new check only if necessary
if has_check:
return
content_type_model = content_type_model or ContentType
ct = content_type_model.objects.get_by_natural_key(app_label=app_label, model=model)
check = Check(
name='Wifi Client',
check_type=check_path,
content_type=ct,
object_id=object_id,
)
check.full_clean()
check.save()
68 changes: 68 additions & 0 deletions openwisp_monitoring/monitoring/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,74 @@ def _get_access_tech():
}
},
},
'max_wifi_clients': {
'label': _('Max WiFi Clients'),
'name': '{name}',
'key': 'max_wifi_clients',
'field_name': 'clients',
'alert_settings': {'operator': '>', 'threshold': 40, 'tolerance': 60},
'notification': {
'problem': {
'verbose_name': 'Max WiFi clients PROBLEM',
'verb': _('has more than'),
'level': 'warning',
'email_subject': _(
'[{site.name}] PROBLEM: Max WiFi Clients exceeded on {notification.target}'
),
'message': _(
'The device [{notification.target}]({notification.target_link}) '
'{notification.verb} {notification.actor.alertsettings.threshold} '
'WiFi clients connected.'
),
},
'recovery': {
'verbose_name': 'Max WiFi clients RECOVERY',
'verb': _('has returned to normal levels'),
'level': 'info',
'email_subject': _(
'[{site.name}] RECOVERY: {notification.target} WiFi clients {notification.verb}'
),
'message': (
'The device [{notification.target}]({notification.target_link}) '
'WiFi clients {notification.verb}.'
),
},
},
},
'min_wifi_clients': {
'label': _('Min WiFi Clients'),
'name': '{name}',
'key': 'min_wifi_clients',
'field_name': 'clients',
'alert_settings': {'operator': '<', 'threshold': 0, 'tolerance': 240},
'notification': {
'problem': {
'verbose_name': 'Min WiFi clients PROBLEM',
'verb': _('has less than'),
'level': 'warning',
'email_subject': _(
'[{site.name}] PROBLEM: {notification.target} {notification.verb} minimum WiFi clients'
),
'message': _(
'The device [{notification.target}]({notification.target_link}) '
'{notification.verb} {notification.actor.alertsettings.threshold} '
'WiFi clients connected.'
),
},
'recovery': {
'verbose_name': 'Min WiFi clients RECOVERY',
'verb': _('has returned to normal levels'),
'level': 'info',
'email_subject': _(
'[{site.name}] RECOVERY: {notification.target} minimum WiFi clients {notification.verb}'
),
'message': (
'The device [{notification.target}]({notification.target_link}) '
'WiFi client {notification.verb}.'
),
},
},
},
'general_clients': {
'label': _('General WiFi Clients'),
'name': _('General WiFi Clients'),
Expand Down
5 changes: 5 additions & 0 deletions tests/openwisp2/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,11 @@
),
'relative': True,
},
'run_wifi_client_checks': {
'task': 'openwisp_monitoring.check.tasks.run_wifi_client_checks',
'schedule': timedelta(minutes=5),
'relative': True,
},
'run_iperf3_checks': {
'task': 'openwisp_monitoring.check.tasks.run_checks',
# https://docs.celeryq.dev/en/latest/userguide/periodic-tasks.html#crontab-schedules
Expand Down

0 comments on commit 085383d

Please sign in to comment.