Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] Added check for WiFi Clients #623

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions docs/user/checks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,50 @@ parameters used for iperf3 checks (e.g. timing, port, username, password,
may need to update the :doc:`metric configuration
<device-checks-and-alert-settings>` to enable alerts for the iperf3
check.

.. _wifi_client_check:

WiFi Client
-----------

This check sends alerts based on the total number of WiFi Clients
connected to a device. It sends two types of alerts:

- **Maximum WiFi Users**: When the total number of WiFi clients connected
to an access point exceeds a predetermined threshold. This functionality
provides valuable insights into the network's performance, signaling
when a specific access point is overwhelmed by an excessive number of
WiFi users.
- **Minimum WiFi Users**: When the total number of WiFi clients connected
to an access point remains at zero for a duration exceeding the
specified tolerance period. It serves as an indicator of whether the
access point is malfunctioning or if its placement is hindering user
connectivity.

This check is **disabled by default**. To enable auto creation of this
check, set :ref:`openwisp_monitoring_auto_wifi_client_check` to ``True``
and configure the task scheduling in your Django project:

.. code-block:: python

from datetime import timedelta

OPENWISP_MONITORING_AUTO_WIFI_CLIENT_CHECK = True
CELERY_BEAT_SCHEDULE.update(
{
"run_wifi_client_checks": {
"task": "openwisp_monitoring.check.tasks.run_wifi_client_checks",
# Run check every 5 minutes
"schedule": timedelta(minutes=5),
"relative": True,
},
}
)

You can also :doc:`add the WiFi Client check
<device-checks-and-alert-settings>` directly from the device page.

You can use the
:ref:`openwisp_monitoring_wifi_client_check_snooze_schedule` setting to
disable this check on specific dates, such as during scheduled
maintenance, to avoid generating unnecessary alerts.
54 changes: 54 additions & 0 deletions docs/user/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,60 @@ check when running on multiple servers. Make sure it is always greater
than the total iperf3 check time, i.e. greater than the TCP + UDP test
time. By default, it is set to **600 seconds (10 mins)**.

.. _openwisp_monitoring_auto_wifi_client_check:

``OPENWISP_MONITORING_AUTO_WIFI_CLIENT_CHECK``
----------------------------------------------

============ =========
**type**: ``bool``
**default**: ``False``
============ =========

This setting allows you to choose whether :ref:`WiFi Client
<wifi_client_check>` checks should be created automatically for newly
registered devices. It's disabled by default.

.. _openwisp_monitoring_wifi_client_check_snooze_schedule:

``OPENWISP_MONITORING_WIFI_CLIENT_CHECK_SNOOZE_SCHEDULE``
---------------------------------------------------------

============ ========
**type**: ``list``
**default**: ``[]``
============ ========

This setting allows you to configure date ranges when the WiFi Client
check should not be executed. The date ranges should be in the format
``(start_date, end_date)`` where both dates are in the format ``MM-DD``.
Both start date and end date are inclusive.

E.g.:

.. code-block:: python

OPENWISP_MONITORING_WIFI_CLIENT_CHECK_SNOOZE_SCHEDULE = [
# Date ranges can expand over months
("12-24", "01-05"),
# Date ranges can be single day
("01-26", "01-26"),
]

.. _openwisp_monitoring_wifi_client_check_interval:

``OPENWISP_MONITORING_WIFI_CLIENT_CHECK_INTERVAL``
--------------------------------------------------

============ =======
**type**: ``int``
**default**: ``5``
============ =======

This setting allows you to configure the WiFi Client check interval used
by :ref:`WiFi Client checks <wifi_client_check>`. By default it is set to
5 minutes.

.. _openwisp_monitoring_auto_charts:

``OPENWISP_MONITORING_AUTO_CHARTS``
Expand Down
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
52 changes: 52 additions & 0 deletions openwisp_monitoring/check/classes/wifi_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from django.utils import timezone
from swapper import load_model

from ...db import timeseries_db
from .. import settings as app_settings
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=int(
(
timezone.localtime()
- timezone.timedelta(
minutes=app_settings.WIFI_CLIENT_CHECK_INTERVAL
)
).timestamp()
),
)
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()
8 changes: 8 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,13 @@
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', []
)
WIFI_CLIENT_CHECK_INTERVAL = int(
get_settings_value('WIFI_CLIENT_CHECK_INTERVAL', 5)
) # in minutes
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()
27 changes: 27 additions & 0 deletions openwisp_monitoring/check/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
from django.db.models.signals import post_save
from swapper import load_model

from ..base.models import auto_wifi_client_check_receiver

Device = load_model('config', 'Device')

_FPING_REACHABLE = (
'',
bytes(
Expand All @@ -10,3 +17,23 @@
'',
bytes('192.168.255.255 : xmt/rcv/%loss = 3/0/100%', encoding='utf8'),
)


class AutoWifiClientCheck(object):
@classmethod
def setUpClass(cls):
super().setUpClass()
post_save.connect(
auto_wifi_client_check_receiver,
sender=Device,
dispatch_uid='auto_wifi_clients_check',
)

@classmethod
def tearDownClass(cls):
super().tearDownClass()
post_save.disconnect(
auto_wifi_client_check_receiver,
sender=Device,
dispatch_uid='auto_wifi_clients_check',
)
Loading
Loading