Skip to content

Commit 63e1612

Browse files
committed
[feature] Add a check which inspects device configuration status periodically #54
1 parent 9bfbf34 commit 63e1612

File tree

23 files changed

+483
-99
lines changed

23 files changed

+483
-99
lines changed

README.rst

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,53 @@ in terms of disk space.
247247

248248
Whether ping checks are created automatically for devices.
249249

250+
``OPENWISP_MONITORING_AUTO_DEVICE_CONFIGURATION_MODIFIED``
251+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
252+
253+
+--------------+-------------+
254+
| **type**: | ``bool`` |
255+
+--------------+-------------+
256+
| **default**: | ``True`` |
257+
+--------------+-------------+
258+
259+
``config_modified`` checks are run peridically to monitor whether device
260+
configuration is applied properly.
261+
This setting is used to govern whether ``config_modified`` checks are
262+
created automatically for newly registered devices.
263+
264+
``OPENWISP_MONITORING_DEVICE_CONFIGURATION_MODIFIED_MAXIMUM_TIME``
265+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
266+
267+
+--------------+-----------+
268+
| **type**: | ``int`` |
269+
+--------------+-----------+
270+
| **default**: | ``5`` |
271+
+--------------+-----------+
272+
273+
This setting allows you to configure the time that the check is allowed to
274+
fail after which the device health status changes to ``problem``.
275+
276+
This default **duration** is 5 minutes. The input represents corresponding
277+
duration in minutes.
278+
279+
**Note**: If the setting ``OPENWISP_MONITORING_AUTO_DEVICE_CONFIGURATION_MODIFIED``
280+
is disabled then this setting need not be declared.
281+
282+
``OPENWISP_MONITORING_DEVICE_CONFIGURATION_MODIFIED_RETENTION_POLICY``
283+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
284+
285+
+--------------+-------------+
286+
| **type**: | ``time`` |
287+
+--------------+-------------+
288+
| **default**: | ``48h0m0s`` |
289+
+--------------+-------------+
290+
291+
This setting allows to modify the duration for which the metric data generated
292+
by ``config_modified`` check is to be retained.
293+
294+
**Note**: If the setting ``OPENWISP_MONITORING_AUTO_DEVICE_CONFIGURATION_MODIFIED``
295+
is disabled then this setting need not be declared.
296+
250297
``OPENWISP_MONITORING_AUTO_CHARTS``
251298
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
252299

openwisp_monitoring/check/apps.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
from django.apps import AppConfig
22
from django.utils.translation import ugettext_lazy as _
33

4+
from .utils import manage_config_modified_retention_policy
5+
46

57
class CheckConfig(AppConfig):
68
name = 'openwisp_monitoring.check'
79
label = 'check'
810
verbose_name = _('Network Monitoring Checks')
11+
12+
def ready(self):
13+
manage_config_modified_retention_policy()

openwisp_monitoring/check/base/models.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
from django.contrib.contenttypes.fields import GenericForeignKey
44
from django.contrib.contenttypes.models import ContentType
5-
from django.db import models
5+
from django.db import models, transaction
66
from django.db.models.signals import post_save
7+
from django.dispatch import receiver
78
from django.utils.functional import cached_property
89
from django.utils.module_loading import import_string
910
from django.utils.translation import gettext_lazy as _
@@ -76,15 +77,13 @@ def perform_check(self, store=True):
7677

7778

7879
if app_settings.AUTO_PING:
79-
from django.db import transaction
80-
from django.dispatch import receiver
8180
from openwisp_monitoring.check.tasks import auto_create_ping
8281

8382
@receiver(post_save, sender=Device, dispatch_uid='auto_ping')
8483
def auto_ping_receiver(sender, instance, created, **kwargs):
8584
"""
8685
Implements OPENWISP_MONITORING_AUTO_PING
87-
The creation step is executed in the backround
86+
The creation step is executed in the background
8887
"""
8988
# we need to skip this otherwise this task will be executed
9089
# every time the configuration is requested via checksum
@@ -96,6 +95,28 @@ def auto_ping_receiver(sender, instance, created, **kwargs):
9695
model=sender.__name__.lower(),
9796
app_label=sender._meta.app_label,
9897
object_id=str(instance.pk),
99-
created=created,
98+
)
99+
)
100+
101+
102+
if app_settings.AUTO_CONFIG_MODIFIED:
103+
from openwisp_monitoring.check.tasks import auto_create_config_modified
104+
105+
@receiver(post_save, sender=Device, dispatch_uid='auto_config_modified')
106+
def auto_config_modified_receiver(sender, instance, created, **kwargs):
107+
"""
108+
Implements OPENWISP_MONITORING_AUTO_CONFIG_MODIFIED
109+
The creation step is executed in the background
110+
"""
111+
# we need to skip this otherwise this task will be executed
112+
# every time the configuration is requested via checksum
113+
if not created:
114+
return
115+
with transaction.atomic():
116+
transaction.on_commit(
117+
lambda: auto_create_config_modified.delay(
118+
model=sender.__name__.lower(),
119+
app_label=sender._meta.app_label,
120+
object_id=str(instance.pk),
100121
)
101122
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
from .config_modified import ConfigModified # noqa
12
from .ping import Ping # noqa
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from django.contrib.contenttypes.models import ContentType
2+
from django.core.exceptions import ValidationError
3+
from swapper import load_model
4+
5+
from openwisp_controller.config.models import Device
6+
7+
Metric = load_model('monitoring', 'Metric')
8+
9+
10+
class BaseCheck(object):
11+
def validate_instance(self):
12+
# check instance is of type device
13+
obj = self.related_object
14+
if not obj or not isinstance(obj, Device):
15+
message = 'A related device is required to perform this operation'
16+
raise ValidationError({'content_type': message, 'object_id': message})
17+
18+
def _get_or_create_metric(self, field_name):
19+
"""
20+
Gets or creates metric
21+
"""
22+
check = self.check_instance
23+
if check.object_id and check.content_type:
24+
obj_id = check.object_id
25+
ct = check.content_type
26+
else:
27+
obj_id = str(check.id)
28+
ct = ContentType.objects.get(
29+
app_label=check._meta.app_label, model=check.__class__.__name__.lower()
30+
)
31+
options = dict(
32+
name=check.name,
33+
object_id=obj_id,
34+
content_type=ct,
35+
field_name=field_name,
36+
key=self.__class__.__name__.lower(),
37+
)
38+
metric, created = Metric.objects.get_or_create(**options)
39+
return metric, created
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from swapper import load_model
2+
3+
from ..settings import CONFIG_MODIFIED_MAX_TIME
4+
from ..utils import CONFIG_MODIFIED_RP
5+
from .base import BaseCheck
6+
7+
AlertSettings = load_model('monitoring', 'AlertSettings')
8+
9+
10+
class ConfigModified(BaseCheck):
11+
def __init__(self, check, params):
12+
self.check_instance = check
13+
self.related_object = check.content_object
14+
self.params = params
15+
16+
def validate(self):
17+
self.validate_instance()
18+
19+
def check(self, store=True):
20+
if not hasattr(self.related_object, 'config'):
21+
return
22+
result = 0 if self.related_object.config.status == 'applied' else 1
23+
if store:
24+
self.get_metric().write(
25+
result, retention_policy=CONFIG_MODIFIED_RP,
26+
)
27+
return result
28+
29+
def get_metric(self):
30+
metric, created = self._get_or_create_metric(field_name='config_modified')
31+
if created:
32+
self._create_alert_setting(metric)
33+
return metric
34+
35+
def _create_alert_setting(self, metric):
36+
alert_s = AlertSettings(
37+
metric=metric, operator='>', value=0, seconds=CONFIG_MODIFIED_MAX_TIME * 60
38+
)
39+
alert_s.full_clean()
40+
alert_s.save()

openwisp_monitoring/check/classes/ping.py

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
11
import subprocess
22

3-
from django.contrib.contenttypes.models import ContentType
43
from django.core.exceptions import ValidationError
54
from jsonschema import draft7_format_checker, validate
65
from jsonschema.exceptions import ValidationError as SchemaError
76
from swapper import load_model
87

9-
from openwisp_controller.config.models import Device
10-
118
from ... import settings as monitoring_settings
129
from .. import settings as app_settings
1310
from ..exceptions import OperationalError
11+
from .base import BaseCheck
1412

1513
Chart = load_model('monitoring', 'Chart')
1614
Metric = load_model('monitoring', 'Metric')
1715
AlertSettings = load_model('monitoring', 'AlertSettings')
1816

1917

20-
class Ping(object):
18+
class Ping(BaseCheck):
2119
schema = {
22-
'$schema': 'http://json-schema.org/draft-04/schema#',
20+
'$schema': 'http://json-schema.org/draft-07/schema#',
2321
'type': 'object',
2422
'additionalProperties': False,
2523
'properties': {
@@ -57,13 +55,6 @@ def validate(self):
5755
self.validate_instance()
5856
self.validate_params()
5957

60-
def validate_instance(self):
61-
# check instance is of type device
62-
obj = self.related_object
63-
if not obj or not isinstance(obj, Device):
64-
message = 'A related device is required ' 'to perform this operation'
65-
raise ValidationError({'content_type': message, 'object_id': message})
66-
6758
def validate_params(self):
6859
try:
6960
validate(self.params, self.schema, format_checker=draft7_format_checker)
@@ -165,23 +156,7 @@ def _get_metric(self):
165156
"""
166157
Gets or creates metric
167158
"""
168-
check = self.check_instance
169-
if check.object_id and check.content_type:
170-
obj_id = check.object_id
171-
ct = check.content_type
172-
else:
173-
obj_id = str(check.id)
174-
ct = ContentType.objects.get(
175-
app_label=check._meta.app_label, model=check.__class__.__name__.lower()
176-
)
177-
options = dict(
178-
name=check.name,
179-
object_id=obj_id,
180-
content_type=ct,
181-
field_name='reachable',
182-
key=self.__class__.__name__.lower(),
183-
)
184-
metric, created = Metric.objects.get_or_create(**options)
159+
metric, created = self._get_or_create_metric(field_name='reachable')
185160
if created:
186161
self._create_alert_settings(metric)
187162
self._create_charts(metric)

openwisp_monitoring/check/migrations/0001_initial.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import collections
1010
import swapper
1111

12+
from ..settings import CHECK_CLASSES
13+
1214

1315
class Migration(migrations.Migration):
1416

@@ -58,7 +60,7 @@ class Migration(migrations.Migration):
5860
(
5961
'check',
6062
models.CharField(
61-
choices=[('openwisp_monitoring.check.classes.Ping', 'Ping')],
63+
choices=CHECK_CLASSES,
6264
db_index=True,
6365
help_text='Select check type',
6466
max_length=128,

openwisp_monitoring/check/migrations/0003_create_ping.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ def create_device_ping(apps, schema_editor):
1111
model=Device.__name__.lower(),
1212
app_label=Device._meta.app_label,
1313
object_id=str(device.pk),
14-
created=True,
1514
)
1615

1716

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from django.db import migrations
2+
from openwisp_monitoring.check.settings import AUTO_CONFIG_MODIFIED
3+
from openwisp_monitoring.check.tasks import auto_create_config_modified
4+
5+
6+
def add_config_modified_checks(apps, schema_editor):
7+
if not AUTO_CONFIG_MODIFIED:
8+
return
9+
Device = apps.get_model('config', 'Device')
10+
for device in Device.objects.all():
11+
auto_create_config_modified.delay(
12+
model=Device.__name__.lower(),
13+
app_label=Device._meta.app_label,
14+
object_id=str(device.pk),
15+
)
16+
17+
18+
def remove_config_modified_checks(apps, schema_editor):
19+
Check = apps.get_model('config', 'Device')
20+
Check.objects.filter(name='Configuration Modified').delete()
21+
22+
23+
class Migration(migrations.Migration):
24+
25+
dependencies = [
26+
('check', '0003_create_ping'),
27+
]
28+
29+
operations = [
30+
migrations.RunPython(
31+
add_config_modified_checks, reverse_code=remove_config_modified_checks
32+
),
33+
]

0 commit comments

Comments
 (0)