diff --git a/openwisp_monitoring/check/base/models.py b/openwisp_monitoring/check/base/models.py index 0d040a7b1..2f2e05b2a 100644 --- a/openwisp_monitoring/check/base/models.py +++ b/openwisp_monitoring/check/base/models.py @@ -98,6 +98,11 @@ def perform_check(self, store=True): """ initiates check instance and calls its check method """ + if ( + hasattr(self.content_object, 'organization_id') + and self.content_object.organization.is_active is False + ): + return return self.check_instance.check(store=True) def perform_check_delayed(self, duration=0): diff --git a/openwisp_monitoring/check/tests/test_models.py b/openwisp_monitoring/check/tests/test_models.py index 0f71fa94d..c0e5aef8c 100644 --- a/openwisp_monitoring/check/tests/test_models.py +++ b/openwisp_monitoring/check/tests/test_models.py @@ -282,6 +282,16 @@ def test_device_unknown_no_config_check(self): self.assertEqual(Notification.objects.count(), 0) self.assertIsNone(c2.perform_check()) + def test_device_organization_disabled_check_not_performed(self): + self._create_config( + status='modified', organization=self._create_org(is_active=False) + ) + self.assertEqual(Check.objects.count(), 3) + check = Check.objects.filter(check_type=self._CONFIG_APPLIED).first() + with patch(f'{self._CONFIG_APPLIED}.check') as mocked_check: + check.perform_check() + mocked_check.assert_not_called() + def test_config_check_problem_with_interval(self): self._create_admin() d = self._create_device(organization=self._create_org()) diff --git a/openwisp_monitoring/device/admin.py b/openwisp_monitoring/device/admin.py index 3c244a3a1..c242dbea1 100644 --- a/openwisp_monitoring/device/admin.py +++ b/openwisp_monitoring/device/admin.py @@ -324,7 +324,7 @@ def get_inlines(self, request, obj=None): for inline in inlines: if not hasattr(inline, 'sortable_options'): inline.sortable_options = {'disabled': True} - if not obj or obj._state.adding: + if not obj or obj._state.adding or obj.organization.is_active is False: inlines.remove(CheckInline) inlines.remove(MetricInline) return inlines diff --git a/openwisp_monitoring/device/api/views.py b/openwisp_monitoring/device/api/views.py index 2dd956256..697318dc6 100644 --- a/openwisp_monitoring/device/api/views.py +++ b/openwisp_monitoring/device/api/views.py @@ -111,10 +111,14 @@ class DeviceMetricView( """ model = DeviceData - queryset = DeviceData.objects.only( - 'id', - 'key', - ).all() + queryset = ( + DeviceData.objects.filter(organization__is_active=True) + .only( + 'id', + 'key', + ) + .all() + ) serializer_class = serializers.Serializer permission_classes = [DevicePermission] schema = schema diff --git a/openwisp_monitoring/device/apps.py b/openwisp_monitoring/device/apps.py index fb6685ff6..739709878 100644 --- a/openwisp_monitoring/device/apps.py +++ b/openwisp_monitoring/device/apps.py @@ -58,6 +58,7 @@ def connect_device_signals(self): DeviceLocation = load_model('geo', 'DeviceLocation') Metric = load_model('monitoring', 'Metric') Chart = load_model('monitoring', 'Chart') + Organization = load_model('openwisp_users', 'Organization') post_save.connect( self.device_post_save_receiver, @@ -120,6 +121,11 @@ def connect_device_signals(self): sender=DeviceLocation, dispatch_uid='post_delete_devicelocation_invalidate_devicedata_cache', ) + post_save.connect( + self.organization_post_save_receiver, + sender=Organization, + dispatch_uid='post_save_organization_disabled_monitoring', + ) @classmethod def device_post_save_receiver(cls, instance, created, **kwargs): @@ -134,6 +140,13 @@ def device_post_delete_receiver(cls, instance, **kwargs): instance.checks.all().delete() instance.metrics.all().delete() + @classmethod + def organization_post_save_receiver(cls, instance, *args, **kwargs): + if instance.is_active is False: + from .tasks import handle_disabled_organization + + handle_disabled_organization.delay(str(instance.id)) + def device_recovery_detection(self): if not app_settings.DEVICE_RECOVERY_DETECTION: return diff --git a/openwisp_monitoring/device/base/models.py b/openwisp_monitoring/device/base/models.py index 2d8eba8b9..b3e3f5d4c 100644 --- a/openwisp_monitoring/device/base/models.py +++ b/openwisp_monitoring/device/base/models.py @@ -435,6 +435,25 @@ def is_metric_critical(metric): return True return False + @classmethod + def handle_disabled_organization(cls, organization_id): + """ + Clears the management IP of all devices belonging to a + disabled organization and set their monitoring status to 'unknown'. + + Parameters: + organization_id (int): The ID of the disabled organization. + + Returns: + None + """ + load_model('config', 'Device').objects.filter( + organization_id=organization_id + ).update(management_ip='') + cls.objects.filter(device__organization_id=organization_id).update( + status='unknown' + ) + class AbstractWifiClient(TimeStampedEditableModel): id = None diff --git a/openwisp_monitoring/device/tasks.py b/openwisp_monitoring/device/tasks.py index 6799acd3c..03ab8577a 100644 --- a/openwisp_monitoring/device/tasks.py +++ b/openwisp_monitoring/device/tasks.py @@ -63,3 +63,9 @@ def write_device_metrics(pk, data, time=None, current=False): except DeviceData.DoesNotExist: return device_data.writer.write(data, time, current) + + +@shared_task(base=OpenwispCeleryTask) +def handle_disabled_organization(organization_id): + DeviceMonitoring = load_model('device_monitoring', 'DeviceMonitoring') + DeviceMonitoring.handle_disabled_organization(organization_id) diff --git a/openwisp_monitoring/device/tests/test_admin.py b/openwisp_monitoring/device/tests/test_admin.py index 0cdf4e02e..8699f856e 100644 --- a/openwisp_monitoring/device/tests/test_admin.py +++ b/openwisp_monitoring/device/tests/test_admin.py @@ -321,6 +321,25 @@ def test_device_add_view(self): self.assertContains(r, '

Map

') self.assertContains(r, '

Credentials

') + def test_device_disabled_organization_admin(self): + self.create_test_data() + device = Device.objects.first() + Check.objects.create( + name='Ping check', + check_type=CHECK_CLASSES[0][0], + content_object=device, + params={}, + ) + org = device.organization + org.is_active = False + org.save() + url = reverse('admin:config_device_change', args=[device.pk]) + response = self.client.get(url) + self.assertContains(response, '

Status

') + self.assertContains(response, '

Charts

') + self.assertNotContains(response, '

Checks

') + self.assertNotContains(response, '

AlertSettings

') + def test_remove_invalid_interface(self): d = self._create_device(organization=self._create_org()) dd = DeviceData(name='test-device', pk=d.pk) diff --git a/openwisp_monitoring/device/tests/test_api.py b/openwisp_monitoring/device/tests/test_api.py index 218ecbc0d..4c6f46a39 100644 --- a/openwisp_monitoring/device/tests/test_api.py +++ b/openwisp_monitoring/device/tests/test_api.py @@ -341,6 +341,15 @@ def test_200_no_date_supplied(self): r = self.client.post(url, netjson, content_type='application/json') self.assertEqual(r.status_code, 200) + def test_404_disabled_organization(self): + org = self._create_org(is_active=False) + device = self._create_device(organization=org) + with self.assertNumQueries(2): + response = self._post_data(device.id, device.key, self._data()) + self.assertEqual(response.status_code, 404) + self.assertEqual(self.metric_queryset.count(), 0) + self.assertEqual(self.chart_queryset.count(), 0) + def test_garbage_wireless_clients(self): o = self._create_org() d = self._create_device(organization=o) diff --git a/openwisp_monitoring/device/tests/test_models.py b/openwisp_monitoring/device/tests/test_models.py index a6331b7fb..e539c0700 100644 --- a/openwisp_monitoring/device/tests/test_models.py +++ b/openwisp_monitoring/device/tests/test_models.py @@ -720,6 +720,20 @@ def test_deleting_device_deletes_tsdb(self): self.assertEqual(self._read_metric(ping1), []) self.assertNotEqual(self._read_metric(ping2), []) + def test_handle_disabled_organization(self): + device_monitoring, _, _, _ = self._create_env() + device = device_monitoring.device + device.management_ip = '10.10.0.5' + device.save() + self.assertEqual(device_monitoring.status, 'ok') + org = device.organization + org.is_active = False + org.save(update_fields=['is_active']) + device_monitoring.refresh_from_db() + device.refresh_from_db() + self.assertEqual(device_monitoring.status, 'unknown') + self.assertEqual(device.management_ip, None) + class TestWifiClientSession(TestWifiClientSessionMixin, TestCase): wifi_client_model = WifiClient