mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Initial work on NAPALM integration
This commit is contained in:
@ -422,7 +422,7 @@ class PlatformSerializer(ModelValidationMixin, serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Platform
|
model = Platform
|
||||||
fields = ['id', 'name', 'slug', 'rpc_client']
|
fields = ['id', 'name', 'slug', 'napalm_driver', 'rpc_client']
|
||||||
|
|
||||||
|
|
||||||
class NestedPlatformSerializer(serializers.ModelSerializer):
|
class NestedPlatformSerializer(serializers.ModelSerializer):
|
||||||
|
@ -7,6 +7,7 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ViewSet
|
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ViewSet
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.http import Http404, HttpResponseForbidden
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
from dcim.models import (
|
from dcim.models import (
|
||||||
@ -224,6 +225,59 @@ class DeviceViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
|
|||||||
write_serializer_class = serializers.WritableDeviceSerializer
|
write_serializer_class = serializers.WritableDeviceSerializer
|
||||||
filter_class = filters.DeviceFilter
|
filter_class = filters.DeviceFilter
|
||||||
|
|
||||||
|
@detail_route(url_path='napalm/(?P<method>get_[a-z_]+)')
|
||||||
|
def napalm(self, request, pk, method):
|
||||||
|
"""
|
||||||
|
Execute a NAPALM method on a Device
|
||||||
|
"""
|
||||||
|
device = get_object_or_404(Device, pk=pk)
|
||||||
|
if not device.primary_ip:
|
||||||
|
raise ServiceUnavailable("This device does not have a primary IP address configured.")
|
||||||
|
if device.platform is None:
|
||||||
|
raise ServiceUnavailable("No platform is configured for this device.")
|
||||||
|
if not device.platform.napalm_driver:
|
||||||
|
raise ServiceUnavailable("No NAPALM driver is configured for this device's platform ().".format(
|
||||||
|
device.platform
|
||||||
|
))
|
||||||
|
|
||||||
|
# Check that NAPALM is installed and verify the configured driver
|
||||||
|
try:
|
||||||
|
import napalm
|
||||||
|
from napalm_base.exceptions import ConnectAuthError, ModuleImportError
|
||||||
|
except ImportError:
|
||||||
|
raise ServiceUnavailable("NAPALM is not installed. Please see the documentation for instructions.")
|
||||||
|
try:
|
||||||
|
driver = napalm.get_network_driver(device.platform.napalm_driver)
|
||||||
|
except ModuleImportError:
|
||||||
|
raise ServiceUnavailable("NAPALM driver for platform {} not found: {}.".format(
|
||||||
|
device.platform, device.platform.napalm_driver
|
||||||
|
))
|
||||||
|
|
||||||
|
# Raise a 404 for invalid NAPALM methods
|
||||||
|
if not hasattr(driver, method):
|
||||||
|
raise Http404()
|
||||||
|
|
||||||
|
# Verify user permission
|
||||||
|
if not request.user.has_perm('dcim.napalm_read'):
|
||||||
|
return HttpResponseForbidden()
|
||||||
|
|
||||||
|
# Connect to the device and execute the given method
|
||||||
|
# TODO: Improve error handling
|
||||||
|
ip_address = str(device.primary_ip.address.ip)
|
||||||
|
d = driver(
|
||||||
|
hostname=ip_address,
|
||||||
|
username=settings.NETBOX_USERNAME,
|
||||||
|
password=settings.NETBOX_PASSWORD
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
d.open()
|
||||||
|
response = getattr(d, method)()
|
||||||
|
except Exception as e:
|
||||||
|
raise ServiceUnavailable("Error connecting to the device: {}".format(e))
|
||||||
|
|
||||||
|
return Response(response)
|
||||||
|
|
||||||
|
|
||||||
@detail_route(url_path='lldp-neighbors')
|
@detail_route(url_path='lldp-neighbors')
|
||||||
def lldp_neighbors(self, request, pk):
|
def lldp_neighbors(self, request, pk):
|
||||||
"""
|
"""
|
||||||
|
@ -558,7 +558,7 @@ class PlatformForm(BootstrapMixin, forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Platform
|
model = Platform
|
||||||
fields = ['name', 'slug', 'rpc_client']
|
fields = ['name', 'slug', 'napalm_driver', 'rpc_client']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
40
netbox/dcim/migrations/0041_napalm_integration.py
Normal file
40
netbox/dcim/migrations/0041_napalm_integration.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.3 on 2017-07-14 17:26
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def rpc_client_to_napalm_driver(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
Migrate legacy RPC clients to their respective NAPALM drivers
|
||||||
|
"""
|
||||||
|
Platform = apps.get_model('dcim', 'Platform')
|
||||||
|
|
||||||
|
Platform.objects.filter(rpc_client='juniper-junos').update(napalm_driver='junos')
|
||||||
|
Platform.objects.filter(rpc_client='cisco-ios').update(napalm_driver='ios')
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0040_inventoryitem_add_asset_tag_description'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='device',
|
||||||
|
options={'ordering': ['name'], 'permissions': (('napalm_read', 'Read-only access to devices via NAPALM'), ('napalm_write', 'Read/write access to devices via NAPALM'))},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='platform',
|
||||||
|
name='napalm_driver',
|
||||||
|
field=models.CharField(blank=True, help_text='The name of the NAPALM driver to use when interacting with devices.', max_length=50, verbose_name='NAPALM driver'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='platform',
|
||||||
|
name='rpc_client',
|
||||||
|
field=models.CharField(blank=True, choices=[['juniper-junos', 'Juniper Junos (NETCONF)'], ['cisco-ios', 'Cisco IOS (SSH)'], ['opengear', 'Opengear (SSH)']], max_length=30, verbose_name='Legacy RPC client'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(rpc_client_to_napalm_driver),
|
||||||
|
]
|
@ -738,7 +738,10 @@ class Platform(models.Model):
|
|||||||
"""
|
"""
|
||||||
name = models.CharField(max_length=50, unique=True)
|
name = models.CharField(max_length=50, unique=True)
|
||||||
slug = models.SlugField(unique=True)
|
slug = models.SlugField(unique=True)
|
||||||
rpc_client = models.CharField(max_length=30, choices=RPC_CLIENT_CHOICES, blank=True, verbose_name='RPC client')
|
napalm_driver = models.CharField(max_length=50, blank=True, verbose_name='NAPALM driver',
|
||||||
|
help_text="The name of the NAPALM driver to use when interacting with devices.")
|
||||||
|
rpc_client = models.CharField(max_length=30, choices=RPC_CLIENT_CHOICES, blank=True,
|
||||||
|
verbose_name='Legacy RPC client')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
@ -809,6 +812,10 @@ class Device(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name']
|
ordering = ['name']
|
||||||
unique_together = ['rack', 'position', 'face']
|
unique_together = ['rack', 'position', 'face']
|
||||||
|
permissions = (
|
||||||
|
('napalm_read', 'Read-only access to devices via NAPALM'),
|
||||||
|
('napalm_write', 'Read/write access to devices via NAPALM'),
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.display_name or super(Device, self).__str__()
|
return self.display_name or super(Device, self).__str__()
|
||||||
|
Reference in New Issue
Block a user