diff --git a/.gitignore b/.gitignore index ab0bd1145..24d8a83c9 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,9 @@ coverage.xml # Django stuff: *.log +# celery +tests/celerybeat* + # Sphinx documentation docs/_build/ @@ -55,6 +58,7 @@ target/ # editors *.komodoproject +.vscode # other *.DS_Store* @@ -63,9 +67,5 @@ target/ local_settings.py *.db *.tar.gz - -# celery -tests/celerybeat* - -# editor -.vscode +Pipfile +Pipfile.lock diff --git a/.travis.yml b/.travis.yml index f74c28874..dfb0eda2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,11 @@ before_install: install: - pip install -e . - # temporary: remove when openwisp-notifications is released + # TODO: removed when openwisp-controller 0.8.0 is released + - pip install -U https://github.com/openwisp/openwisp-controller/tarball/master + # TODO: removed when openwisp-users 0.3.0 is released + - pip install -U https://github.com/openwisp/openwisp-users/tarball/master + # TODO: remove when openwisp-notifications 0.1 is released - pip install -U https://github.com/openwisp/openwisp-notifications/tarball/master script: diff --git a/README.rst b/README.rst index d6a62a153..dd7b7d6ea 100644 --- a/README.rst +++ b/README.rst @@ -793,7 +793,7 @@ For ``check`` app, from openwisp_monitoring.check.admin import CheckAdmin as BaseCheckAdmin from swapper import load_model - Check = load_model('Check') + Check = load_model('check', 'Check') admin.site.unregister(Check) @@ -808,7 +808,9 @@ For ``device_monitoring`` app, from django.contrib import admin from openwisp_monitoring.device_monitoring.admin import DeviceAdmin as BaseDeviceAdmin - from openwisp_controller.config.models import Device + from swapper import load_model + + Device = load_model('config', 'Device') admin.site.unregister(Device) diff --git a/openwisp_monitoring/check/apps.py b/openwisp_monitoring/check/apps.py index 4bffb7915..479d9640d 100644 --- a/openwisp_monitoring/check/apps.py +++ b/openwisp_monitoring/check/apps.py @@ -1,8 +1,24 @@ from django.apps import AppConfig +from django.db.models.signals import post_save from django.utils.translation import ugettext_lazy as _ +from openwisp_monitoring.check import settings as app_settings +from swapper import load_model class CheckConfig(AppConfig): name = 'openwisp_monitoring.check' label = 'check' verbose_name = _('Network Monitoring Checks') + + def ready(self): + self._connect_signals() + + def _connect_signals(self): + if app_settings.AUTO_PING: + from .base.models import auto_ping_receiver + + post_save.connect( + auto_ping_receiver, + sender=load_model('config', 'Device'), + dispatch_uid='auto_ping', + ) diff --git a/openwisp_monitoring/check/base/models.py b/openwisp_monitoring/check/base/models.py index 0c4a42ceb..428a1f87b 100644 --- a/openwisp_monitoring/check/base/models.py +++ b/openwisp_monitoring/check/base/models.py @@ -2,15 +2,14 @@ from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType -from django.db import models -from django.db.models.signals import post_save +from django.db import models, transaction from django.utils.functional import cached_property from django.utils.module_loading import import_string from django.utils.translation import gettext_lazy as _ from jsonfield import JSONField from openwisp_monitoring.check import settings as app_settings +from openwisp_monitoring.check.tasks import auto_create_ping -from openwisp_controller.config.models import Device from openwisp_utils.base import TimeStampedEditableModel @@ -75,26 +74,20 @@ def perform_check(self, store=True): return self.check_instance.check(store=True) -if app_settings.AUTO_PING: - from django.db import transaction - from django.dispatch import receiver - from openwisp_monitoring.check.tasks import auto_create_ping - - @receiver(post_save, sender=Device, dispatch_uid='auto_ping') - def auto_ping_receiver(sender, instance, created, **kwargs): - """ - Implements OPENWISP_MONITORING_AUTO_PING - 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 - with transaction.atomic(): - transaction.on_commit( - lambda: auto_create_ping.delay( - model=sender.__name__.lower(), - app_label=sender._meta.app_label, - object_id=str(instance.pk), - ) +def auto_ping_receiver(sender, instance, created, **kwargs): + """ + Implements OPENWISP_MONITORING_AUTO_PING + 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 + with transaction.atomic(): + transaction.on_commit( + lambda: auto_create_ping.delay( + model=sender.__name__.lower(), + app_label=sender._meta.app_label, + object_id=str(instance.pk), ) + ) diff --git a/openwisp_monitoring/check/classes/base.py b/openwisp_monitoring/check/classes/base.py index f2d7c4d0f..4251887f7 100644 --- a/openwisp_monitoring/check/classes/base.py +++ b/openwisp_monitoring/check/classes/base.py @@ -2,10 +2,9 @@ from django.core.exceptions import ValidationError from swapper import load_model -from openwisp_controller.config.models import Device - Check = load_model('check', 'Check') Metric = load_model('monitoring', 'Metric') +Device = load_model('config', 'Device') class BaseCheck(object): diff --git a/openwisp_monitoring/check/tasks.py b/openwisp_monitoring/check/tasks.py index 6d14d799b..ac77f26a5 100644 --- a/openwisp_monitoring/check/tasks.py +++ b/openwisp_monitoring/check/tasks.py @@ -37,7 +37,8 @@ def perform_check(uuid): @shared_task def auto_create_ping(model, app_label, object_id): """ - Called by openwisp_monitoring.check.models.auto_ping_receiver + Called by django signal (dispatch_uid: auto_ping) + registered in check app's apps.py file. """ Check = load_model('check', 'Check') ping_path = 'openwisp_monitoring.check.classes.Ping' diff --git a/openwisp_monitoring/device/admin.py b/openwisp_monitoring/device/admin.py index 083661f15..55e7b4adc 100644 --- a/openwisp_monitoring/device/admin.py +++ b/openwisp_monitoring/device/admin.py @@ -8,7 +8,6 @@ from swapper import load_model from openwisp_controller.config.admin import DeviceAdmin as BaseDeviceAdmin -from openwisp_controller.config.models import Device from ..monitoring.admin import MetricAdmin from . import settings as app_settings @@ -16,9 +15,12 @@ DeviceData = load_model('device_monitoring', 'DeviceData') DeviceMonitoring = load_model('device_monitoring', 'DeviceMonitoring') Chart = load_model('monitoring', 'Chart') +Device = load_model('config', 'Device') class DeviceAdmin(BaseDeviceAdmin): + change_form_template = 'admin/config/device/change_form.html' + def get_extra_context(self, pk=None): ctx = super().get_extra_context(pk) if pk: diff --git a/openwisp_monitoring/device/api/views.py b/openwisp_monitoring/device/api/views.py index dc11cbcd2..e154300e6 100644 --- a/openwisp_monitoring/device/api/views.py +++ b/openwisp_monitoring/device/api/views.py @@ -17,8 +17,6 @@ from rest_framework.response import Response from swapper import load_model -from openwisp_controller.config.models import Device - from ... import settings as monitoring_settings from ...monitoring.exceptions import InvalidChartConfigException from ..schema import schema @@ -29,6 +27,7 @@ Chart = load_model('monitoring', 'Chart') Metric = load_model('monitoring', 'Metric') AlertSettings = load_model('monitoring', 'AlertSettings') +Device = load_model('config', 'Device') DeviceData = load_model('device_monitoring', 'DeviceData') diff --git a/openwisp_monitoring/device/apps.py b/openwisp_monitoring/device/apps.py index 02e4fa8d2..8745a1363 100644 --- a/openwisp_monitoring/device/apps.py +++ b/openwisp_monitoring/device/apps.py @@ -2,11 +2,13 @@ from django.core.cache import cache from django.db.models.signals import post_delete, post_save from django.utils.translation import gettext_lazy as _ -from django_netjsonconfig.signals import checksum_requested from openwisp_notifications.signals import notify from openwisp_notifications.types import register_notification_type from swapper import load_model +from openwisp_controller.config.signals import checksum_requested +from openwisp_controller.connection.signals import is_working_changed + from . import settings as app_settings from .signals import device_metrics_received, health_status_changed from .utils import get_device_recovery_cache_key, manage_short_retention_policy @@ -25,7 +27,7 @@ def ready(self): self.device_recovery_detection() def connect_device_signals(self): - from openwisp_controller.config.models import Device + Device = load_model('config', 'Device') post_save.connect( self.device_post_save_receiver, @@ -56,8 +58,7 @@ def device_recovery_detection(self): if not app_settings.DEVICE_RECOVERY_DETECTION: return - from openwisp_controller.config.models import Device - + Device = load_model('config', 'Device') DeviceData = load_model('device_monitoring', 'DeviceData') DeviceMonitoring = load_model('device_monitoring', 'DeviceMonitoring') health_status_changed.connect( @@ -96,13 +97,10 @@ def trigger_device_recovery_checks(cls, instance, **kwargs): trigger_device_checks.delay(pk=instance.pk) @classmethod - def connect_is_working_changed(self): - from openwisp_controller.connection.models import DeviceConnection - from openwisp_controller.connection.signals import is_working_changed - + def connect_is_working_changed(cls): is_working_changed.connect( - self.is_working_changed_receiver, - sender=DeviceConnection, + cls.is_working_changed_receiver, + sender=load_model('connection', 'DeviceConnection'), dispatch_uid='is_working_changed_monitoring', ) diff --git a/openwisp_monitoring/device/base/models.py b/openwisp_monitoring/device/base/models.py index c8694091f..0b7cc7387 100644 --- a/openwisp_monitoring/device/base/models.py +++ b/openwisp_monitoring/device/base/models.py @@ -3,6 +3,7 @@ from collections import OrderedDict from datetime import datetime +import swapper from cache_memoize import cache_memoize from dateutil.relativedelta import relativedelta from django.core.exceptions import ValidationError @@ -45,7 +46,7 @@ class AbstractDeviceData(object): def __init__(self, *args, **kwargs): self.data = kwargs.pop('data', None) - return super().__init__(*args, **kwargs) + super().__init__(*args, **kwargs) @property def data_user_friendly(self): @@ -182,7 +183,9 @@ def json(self, *args, **kwargs): class AbstractDeviceMonitoring(TimeStampedEditableModel): device = models.OneToOneField( - 'config.Device', on_delete=models.CASCADE, related_name='monitoring' + swapper.get_model_name('config', 'Device'), + on_delete=models.CASCADE, + related_name='monitoring', ) STATUS = Choices( ('unknown', _(app_settings.HEALTH_STATUS_LABELS['unknown'])), diff --git a/openwisp_monitoring/device/migrations/0001_initial.py b/openwisp_monitoring/device/migrations/0001_initial.py index 302775cf3..2f9383e6c 100644 --- a/openwisp_monitoring/device/migrations/0001_initial.py +++ b/openwisp_monitoring/device/migrations/0001_initial.py @@ -24,6 +24,6 @@ class Migration(migrations.Migration): 'device_monitoring', 'DeviceData' ), }, - bases=('config.device',), + bases=(swapper.get_model_name('config', 'Device'),), ), ] diff --git a/openwisp_monitoring/device/migrations/0002_devicemonitoring.py b/openwisp_monitoring/device/migrations/0002_devicemonitoring.py index 6728124d2..b8ecd4b4e 100644 --- a/openwisp_monitoring/device/migrations/0002_devicemonitoring.py +++ b/openwisp_monitoring/device/migrations/0002_devicemonitoring.py @@ -86,7 +86,7 @@ class Migration(migrations.Migration): models.OneToOneField( on_delete=django.db.models.deletion.CASCADE, related_name='monitoring', - to='config.Device', + to=swapper.get_model_name('config', 'Device'), ), ), ], diff --git a/openwisp_monitoring/device/models.py b/openwisp_monitoring/device/models.py index 2b7480cdf..6929bc79f 100644 --- a/openwisp_monitoring/device/models.py +++ b/openwisp_monitoring/device/models.py @@ -1,12 +1,12 @@ from django.contrib.contenttypes.fields import GenericRelation from swapper import get_model_name, swappable_setting -from openwisp_controller.config.models import Device +from openwisp_controller.config.models import Device as BaseDevice from .base.models import AbstractDeviceData, AbstractDeviceMonitoring -class DeviceData(AbstractDeviceData, Device): +class DeviceData(AbstractDeviceData, BaseDevice): checks = GenericRelation(get_model_name('check', 'Check')) metrics = GenericRelation(get_model_name('monitoring', 'Metric')) diff --git a/openwisp_monitoring/device/tests/__init__.py b/openwisp_monitoring/device/tests/__init__.py index 474bb581b..a34e9dfa1 100644 --- a/openwisp_monitoring/device/tests/__init__.py +++ b/openwisp_monitoring/device/tests/__init__.py @@ -5,8 +5,7 @@ from django.urls import reverse from swapper import load_model -from openwisp_controller.config.models import Config, Device -from openwisp_controller.config.tests import CreateConfigTemplateMixin +from openwisp_controller.config.tests.utils import CreateConfigTemplateMixin from ...monitoring.tests import TestMonitoringMixin from ..utils import manage_short_retention_policy @@ -14,6 +13,8 @@ Metric = load_model('monitoring', 'Metric') DeviceData = load_model('device_monitoring', 'DeviceData') Chart = load_model('monitoring', 'Chart') +Config = load_model('config', 'Config') +Device = load_model('config', 'Device') class TestDeviceMonitoringMixin(CreateConfigTemplateMixin, TestMonitoringMixin): diff --git a/openwisp_monitoring/device/tests/test_admin.py b/openwisp_monitoring/device/tests/test_admin.py index 306ce260b..2464f86f6 100644 --- a/openwisp_monitoring/device/tests/test_admin.py +++ b/openwisp_monitoring/device/tests/test_admin.py @@ -7,6 +7,7 @@ Chart = load_model('monitoring', 'Chart') Metric = load_model('monitoring', 'Metric') DeviceData = load_model('device_monitoring', 'DeviceData') +User = get_user_model() class TestAdmin(DeviceMonitoringTestCase): @@ -15,7 +16,6 @@ class TestAdmin(DeviceMonitoringTestCase): """ def _login_admin(self): - User = get_user_model() u = User.objects.create_superuser('admin', 'admin', 'test@test.com') self.client.force_login(u) diff --git a/openwisp_monitoring/device/tests/test_api.py b/openwisp_monitoring/device/tests/test_api.py index 5444019e7..16ce489f8 100644 --- a/openwisp_monitoring/device/tests/test_api.py +++ b/openwisp_monitoring/device/tests/test_api.py @@ -367,12 +367,12 @@ def test_invalid_chart_config(self): d = self._create_device(organization=self._create_org()) m = self._create_object_metric(name='test_metric', content_object=d) c = self._create_chart(metric=m, test_data=None) + c.configuration = 'invalid' + c.save() with redirect_stderr(StringIO()) as stderr: - c.configuration = 'invalid' - c.save() - r = self.client.get(self._url(d.pk.hex, d.key)) + response = self.client.get(self._url(d.pk.hex, d.key)) self.assertIn('InvalidChartConfigException', stderr.getvalue()) - self.assertEqual(r.status_code, 200) + self.assertEqual(response.status_code, 200) def test_available_memory(self): o = self._create_org() diff --git a/openwisp_monitoring/device/tests/test_device_notifications.py b/openwisp_monitoring/device/tests/test_device_notifications.py index 7723ae90a..f4f80991f 100644 --- a/openwisp_monitoring/device/tests/test_device_notifications.py +++ b/openwisp_monitoring/device/tests/test_device_notifications.py @@ -2,11 +2,11 @@ from django.utils.html import strip_tags from swapper import load_model -from openwisp_controller.connection.models import Credentials, DeviceConnection - from .test_models import BaseTestCase Notification = load_model('openwisp_notifications', 'Notification') +Credentials = load_model('connection', 'Credentials') +DeviceConnection = load_model('connection', 'DeviceConnection') class TestDeviceNotifications(BaseTestCase): @@ -26,7 +26,7 @@ def _generic_notification_test( n = Notification.objects.first() email = mail.outbox.pop() - html_message, content_type = email.alternatives.pop() + html_message, _ = email.alternatives.pop() self.assertEqual(n.type, exp_type) self.assertEqual(n.level, exp_level) self.assertEqual(n.verb, exp_verb) @@ -45,7 +45,7 @@ def _generic_notification_test( self.assertIn(n.message, html_message) self.assertIn( f'' - 'For further information see "device: test-device".', + 'For further information see "device: default.test.device".', html_message, ) diff --git a/openwisp_monitoring/device/tests/test_models.py b/openwisp_monitoring/device/tests/test_models.py index c672dbcef..3d7613f00 100644 --- a/openwisp_monitoring/device/tests/test_models.py +++ b/openwisp_monitoring/device/tests/test_models.py @@ -6,7 +6,6 @@ from openwisp_notifications.signals import notify from swapper import load_model -from openwisp_controller.connection.models import Credentials, DeviceConnection from openwisp_utils.tests import catch_signal from ..signals import health_status_changed @@ -15,6 +14,8 @@ Check = load_model('check', 'Check') DeviceMonitoring = load_model('device_monitoring', 'DeviceMonitoring') DeviceData = load_model('device_monitoring', 'DeviceData') +DeviceConnection = load_model('connection', 'DeviceConnection') +Credentials = load_model('connection', 'Credentials') class BaseTestCase(DeviceMonitoringTestCase): diff --git a/openwisp_monitoring/monitoring/migrations/0001_initial.py b/openwisp_monitoring/monitoring/migrations/0001_initial.py index b228f7956..bdec50c2f 100644 --- a/openwisp_monitoring/monitoring/migrations/0001_initial.py +++ b/openwisp_monitoring/monitoring/migrations/0001_initial.py @@ -232,7 +232,7 @@ class Migration(migrations.Migration): 'metric', models.OneToOneField( on_delete=django.db.models.deletion.CASCADE, - to='monitoring.Metric', + to=swapper.get_model_name('monitoring', 'Metric'), ), ), ], @@ -245,7 +245,8 @@ class Migration(migrations.Migration): model_name='graph', name='metric', field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to='monitoring.Metric' + on_delete=django.db.models.deletion.CASCADE, + to=swapper.get_model_name('monitoring', 'Metric'), ), ), migrations.AlterUniqueTogether( diff --git a/openwisp_monitoring/monitoring/tests/test_monitoring_notifications.py b/openwisp_monitoring/monitoring/tests/test_monitoring_notifications.py index 1a932cbbc..677ff1da0 100644 --- a/openwisp_monitoring/monitoring/tests/test_monitoring_notifications.py +++ b/openwisp_monitoring/monitoring/tests/test_monitoring_notifications.py @@ -6,13 +6,14 @@ from django.utils.html import strip_tags from swapper import load_model -from openwisp_controller.config.models import Config, Device -from openwisp_users.models import OrganizationUser - from ...device.tests import DeviceMonitoringTestCase Metric = load_model('monitoring', 'Metric') Notification = load_model('openwisp_notifications', 'Notification') +Device = load_model('config', 'Device') +Config = load_model('config', 'Config') +OrganizationUser = load_model('openwisp_users', 'OrganizationUser') + notification_queryset = Notification.objects.order_by('timestamp') start_time = timezone.now() ten_minutes_ago = start_time - timedelta(minutes=10) @@ -331,7 +332,7 @@ def test_email_notification(self): self.assertIn(n.message, html_message) self.assertIn( f'' - 'For further information see "device: test-device".', + 'For further information see "device: default.test.device".', html_message, ) @@ -347,7 +348,7 @@ def test_email_notification(self): self.assertIn(n.message, html_message) self.assertIn( f'' - 'For further information see "device: test-device".', + 'For further information see "device: default.test.device".', html_message, ) diff --git a/setup.cfg b/setup.cfg index 7aae69b3b..f308e9d79 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,7 +14,7 @@ max-line-length = 110 ignore = W605, W503, W504 [isort] -known_third_party = django, django_x509, django_netjsonconfig +known_third_party = django, django_x509 known_first_party = openwisp_users, openwisp_utils, openwisp_controller line_length=88 default_section=THIRDPARTY diff --git a/tests/openwisp2/sample_device_monitoring/migrations/0001_initial.py b/tests/openwisp2/sample_device_monitoring/migrations/0001_initial.py index a59c3d4b7..c99871893 100644 --- a/tests/openwisp2/sample_device_monitoring/migrations/0001_initial.py +++ b/tests/openwisp2/sample_device_monitoring/migrations/0001_initial.py @@ -6,6 +6,7 @@ import model_utils.fields import openwisp_monitoring.device.base.models import uuid +import swapper class Migration(migrations.Migration): @@ -23,7 +24,7 @@ class Migration(migrations.Migration): options={'proxy': True, 'indexes': [], 'constraints': [],}, bases=( openwisp_monitoring.device.base.models.AbstractDeviceData, - 'config.device', + swapper.get_model_name('config', 'Device'), ), ), migrations.CreateModel( @@ -76,7 +77,7 @@ class Migration(migrations.Migration): models.OneToOneField( on_delete=django.db.models.deletion.CASCADE, related_name='monitoring', - to='config.Device', + to=swapper.get_model_name('config', 'Device'), ), ), ( diff --git a/tests/openwisp2/sample_device_monitoring/models.py b/tests/openwisp2/sample_device_monitoring/models.py index 37cc6986c..a6541afa7 100644 --- a/tests/openwisp2/sample_device_monitoring/models.py +++ b/tests/openwisp2/sample_device_monitoring/models.py @@ -6,10 +6,10 @@ ) from swapper import get_model_name -from openwisp_controller.config.models import Device +from openwisp_controller.config.models import Device as BaseDevice -class DeviceData(AbstractDeviceData, Device): +class DeviceData(AbstractDeviceData, BaseDevice): checks = GenericRelation(get_model_name('check', 'Check')) metrics = GenericRelation(get_model_name('monitoring', 'Metric')) diff --git a/tests/openwisp2/sample_monitoring/migrations/0001_initial.py b/tests/openwisp2/sample_monitoring/migrations/0001_initial.py index 004fc62b9..1155f89db 100644 --- a/tests/openwisp2/sample_monitoring/migrations/0001_initial.py +++ b/tests/openwisp2/sample_monitoring/migrations/0001_initial.py @@ -6,6 +6,7 @@ import django.utils.timezone import model_utils.fields import uuid +import swapper from openwisp_monitoring.monitoring.charts import get_chart_configuration_choices @@ -131,7 +132,7 @@ class Migration(migrations.Migration): 'metric', models.OneToOneField( on_delete=django.db.models.deletion.CASCADE, - to='sample_monitoring.Metric', + to=swapper.get_model_name('monitoring', 'Metric'), ), ), ], @@ -177,7 +178,7 @@ class Migration(migrations.Migration): 'metric', models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, - to='sample_monitoring.Metric', + to=swapper.get_model_name('monitoring', 'Metric'), ), ), ], diff --git a/tests/openwisp2/settings.py b/tests/openwisp2/settings.py index 140af031e..25dbde11b 100644 --- a/tests/openwisp2/settings.py +++ b/tests/openwisp2/settings.py @@ -71,7 +71,6 @@ ] EXTENDED_APPS = [ - 'django_netjsonconfig', 'django_x509', 'django_loci', ]