-
-
Notifications
You must be signed in to change notification settings - Fork 120
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[feature] Add a check which inspects device configuration status peri…
…odically #54
- Loading branch information
Showing
21 changed files
with
455 additions
and
89 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,13 @@ | ||
from django.apps import AppConfig | ||
from django.utils.translation import ugettext_lazy as _ | ||
|
||
from .utils import manage_config_status_retention_policy | ||
|
||
|
||
class CheckConfig(AppConfig): | ||
name = 'openwisp_monitoring.check' | ||
label = 'check' | ||
verbose_name = _('Network Monitoring Checks') | ||
|
||
def ready(self): | ||
manage_config_status_retention_policy() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
from .config_status import ConfigStatus # noqa | ||
from .ping import Ping # noqa |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
from django.contrib.contenttypes.models import ContentType | ||
from django.core.exceptions import ValidationError | ||
from swapper import load_model | ||
|
||
from openwisp_controller.config.models import Device | ||
|
||
Metric = load_model('monitoring', 'Metric') | ||
|
||
|
||
class BaseCheck(object): | ||
def validate_instance(self): | ||
# check instance is of type device | ||
obj = self.related_object | ||
if not obj or not isinstance(obj, Device): | ||
message = 'A related device is required to perform this operation' | ||
raise ValidationError({'content_type': message, 'object_id': message}) | ||
|
||
def _get_or_create_metric(self, field_name): | ||
""" | ||
Gets or creates metric | ||
""" | ||
check = self.check_instance | ||
if check.object_id and check.content_type: | ||
obj_id = check.object_id | ||
ct = check.content_type | ||
else: | ||
obj_id = str(check.id) | ||
ct = ContentType.objects.get( | ||
app_label=check._meta.app_label, model=check.__class__.__name__.lower() | ||
) | ||
options = dict( | ||
name=check.name, | ||
object_id=obj_id, | ||
content_type=ct, | ||
field_name=field_name, | ||
key=self.__class__.__name__.lower(), | ||
) | ||
metric, created = Metric.objects.get_or_create(**options) | ||
return metric, created |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
from time import time | ||
|
||
from swapper import load_model | ||
|
||
from ...monitoring.utils import write | ||
from ..settings import CONFIG_MODIFIED_MAX_TIME | ||
from ..utils import CONFIG_STATUS_RP | ||
from .base import BaseCheck | ||
|
||
Graph = load_model('monitoring', 'Graph') | ||
Threshold = load_model('monitoring', 'Threshold') | ||
|
||
|
||
class ConfigStatus(BaseCheck): | ||
def __init__(self, check, params): | ||
self.check_instance = check | ||
self.related_object = check.content_object | ||
self.params = params | ||
|
||
def validate(self): | ||
self.validate_instance() | ||
|
||
def check(self, store=True): | ||
if not hasattr(self.related_object, 'config'): | ||
return | ||
result = 0 if self.related_object.config.status == 'applied' else 1 | ||
if result and self._check_modified_status_crossed_max_time(): | ||
# TODO: When is the status supposed to be made critical? | ||
dm = self.related_object.monitoring | ||
if dm.status in ['ok', 'unknown']: | ||
dm.update_status('problem') | ||
if store: | ||
metric = self.get_metric() | ||
# TODO: Find why doing the same by adding rp as an arg | ||
# in metric write changes status to problem | ||
write( | ||
name=metric.key, | ||
values={metric.field_name: result}, | ||
tags=metric.tags, | ||
retention_policy=CONFIG_STATUS_RP, | ||
) | ||
return result | ||
|
||
# TODO: Check if this can be done using threshold | ||
def _check_modified_status_crossed_max_time(self): | ||
# A small margin is kept to take care of border cases | ||
# TODO: Find why `m` --> minute is not working correctly! | ||
since = f'now() - {int(CONFIG_MODIFIED_MAX_TIME*63)}s' | ||
measurement_list = self.get_metric().read( | ||
since=since, limit=None, order='time DESC' | ||
) | ||
for measurement in measurement_list: | ||
if not measurement['config_status']: | ||
break | ||
minutes_difference = (time() - measurement['time']) / 60 | ||
if minutes_difference >= CONFIG_MODIFIED_MAX_TIME: | ||
return True | ||
return False | ||
|
||
def get_metric(self): | ||
metric, created = self._get_or_create_metric(field_name='config_status') | ||
if created: | ||
self._create_threshold(metric) | ||
return metric | ||
|
||
def _create_threshold(self, metric): | ||
t = Threshold(metric=metric, operator='>', value=0, seconds=0) | ||
t.full_clean() | ||
t.save() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
openwisp_monitoring/check/migrations/0004_create_config_status.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
from django.db import migrations | ||
from openwisp_monitoring.check.settings import AUTO_CONFIG_STATUS | ||
from openwisp_monitoring.check.tasks import auto_create_config_status | ||
|
||
|
||
def add_config_status_checks(apps, schema_editor): | ||
if not AUTO_CONFIG_STATUS: | ||
return | ||
Device = apps.get_model('config', 'Device') | ||
for device in Device.objects.all(): | ||
auto_create_config_status.delay( | ||
model=Device.__name__.lower(), | ||
app_label=Device._meta.app_label, | ||
object_id=str(device.pk), | ||
) | ||
|
||
|
||
def remove_config_status_checks(apps, schema_editor): | ||
Check = apps.get_model('config', 'Device') | ||
Check.objects.filter(name='Config Status').delete() | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('check', '0003_create_ping'), | ||
] | ||
|
||
operations = [ | ||
migrations.RunPython( | ||
add_config_status_checks, reverse_code=remove_config_status_checks | ||
), | ||
] |
Oops, something went wrong.