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] Implement SNMP check #297 #309

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
76 changes: 76 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ Available Features
* Extensible metrics and charts: it's possible to define new metrics and new charts
* API to retrieve the chart metrics and status information of each device
based on `NetJSON DeviceMonitoring <http://netjson.org/docs/what.html#devicemonitoring>`_
* Collection of monitoring information via `SNMP <#snmp>`_

------------

Expand Down Expand Up @@ -893,6 +894,69 @@ configuration status of a device changes, this ensures the check reacts
quickly to events happening in the network and informs the user promptly
if there's anything that is not working as intended.

SNMP
~~~~

This check provides an alternative way to collect monitoring information from devices that don't have
the ability to use `openwisp-monitoring agent <https://github.com/openwisp/openwrt-openwisp-monitoring>`_.
The information is collected via SNMP using `netengine <https://github.com/openwisp/netengine/>`_. The devices
need to have an SNMP daemon in order for this to work.
This check is disabled by default, you may choose to enable auto creation of this check by setting
`OPENWISP_MONITORING_AUTO_SNMP <#OPENWISP_MONITORING_AUTO_SNMP>`_ to ``True``.

Instructions to configure an SNMP Check
---------------------------------------

The following steps will help you use the SNMP Check feature:

1. Register your device to OpenWISP
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Register your device making sure the ``mac address`` and ``management ip`` are correct:

.. image:: https://raw.githubusercontent.com/openwisp/openwisp-monitoring/issues/297-snmp-check/docs/snmp-check/snmp-check-1.png
:alt: Creating a device example

It is recommended to keep the `OPENWISP_MONITORING_AUTO_CLEAR_MANAGEMENT_IP <#OPENWISP_MONITORING_AUTO_CLEAR_MANAGEMENT_IP>`_
setting off to avoid losing the ``management ip``. This is important for the check to work.

1. Create SNMP access credentials and add them to your device
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In the admin, go to ``Access Credentials > Add Access Credentials`` and select a type suitable for your device's backend.

.. image:: https://raw.githubusercontent.com/openwisp/openwisp-monitoring/issues/297-snmp-check/docs/snmp-check/snmp-check-2.png
:alt: Creating access credentials example

Then either enable ``Auto Add`` (it needs to have the same organization as the device for this to work), or add it to
your device manually as shown below.

.. image:: https://raw.githubusercontent.com/openwisp/openwisp-monitoring/issues/297-snmp-check/docs/snmp-check/snmp-check-3.png
:alt: Adding access credentials to device example

3. Create an SNMP check
~~~~~~~~~~~~~~~~~~~~~~~

You can skip this step and let OpenWISP do this automatically if you have the setting
`OPENWISP_MONITORING_AUTO_SNMP <#OPENWISP_MONITORING_AUTO_SNMP>`_ enabled.

To create the check manually, in the admin, go to ``Check > Add Check``. Now create a check as shown below:

.. image:: https://raw.githubusercontent.com/openwisp/openwisp-monitoring/issues/297-snmp-check/docs/snmp-check/snmp-check-4.png
:alt: Creating SNMP check example

Here, ``Object id`` is the UUID of the device you just created.

4. Run the check
~~~~~~~~~~~~~~~~

This should happen automatically if you have celery running in the background. For testing, you can
run this check manually using the `run_checks <#run_checks>`_ command. After that, you should see the
device charts and status instantly.

.. image:: https://raw.githubusercontent.com/openwisp/openwisp-monitoring/issues/297-snmp-check/docs/snmp-check/snmp-check-5.png
:alt: Device status collected by snmp example

Settings
--------

Expand Down Expand Up @@ -976,6 +1040,18 @@ validating custom parameters of a ``Check`` object.
This setting allows you to choose whether `config_applied <#configuration-applied>`_ checks should be
created automatically for newly registered devices. It's enabled by default.

``OPENWISP_MONITORING_AUTO_SNMP``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+--------------+-------------+
| **type**: | ``bool`` |
+--------------+-------------+
| **default**: | ``False`` |
+--------------+-------------+

Whether `SNMP <#snmp>`_ checks are created automatically for devices. The devices need to have an snmp daemon
installed in order for this check to work.

``OPENWISP_MONITORING_AUTO_CHARTS``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
Binary file added docs/snmp-check/snmp-check-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/snmp-check/snmp-check-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/snmp-check/snmp-check-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/snmp-check/snmp-check-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/snmp-check/snmp-check-5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions openwisp_monitoring/check/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,12 @@ def _connect_signals(self):
sender=load_model('config', 'Device'),
dispatch_uid='auto_config_check',
)

if app_settings.AUTO_SNMP:
from .base.models import auto_snmp_receiver

post_save.connect(
auto_snmp_receiver,
sender=load_model('config', 'Device'),
dispatch_uid='auto_snmp',
)
24 changes: 23 additions & 1 deletion openwisp_monitoring/check/base/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
from jsonfield import JSONField

from openwisp_monitoring.check import settings as app_settings
from openwisp_monitoring.check.tasks import auto_create_config_check, auto_create_ping
from openwisp_monitoring.check.tasks import (
auto_create_config_check,
auto_create_ping,
auto_create_snmp_devicemonitoring,
)
from openwisp_utils.base import TimeStampedEditableModel

from ...utils import transaction_on_commit
Expand Down Expand Up @@ -116,3 +120,21 @@ def auto_config_check_receiver(sender, instance, created, **kwargs):
object_id=str(instance.pk),
)
)


def auto_snmp_receiver(sender, instance, created, **kwargs):
"""
Implements OPENWISP_MONITORING_AUTO_SNMP
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_snmp_devicemonitoring.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,2 +1,3 @@
from .config_applied import ConfigApplied # noqa
from .ping import Ping # noqa
from .snmp_devicemonitoring import Snmp # noqa
13 changes: 13 additions & 0 deletions openwisp_monitoring/check/classes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
from django.core.exceptions import ValidationError
from swapper import load_model

from .. import settings as app_settings

Check = load_model('check', 'Check')
Metric = load_model('monitoring', 'Metric')
Device = load_model('config', 'Device')
DeviceData = load_model('device_monitoring', 'DeviceData')


class BaseCheck(object):
Expand Down Expand Up @@ -48,3 +51,13 @@ def _get_or_create_metric(self, configuration=None):
)
metric, created = Metric._get_or_create(**options)
return metric, created

def _get_ip(self):
"""
Figures out ip to use or fails raising OperationalError
"""
device = self.related_object
ip = device.management_ip
if not ip and not app_settings.MANAGEMENT_IP_ONLY:
ip = device.last_ip
return ip
10 changes: 0 additions & 10 deletions openwisp_monitoring/check/classes/ping.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,16 +134,6 @@ def _get_param(self, param):
"""
return self.params.get(param, self.schema['properties'][param]['default'])

def _get_ip(self):
"""
Figures out ip to use or fails raising OperationalError
"""
device = self.related_object
ip = device.management_ip
if not ip and not app_settings.MANAGEMENT_IP_ONLY:
ip = device.last_ip
return ip

def _command(self, command):
"""
Executes command (easier to mock)
Expand Down
62 changes: 62 additions & 0 deletions openwisp_monitoring/check/classes/snmp_devicemonitoring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from django.utils.functional import cached_property
from netengine.backends.snmp.airos import AirOS
from netengine.backends.snmp.openwrt import OpenWRT
from swapper import load_model

from openwisp_monitoring.device.api.views import MetricChartsMixin

from .base import BaseCheck

Chart = load_model('monitoring', 'Chart')
Metric = load_model('monitoring', 'Metric')
Device = load_model('config', 'Device')
DeviceData = load_model('device_monitoring', 'DeviceData')
Credentials = load_model('connection', 'Credentials')
AlertSettings = load_model('monitoring', 'AlertSettings')


class Snmp(BaseCheck, MetricChartsMixin):
def check(self, store=True):
result = self.netengine_instance.to_dict()
self._init_previous_data()
self.related_object.data = result
if store:
self.store_result(result)
return result

def store_result(self, data):
"""
store result in the DB
"""
pk = self.related_object.pk
device_data = DeviceData.objects.get(pk=pk)
device_data.data = data
device_data.save_data()
self._write(pk)

@cached_property
def netengine_instance(self):
ip = self._get_ip()
connector = self._get_connnector()
return connector(host=ip, **self._get_credential_params())

@cached_property
def credential_instance(self):
return Credentials.objects.filter(
deviceconnection__device_id=self.related_object,
connector__endswith='OpenWRTSnmp',
).last()

def _get_connnector(self):
connectors = {
'openwisp_controller.connection.connectors.openwrt.snmp.OpenWRTSnmp': OpenWRT,
'openwisp_controller.connection.connectors.airos.snmp.AirOsSnmp': AirOS,
}
try:
return connectors.get(self.credential_instance.connector, OpenWRT)
except AttributeError:
# in case credentials are not available
return OpenWRT

def _get_credential_params(self):
return getattr(self.credential_instance, 'params', {})
5 changes: 5 additions & 0 deletions openwisp_monitoring/check/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@
(
('openwisp_monitoring.check.classes.Ping', 'Ping'),
('openwisp_monitoring.check.classes.ConfigApplied', 'Configuration Applied'),
(
'openwisp_monitoring.check.classes.Snmp',
'SNMP Device Monitoring',
),
),
)
AUTO_PING = get_settings_value('AUTO_PING', True)
AUTO_CONFIG_CHECK = get_settings_value('AUTO_DEVICE_CONFIG_CHECK', True)
AUTO_SNMP = get_settings_value('AUTO_SNMP', False)
MANAGEMENT_IP_ONLY = get_settings_value('MANAGEMENT_IP_ONLY', True)
PING_CHECK_CONFIG = get_settings_value('PING_CHECK_CONFIG', {})
29 changes: 29 additions & 0 deletions openwisp_monitoring/check/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,32 @@ def auto_create_config_check(
)
check.full_clean()
check.save()


@shared_task
def auto_create_snmp_devicemonitoring(
model, app_label, object_id, check_model=None, content_type_model=None
):
"""
Called by openwisp_monitoring.check.models.auto_snmp_receiver
"""
Check = check_model or get_check_model()
devicemonitoring_path = 'openwisp_monitoring.check.classes.Snmp'
has_check = Check.objects.filter(
object_id=object_id,
content_type__model='device',
check_type=devicemonitoring_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(app_label=app_label, model=model)
check = Check(
name='SNMP Device Monitoring',
check_type=devicemonitoring_path,
content_type=ct,
object_id=object_id,
)
check.full_clean()
check.save()
Loading