Просмотр исходного кода

Closes #2367: Remove deprecated RPCClient functionality

Jeremy Stretch 7 лет назад
Родитель
Сommit
c4be440cd1

+ 1 - 1
netbox/dcim/api/serializers.py

@@ -344,7 +344,7 @@ class PlatformSerializer(ValidatedModelSerializer):
 
     class Meta:
         model = Platform
-        fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'rpc_client']
+        fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args']
 
 
 class NestedPlatformSerializer(WritableNestedSerializer):

+ 0 - 10
netbox/dcim/constants.py

@@ -243,13 +243,3 @@ CONNECTION_STATUS_CHOICES = [
     [CONNECTION_STATUS_PLANNED, 'Planned'],
     [CONNECTION_STATUS_CONNECTED, 'Connected'],
 ]
-
-# Platform -> RPC client mappings
-RPC_CLIENT_JUNIPER_JUNOS = 'juniper-junos'
-RPC_CLIENT_CISCO_IOS = 'cisco-ios'
-RPC_CLIENT_OPENGEAR = 'opengear'
-RPC_CLIENT_CHOICES = [
-    [RPC_CLIENT_JUNIPER_JUNOS, 'Juniper Junos (NETCONF)'],
-    [RPC_CLIENT_CISCO_IOS, 'Cisco IOS (SSH)'],
-    [RPC_CLIENT_OPENGEAR, 'Opengear (SSH)'],
-]

+ 2 - 4
netbox/dcim/fixtures/dcim.json

@@ -1903,8 +1903,7 @@
     "pk": 1,
     "fields": {
         "name": "Juniper Junos",
-        "slug": "juniper-junos",
-        "rpc_client": "juniper-junos"
+        "slug": "juniper-junos"
     }
 },
 {
@@ -1912,8 +1911,7 @@
     "pk": 2,
     "fields": {
         "name": "Opengear",
-        "slug": "opengear",
-        "rpc_client": "opengear"
+        "slug": "opengear"
     }
 },
 {

+ 6 - 12
netbox/dcim/fixtures/initial_data.json

@@ -149,8 +149,7 @@
     "pk": 1,
     "fields": {
         "name": "Cisco IOS",
-        "slug": "cisco-ios",
-        "rpc_client": "cisco-ios"
+        "slug": "cisco-ios"
     }
 },
 {
@@ -158,8 +157,7 @@
     "pk": 2,
     "fields": {
         "name": "Cisco NX-OS",
-        "slug": "cisco-nx-os",
-        "rpc_client": ""
+        "slug": "cisco-nx-os"
     }
 },
 {
@@ -167,8 +165,7 @@
     "pk": 3,
     "fields": {
         "name": "Juniper Junos",
-        "slug": "juniper-junos",
-        "rpc_client": "juniper-junos"
+        "slug": "juniper-junos"
     }
 },
 {
@@ -176,8 +173,7 @@
     "pk": 4,
     "fields": {
         "name": "Arista EOS",
-        "slug": "arista-eos",
-        "rpc_client": ""
+        "slug": "arista-eos"
     }
 },
 {
@@ -185,8 +181,7 @@
     "pk": 5,
     "fields": {
         "name": "Linux",
-        "slug": "linux",
-        "rpc_client": ""
+        "slug": "linux"
     }
 },
 {
@@ -194,8 +189,7 @@
     "pk": 6,
     "fields": {
         "name": "Opengear",
-        "slug": "opengear",
-        "rpc_client": "opengear"
+        "slug": "opengear"
     }
 }
 ]

+ 1 - 1
netbox/dcim/forms.py

@@ -744,7 +744,7 @@ class PlatformForm(BootstrapMixin, forms.ModelForm):
 
     class Meta:
         model = Platform
-        fields = ['name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'rpc_client']
+        fields = ['name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args']
         widgets = {
             'napalm_args': SmallTextarea(),
         }

+ 17 - 0
netbox/dcim/migrations/0062_remove_platform_rpc_client.py

@@ -0,0 +1,17 @@
+# Generated by Django 2.0.8 on 2018-08-16 16:17
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dcim', '0061_platform_napalm_args'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='platform',
+            name='rpc_client',
+        ),
+    ]

+ 0 - 15
netbox/dcim/models.py

@@ -17,7 +17,6 @@ from timezone_field import TimeZoneField
 from circuits.models import Circuit
 from extras.constants import OBJECTCHANGE_ACTION_DELETE, OBJECTCHANGE_ACTION_UPDATE
 from extras.models import ConfigContextModel, CustomFieldModel, ObjectChange
-from extras.rpc import RPC_CLIENTS
 from utilities.fields import ColorField, NullableCharField
 from utilities.managers import NaturalOrderByManager
 from utilities.models import ChangeLoggedModel
@@ -1096,12 +1095,6 @@ class Platform(ChangeLoggedModel):
         verbose_name='NAPALM arguments',
         help_text='Additional arguments to pass when initiating the NAPALM driver (JSON format)'
     )
-    rpc_client = models.CharField(
-        max_length=30,
-        choices=RPC_CLIENT_CHOICES,
-        blank=True,
-        verbose_name='Legacy RPC client'
-    )
 
     csv_headers = ['name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args']
 
@@ -1507,14 +1500,6 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
     def get_status_class(self):
         return STATUS_CLASSES[self.status]
 
-    def get_rpc_client(self):
-        """
-        Return the appropriate RPC (e.g. NETCONF, ssh, etc.) client for this device's platform, if one is defined.
-        """
-        if not self.platform:
-            return None
-        return RPC_CLIENTS.get(self.platform.rpc_client)
-
 
 #
 # Console ports

+ 0 - 125
netbox/extras/management/commands/run_inventory.py

@@ -1,125 +0,0 @@
-from getpass import getpass
-
-from django.conf import settings
-from django.core.management.base import BaseCommand, CommandError
-from django.db import transaction
-from ncclient.transport.errors import AuthenticationError
-from paramiko import AuthenticationException
-
-from dcim.models import DEVICE_STATUS_ACTIVE, Device, InventoryItem, Site
-
-
-class Command(BaseCommand):
-    help = "Update inventory information for specified devices"
-    username = settings.NAPALM_USERNAME
-    password = settings.NAPALM_PASSWORD
-
-    def add_arguments(self, parser):
-        parser.add_argument('-u', '--username', dest='username', help="Specify the username to use")
-        parser.add_argument('-p', '--password', action='store_true', default=False, help="Prompt for password to use")
-        parser.add_argument('-s', '--site', dest='site', action='append',
-                            help="Filter devices by site (include argument once per site)")
-        parser.add_argument('-n', '--name', dest='name', help="Filter devices by name (regular expression)")
-        parser.add_argument('--full', action='store_true', default=False, help="For inventory update for all devices")
-        parser.add_argument('--fake', action='store_true', default=False, help="Do not actually update database")
-
-    def handle(self, *args, **options):
-
-        def create_inventory_items(inventory_items, parent=None):
-            for item in inventory_items:
-                i = InventoryItem(device=device, parent=parent, name=item['name'], part_id=item['part_id'],
-                                  serial=item['serial'], discovered=True)
-                i.save()
-                create_inventory_items(item.get('items', []), parent=i)
-
-        # Credentials
-        if options['username']:
-            self.username = options['username']
-        if options['password']:
-            self.password = getpass("Password: ")
-
-        # Attempt to inventory only active devices
-        device_list = Device.objects.filter(status=DEVICE_STATUS_ACTIVE)
-
-        # --site: Include only devices belonging to specified site(s)
-        if options['site']:
-            sites = Site.objects.filter(slug__in=options['site'])
-            if sites:
-                site_names = [s.name for s in sites]
-                self.stdout.write("Running inventory for these sites: {}".format(', '.join(site_names)))
-            else:
-                raise CommandError("One or more sites specified but none found.")
-            device_list = device_list.filter(site__in=sites)
-
-        # --name: Filter devices by name matching a regex
-        if options['name']:
-            device_list = device_list.filter(name__iregex=options['name'])
-
-        # --full: Gather inventory data for *all* devices
-        if options['full']:
-            self.stdout.write("WARNING: Running inventory for all devices! Prior data will be overwritten. (--full)")
-
-        # --fake: Gathering data but not updating the database
-        if options['fake']:
-            self.stdout.write("WARNING: Inventory data will not be saved! (--fake)")
-
-        device_count = device_list.count()
-        self.stdout.write("** Found {} devices...".format(device_count))
-
-        for i, device in enumerate(device_list, start=1):
-
-            self.stdout.write("[{}/{}] {}: ".format(i, device_count, device.name), ending='')
-
-            # Skip inactive devices
-            if not device.status:
-                self.stdout.write("Skipped (not active)")
-                continue
-
-            # Skip devices without primary_ip set
-            if not device.primary_ip:
-                self.stdout.write("Skipped (no primary IP set)")
-                continue
-
-            # Skip devices which have already been inventoried if not doing a full update
-            if device.serial and not options['full']:
-                self.stdout.write("Skipped (Serial: {})".format(device.serial))
-                continue
-
-            RPC = device.get_rpc_client()
-            if not RPC:
-                self.stdout.write("Skipped (no RPC client available for platform {})".format(device.platform))
-                continue
-
-            # Connect to device and retrieve inventory info
-            try:
-                with RPC(device, self.username, self.password) as rpc_client:
-                    inventory = rpc_client.get_inventory()
-            except KeyboardInterrupt:
-                raise
-            except (AuthenticationError, AuthenticationException):
-                self.stdout.write("Authentication error!")
-                continue
-            except Exception as e:
-                self.stdout.write("Error: {}".format(e))
-                continue
-
-            if options['verbosity'] > 1:
-                self.stdout.write("")
-                self.stdout.write("\tSerial: {}".format(inventory['chassis']['serial']))
-                self.stdout.write("\tDescription: {}".format(inventory['chassis']['description']))
-                for item in inventory['items']:
-                    self.stdout.write("\tItem: {} / {} ({})".format(item['name'], item['part_id'],
-                                                                    item['serial']))
-            else:
-                self.stdout.write("{} ({})".format(inventory['chassis']['description'], inventory['chassis']['serial']))
-
-            if not options['fake']:
-                with transaction.atomic():
-                    # Update device serial
-                    if device.serial != inventory['chassis']['serial']:
-                        device.serial = inventory['chassis']['serial']
-                        device.save()
-                    InventoryItem.objects.filter(device=device, discovered=True).delete()
-                    create_inventory_items(inventory.get('items', []))
-
-        self.stdout.write("Finished!")

+ 0 - 235
netbox/extras/rpc.py

@@ -1,235 +0,0 @@
-import re
-import time
-
-import paramiko
-import xmltodict
-from ncclient import manager
-
-CONNECT_TIMEOUT = 5  # seconds
-
-
-class RPCClient(object):
-
-    def __init__(self, device, username='', password=''):
-        self.username = username
-        self.password = password
-        try:
-            self.host = str(device.primary_ip.address.ip)
-        except AttributeError:
-            raise Exception("Specified device ({}) does not have a primary IP defined.".format(device))
-
-    def get_inventory(self):
-        """
-        Returns a dictionary representing the device chassis and installed inventory items.
-
-        {
-            'chassis': {
-                'serial': <str>,
-                'description': <str>,
-            }
-            'items': [
-                {
-                    'name': <str>,
-                    'part_id': <str>,
-                    'serial': <str>,
-                },
-                ...
-            ]
-        }
-        """
-        raise NotImplementedError("Feature not implemented for this platform.")
-
-
-class SSHClient(RPCClient):
-    def __enter__(self):
-
-        self.ssh = paramiko.SSHClient()
-        self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
-        try:
-            self.ssh.connect(
-                self.host,
-                username=self.username,
-                password=self.password,
-                timeout=CONNECT_TIMEOUT,
-                allow_agent=False,
-                look_for_keys=False,
-            )
-        except paramiko.AuthenticationException:
-            # Try default credentials if the configured creds don't work
-            try:
-                default_creds = self.default_credentials
-                if default_creds.get('username') and default_creds.get('password'):
-                    self.ssh.connect(
-                        self.host,
-                        username=default_creds['username'],
-                        password=default_creds['password'],
-                        timeout=CONNECT_TIMEOUT,
-                        allow_agent=False,
-                        look_for_keys=False,
-                    )
-                else:
-                    raise ValueError('default_credentials are incomplete.')
-            except AttributeError:
-                raise paramiko.AuthenticationException
-
-        self.session = self.ssh.invoke_shell()
-        self.session.recv(1000)
-        return self
-
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        self.ssh.close()
-
-    def _send(self, cmd, pause=1):
-        self.session.send('{}\n'.format(cmd))
-        data = ''
-        time.sleep(pause)
-        while self.session.recv_ready():
-            data += self.session.recv(4096).decode()
-            if not data:
-                break
-        return data
-
-
-class JunosNC(RPCClient):
-    """
-    NETCONF client for Juniper Junos devices
-    """
-
-    def __enter__(self):
-
-        # Initiate a connection to the device
-        self.manager = manager.connect(host=self.host, username=self.username, password=self.password,
-                                       hostkey_verify=False, timeout=CONNECT_TIMEOUT)
-
-        return self
-
-    def __exit__(self, exc_type, exc_val, exc_tb):
-
-        # Close the connection to the device
-        self.manager.close_session()
-
-    def get_inventory(self):
-
-        def glean_items(node, depth=0):
-            items = []
-            items_list = node.get('chassis{}-module'.format('-sub' * depth), [])
-            # Junos like to return single children directly instead of as a single-item list
-            if hasattr(items_list, 'items'):
-                items_list = [items_list]
-            for item in items_list:
-                m = {
-                    'name': item['name'],
-                    'part_id': item.get('model-number') or item.get('part-number', ''),
-                    'serial': item.get('serial-number', ''),
-                }
-                child_items = glean_items(item, depth + 1)
-                if child_items:
-                    m['items'] = child_items
-                items.append(m)
-            return items
-
-        rpc_reply = self.manager.dispatch('get-chassis-inventory')
-        inventory_raw = xmltodict.parse(rpc_reply.xml)['rpc-reply']['chassis-inventory']['chassis']
-
-        result = dict()
-
-        # Gather chassis data
-        result['chassis'] = {
-            'serial': inventory_raw['serial-number'],
-            'description': inventory_raw['description'],
-        }
-
-        # Gather inventory items
-        result['items'] = glean_items(inventory_raw)
-
-        return result
-
-
-class IOSSSH(SSHClient):
-    """
-    SSH client for Cisco IOS devices
-    """
-
-    def get_inventory(self):
-        def version():
-
-            def parse(cmd_out, rex):
-                for i in cmd_out:
-                    match = re.search(rex, i)
-                    if match:
-                        return match.groups()[0]
-
-            sh_ver = self._send('show version').split('\r\n')
-            return {
-                'serial': parse(sh_ver, r'Processor board ID ([^\s]+)'),
-                'description': parse(sh_ver, r'cisco ([^\s]+)')
-            }
-
-        def items(chassis_serial=None):
-            cmd = self._send('show inventory').split('\r\n\r\n')
-            for i in cmd:
-                i_fmt = i.replace('\r\n', ' ')
-                try:
-                    m_name = re.search(r'NAME: "([^"]+)"', i_fmt).group(1)
-                    m_pid = re.search(r'PID: ([^\s]+)', i_fmt).group(1)
-                    m_serial = re.search(r'SN: ([^\s]+)', i_fmt).group(1)
-                    # Omit built-in items and those with no PID
-                    if m_serial != chassis_serial and m_pid.lower() != 'unspecified':
-                        yield {
-                            'name': m_name,
-                            'part_id': m_pid,
-                            'serial': m_serial,
-                        }
-                except AttributeError:
-                    continue
-
-        self._send('term length 0')
-        sh_version = version()
-
-        return {
-            'chassis': sh_version,
-            'items': list(items(chassis_serial=sh_version.get('serial')))
-        }
-
-
-class OpengearSSH(SSHClient):
-    """
-    SSH client for Opengear devices
-    """
-    default_credentials = {
-        'username': 'root',
-        'password': 'default',
-    }
-
-    def get_inventory(self):
-
-        try:
-            stdin, stdout, stderr = self.ssh.exec_command("showserial")
-            serial = stdout.readlines()[0].strip()
-        except Exception:
-            raise RuntimeError("Failed to glean chassis serial from device.")
-        # Older models don't provide serial info
-        if serial == "No serial number information available":
-            serial = ''
-
-        try:
-            stdin, stdout, stderr = self.ssh.exec_command("config -g config.system.model")
-            description = stdout.readlines()[0].split(' ', 1)[1].strip()
-        except Exception:
-            raise RuntimeError("Failed to glean chassis description from device.")
-
-        return {
-            'chassis': {
-                'serial': serial,
-                'description': description,
-            },
-            'items': [],
-        }
-
-
-# For mapping platform -> NC client
-RPC_CLIENTS = {
-    'juniper-junos': JunosNC,
-    'cisco-ios': IOSSSH,
-    'opengear': OpengearSSH,
-}