Skip to content

Commit

Permalink
feat(snmp): add SNMP configuration to devices
Browse files Browse the repository at this point in the history
- Configure SNMP contacts and locations
- Assign SNMP communities to each device with read or read-write types
  • Loading branch information
cpaillet committed Jun 10, 2024
1 parent 61d560a commit 75de0b4
Show file tree
Hide file tree
Showing 17 changed files with 369 additions and 37 deletions.
30 changes: 30 additions & 0 deletions netbox_cmdb/netbox_cmdb/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from netbox_cmdb.models.bgp_community_list import BGPCommunityList, BGPCommunityListTerm
from netbox_cmdb.models.prefix_list import PrefixList, PrefixListTerm
from netbox_cmdb.models.route_policy import RoutePolicy, RoutePolicyTerm
from netbox_cmdb.models.snmp import SNMP, SNMPCommunity


class BaseAdmin(admin.ModelAdmin):
Expand Down Expand Up @@ -173,6 +174,35 @@ class BGPCommunityListAdmin(BaseAdmin):
)


@admin.register(SNMP)
class SNMPAdmin(BaseAdmin):
"""Admin class to manage SNMPCommunity objects."""

list_display = (
"device",
"community_list_display",
"location",
"contact",
)

def community_list_display(self, obj):
return ", ".join([str(community) for community in obj.community_list.all()])

community_list_display.short_description = "Community List"


@admin.register(SNMPCommunity)
class SNMPCommunitytAdmin(BaseAdmin):
"""Admin class to manage SNMP objects."""

list_display = (
"name",
"type",
"community",
)
search_fields = ("name", "name")


# We need to register Netbox core models to the Admin page or we won't be able to lookup
# dynamically over the objects.
@admin.register(IPAddress)
Expand Down
Empty file.
19 changes: 19 additions & 0 deletions netbox_cmdb/netbox_cmdb/api/snmp/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Route Policy serializers."""

from rest_framework.serializers import ModelSerializer

from netbox_cmdb.models.snmp import SNMP, SNMPCommunity


class SNMPCommunitySerializer(ModelSerializer):

class Meta:
model = SNMPCommunity
fields = ["name", "community", "type"]


class SNMPSerializer(ModelSerializer):

class Meta:
model = SNMP
fields = ["community_list", "location", "contact", "device"]
18 changes: 18 additions & 0 deletions netbox_cmdb/netbox_cmdb/api/snmp/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""Route Policy views."""

from netbox_cmdb import filtersets

from netbox_cmdb.api.route_policy.serializers import WritableRoutePolicySerializer
from netbox_cmdb.api.viewsets import CustomNetBoxModelViewSet
from netbox_cmdb.models.snmp import SNMPCommunity
from netbox_cmdb.api.snmp.serializers import SNMPCommunitySerializer


class SNMPCommunityViewSet(CustomNetBoxModelViewSet):
queryset = SNMPCommunity.objects.all()
serializer_class = SNMPCommunitySerializer
filterset_fields = [
"name",
"community",
"type",
]
3 changes: 3 additions & 0 deletions netbox_cmdb/netbox_cmdb/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from netbox_cmdb.api.bgp_community_list.views import BGPCommunityListViewSet
from netbox_cmdb.api.prefix_list.views import PrefixListViewSet
from netbox_cmdb.api.route_policy.views import RoutePolicyViewSet
from netbox_cmdb.api.snmp.views import SNMPCommunityViewSet

router = NetBoxRouter()

Expand All @@ -21,6 +22,8 @@
router.register("peer-groups", BGPPeerGroupViewSet)
router.register("prefix-lists", PrefixListViewSet)
router.register("route-policies", RoutePolicyViewSet)
# router.register("snmp", SNMPViewSet)
router.register("snmp-community", SNMPCommunityViewSet)

urlpatterns = [
path(
Expand Down
12 changes: 12 additions & 0 deletions netbox_cmdb/netbox_cmdb/choices.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
from utilities.choices import ChoiceSet


class SNMPCommunityType(ChoiceSet):
"""A ChoiceSet to define the communityType."""

RO = "readonly"
RW = "readwrite"

CHOICES = [
(RO, "ReadOnly", "green"),
(RW, "Read&Write", "red"),
]


class AssetStateChoices(ChoiceSet):
"""A ChoiceSet to define the state of an asset."""

Expand Down
17 changes: 16 additions & 1 deletion netbox_cmdb/netbox_cmdb/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
from django import forms
from django.utils.translation import gettext as _
from extras.models import Tag
from netbox_cmdb.models.snmp import SNMP, SNMPCommunity
from utilities.forms import DynamicModelMultipleChoiceField
from utilities.forms.fields import DynamicModelChoiceField, MultipleChoiceField

from netbox.forms import NetBoxModelFilterSetForm, NetBoxModelForm
from netbox_cmdb.choices import AssetMonitoringStateChoices, AssetStateChoices
from netbox_cmdb.choices import AssetMonitoringStateChoices, AssetStateChoices, SNMPCommunityType
from netbox_cmdb.models.bgp import ASN, BGPPeerGroup, BGPSession


Expand Down Expand Up @@ -83,3 +84,17 @@ def clean(self):
pass # such validation is already handled in previous validation steps
if count < 1:
raise forms.ValidationError("You must have at least one term.")


class SNMPGroupForm(NetBoxModelForm):
device = DynamicModelChoiceField(queryset=Device.objects.all())

class Meta:
model = SNMP
fields = ["device", "community_list", "location", "contact"]


class SNMPCommunityGroupForm(NetBoxModelForm):
class Meta:
model = SNMPCommunity
fields = ["name", "community", "type"]
42 changes: 42 additions & 0 deletions netbox_cmdb/netbox_cmdb/migrations/0040_snmpcommunity_snmp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('dcim', '0161_cabling_cleanup'),
('netbox_cmdb', '0039_logicalinterface'),
]

operations = [
migrations.CreateModel(
name='SNMPCommunity',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('name', models.CharField(max_length=100, unique=True)),
('community', models.CharField(max_length=31)),
('type', models.CharField(default='Read-Only', max_length=10)),
],
options={
'verbose_name_plural': 'SNMP Communities',
},
),
migrations.CreateModel(
name='SNMP',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('location', models.CharField(max_length=31)),
('contact', models.CharField(max_length=31)),
('community_list', models.ManyToManyField(blank=True, default=None, related_name='%(class)s_community', to='netbox_cmdb.snmpcommunity')),
('device', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='dcim.device')),
],
options={
'verbose_name_plural': 'SNMP',
},
),
]
46 changes: 46 additions & 0 deletions netbox_cmdb/netbox_cmdb/models/snmp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from django.db import models
from netbox_cmdb.choices import SNMPCommunityType
from netbox.models import ChangeLoggedModel
from django.core.exceptions import ValidationError
from django.contrib.postgres.fields import ArrayField


class SNMPCommunity(ChangeLoggedModel):
"""A Snmp Community"""

name = models.CharField(max_length=100, unique=True)
community = models.CharField(max_length=31)
type = models.CharField(
max_length=10,
choices=SNMPCommunityType,
default=SNMPCommunityType.RO,
help_text="Defines the community string permissions of either read-only RO or read-write RW",
)

def __str__(self):
return f"{self.name}"

class Meta:
verbose_name_plural = "SNMP Communities"


class SNMP(ChangeLoggedModel):
"""A Snmp configuration"""

community_list = models.ManyToManyField(
to=SNMPCommunity, related_name="%(class)s_community", blank=True, default=None
)

location = models.CharField(max_length=31)
contact = models.CharField(max_length=31)

device = models.OneToOneField(
to="dcim.Device",
on_delete=models.CASCADE,
)

class Meta:
verbose_name_plural = "SNMP"

def __str__(self):
return f"SNMP configuration of {self.device.name}"
24 changes: 24 additions & 0 deletions netbox_cmdb/netbox_cmdb/navigation.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,28 @@
),
),
),
PluginMenuItem(
link="plugins:netbox_cmdb:snmp_list",
link_text="SNMP",
buttons=(
PluginMenuButton(
link="plugins:netbox_cmdb:snmp_add",
title="SNMP",
icon_class="mdi mdi-plus-thick",
color=ButtonColorChoices.GREEN,
),
),
),
PluginMenuItem(
link="plugins:netbox_cmdb:snmpcommunity_list",
link_text="SNMP Community",
buttons=(
PluginMenuButton(
link="plugins:netbox_cmdb:snmpcommunity_add",
title="SNMP Community",
icon_class="mdi mdi-plus-thick",
color=ButtonColorChoices.GREEN,
),
),
),
)
15 changes: 15 additions & 0 deletions netbox_cmdb/netbox_cmdb/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from netbox.tables import NetBoxTable, columns
from netbox_cmdb.models.bgp import ASN, BGPPeerGroup, BGPSession
from netbox_cmdb.models.snmp import SNMP, SNMPCommunity


class ASNTable(NetBoxTable):
Expand Down Expand Up @@ -60,3 +61,17 @@ class Meta(NetBoxTable.Meta):
"route_policy_out",
"maximum_prefixes",
)


class SNMPTable(NetBoxTable):
device = tables.LinkColumn()

class Meta(NetBoxTable.Meta):
model = SNMP
fields = ("device", "community_list", "location", "contact")


class SNMPCommunityTable(NetBoxTable):
class Meta(NetBoxTable.Meta):
model = SNMPCommunity
fields = ("name", "community", "type")
28 changes: 1 addition & 27 deletions netbox_cmdb/netbox_cmdb/tests/bgp/test_bgp_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from ipam.models.ip import IPAddress
from netaddr import IPNetwork
from rest_framework.exceptions import ErrorDetail
from netbox_cmdb.tests.common import BaseTestCase
from tenancy.models.tenants import Tenant

from netbox_cmdb.api.bgp.serializers import BGPGlobalSerializer, BGPSessionSerializer
Expand All @@ -14,33 +15,6 @@
from netbox_cmdb.models.route_policy import RoutePolicy, RoutePolicyTerm


class BaseTestCase(TestCase):
def setUp(self):
site = Site.objects.create(name="SiteTest", slug="site-test")
manufacturer = Manufacturer.objects.create(name="test", slug="test")
device_type = DeviceType.objects.create(
manufacturer=manufacturer, model="model-test", slug="model-test"
)
device_role = DeviceRole.objects.create(name="role-test", slug="role-test")
self.device1 = Device.objects.create(
name="router-test1",
device_role=device_role,
device_type=device_type,
site=site,
)
self.asn1 = ASN.objects.create(number="1", organization_name="router-test1")
self.ip_address1 = IPAddress.objects.create(address="10.0.0.1/32")
self.device2 = Device.objects.create(
name="router-test2",
device_role=device_role,
device_type=device_type,
site=site,
)
self.asn2 = ASN.objects.create(number="2", organization_name="router-test2")
self.ip_address2 = IPAddress.objects.create(address="10.0.0.2/32")
self.tenant = Tenant.objects.create(name="tenant1", slug="tenant1")


class BGPGlobalSerializerCreate(BaseTestCase):
def test_create(self):
data = {
Expand Down
33 changes: 33 additions & 0 deletions netbox_cmdb/netbox_cmdb/tests/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from dcim.models.devices import Device, DeviceRole, DeviceType, Manufacturer
from dcim.models.sites import Site
from django.test import TestCase
from ipam.models.ip import IPAddress
from tenancy.models.tenants import Tenant
from netbox_cmdb.models.bgp import ASN


class BaseTestCase(TestCase):
def setUp(self):
site = Site.objects.create(name="SiteTest", slug="site-test")
manufacturer = Manufacturer.objects.create(name="test", slug="test")
device_type = DeviceType.objects.create(
manufacturer=manufacturer, model="model-test", slug="model-test"
)
device_role = DeviceRole.objects.create(name="role-test", slug="role-test")
self.device1 = Device.objects.create(
name="router-test1",
device_role=device_role,
device_type=device_type,
site=site,
)
self.asn1 = ASN.objects.create(number="1", organization_name="router-test1")
self.ip_address1 = IPAddress.objects.create(address="10.0.0.1/32")
self.device2 = Device.objects.create(
name="router-test2",
device_role=device_role,
device_type=device_type,
site=site,
)
self.asn2 = ASN.objects.create(number="2", organization_name="router-test2")
self.ip_address2 = IPAddress.objects.create(address="10.0.0.2/32")
self.tenant = Tenant.objects.create(name="tenant1", slug="tenant1")
Empty file.
Loading

0 comments on commit 75de0b4

Please sign in to comment.