From 79c4de29b591b1b0c234d22bbda15c22c527520f Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Tue, 18 Jul 2023 11:25:09 -0500 Subject: [PATCH 1/7] PRVB --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 33e609f..1f254fb 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='netbox_config_backup', - version='1.4.8', + version='1.4.9', description='NetBox Configuration Backup', long_description='Plugin to backup device configuration', url='https://github.com/dansheps/netbox-config-backup/', From 73d2abe6a020f8c9f8360191adfd9d4cfa716d0b Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Wed, 9 Aug 2023 08:25:14 -0500 Subject: [PATCH 2/7] Add logging and move Queue job to start of get_extra_context --- netbox_config_backup/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/netbox_config_backup/views.py b/netbox_config_backup/views.py index f875a37..805ecdf 100644 --- a/netbox_config_backup/views.py +++ b/netbox_config_backup/views.py @@ -42,6 +42,10 @@ class BackupView(ObjectView): def get_extra_context(self, request, instance): + if BackupJob.is_queued(instance) is False: + logger.debug(f'{instance}: Queuing Job') + BackupJob.enqueue_if_needed(instance) + jobs = BackupJob.objects.filter(backup=instance).order_by() is_running = True if jobs.filter(status=JobResultStatusChoices.STATUS_RUNNING).count() > 0 else False is_pending = True if jobs.filter(status=JobResultStatusChoices.STATUS_PENDING).count() > 0 else False @@ -52,10 +56,6 @@ def get_extra_context(self, request, instance): if is_running: job_status = 'Running' - if BackupJob.is_queued(instance) is False: - logger.debug(f'{instance}: Queuing Job') - BackupJob.enqueue_if_needed(instance) - status = { 'status': job_status, 'scheduled': BackupJob.is_queued(instance), From 47a83565bc244bbb59365756030e1c8a27852b9f Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Wed, 9 Aug 2023 10:47:28 -0500 Subject: [PATCH 3/7] Update in prep for 3.6 --- netbox_config_backup/__init__.py | 2 +- .../management/commands/runbackup.py | 22 +++++++--- .../netbox_config_backup/backups.html | 18 +------- netbox_config_backup/utils/rq.py | 43 ++++++++++++------- 4 files changed, 46 insertions(+), 39 deletions(-) diff --git a/netbox_config_backup/__init__.py b/netbox_config_backup/__init__.py index ceb6231..0ec2cff 100644 --- a/netbox_config_backup/__init__.py +++ b/netbox_config_backup/__init__.py @@ -13,7 +13,7 @@ class NetboxConfigBackup(PluginConfig): author_email = metadata.get('Author-email') base_url = 'configbackup' min_version = '3.5.0' - max_version = '3.5.99' + max_version = '3.6.99' required_settings = [ 'repository', 'committer', diff --git a/netbox_config_backup/management/commands/runbackup.py b/netbox_config_backup/management/commands/runbackup.py index 8eb5a55..224506b 100644 --- a/netbox_config_backup/management/commands/runbackup.py +++ b/netbox_config_backup/management/commands/runbackup.py @@ -3,15 +3,27 @@ from django.db import transaction from netbox_config_backup.tasks import backup_job +from netbox_config_backup.utils import remove_queued +from netbox_config_backup.utils.rq import can_backup class Command(BaseCommand): def add_arguments(self, parser): - parser.add_argument('--time', help="time") - parser.add_argument('device', help="Device Name") + parser.add_argument('--time', dest='time', help="time") + parser.add_argument('--device', dest='device', help="Device Name") + + def run_backup(self, backup): + if can_backup(backup): + backupjob = backup.jobs.filter(backup__device__name=backup.device).last() + backup_job(backupjob.pk) + remove_queued(backup) def handle(self, *args, **options): - from netbox_config_backup.models import Backup, BackupJob + from netbox_config_backup.models import Backup + if options['device']: + backup = Backup.objects.filter(device=options['device']) + self.run_backup(backup) + else: + for backup in Backup.objects.all(): + self.run_backup(backup) - backupjob = BackupJob.objects.filter(backup__device__name=options['device']).last() - backup_job(backupjob.pk) diff --git a/netbox_config_backup/templates/netbox_config_backup/backups.html b/netbox_config_backup/templates/netbox_config_backup/backups.html index 1bea5e9..0536030 100644 --- a/netbox_config_backup/templates/netbox_config_backup/backups.html +++ b/netbox_config_backup/templates/netbox_config_backup/backups.html @@ -1,4 +1,4 @@ -{% extends 'generic/object.html' %} +{% extends 'generic/object_children.html' %} {% load helpers %} {% block subtitle %} @@ -18,19 +18,3 @@ {% endblock %} - -{% block content %} - {% include 'inc/table_controls_htmx.html' with table_modal="BackupsTable_config" %} - -
-
- {% include 'htmx/table.html' %} -
-
- -{% endblock %} - -{% block modals %} - {{ block.super }} - {% table_config_form table %} -{% endblock modals %} diff --git a/netbox_config_backup/utils/rq.py b/netbox_config_backup/utils/rq.py index 44aabcb..348c487 100644 --- a/netbox_config_backup/utils/rq.py +++ b/netbox_config_backup/utils/rq.py @@ -13,6 +13,31 @@ logger = logging.getLogger(f"netbox_config_backup") +def can_backup(backup): + if backup.device is None: + logger.info(f'No device for {backup}') + return False + elif backup.status == StatusChoices.STATUS_DISABLED: + logger.info(f'Backup disabled for {backup}') + return False + elif backup.device.status in [DeviceStatusChoices.STATUS_OFFLINE, + DeviceStatusChoices.STATUS_FAILED, + DeviceStatusChoices.STATUS_INVENTORY, + DeviceStatusChoices.STATUS_PLANNED]: + logger.info(f'Backup disabled for {backup} due to device status ({backup.device.status})') + return False + elif (backup.ip is None and backup.device.primary_ip is None) or backup.device.platform is None or \ + backup.device.platform.napalm_driver == '' or backup.device.platform.napalm_driver is None: + if backup.ip is None and backup.device.primary_ip is None: + logger.error(f'Backup disabled for {backup} due to no primary IP ({backup.device.status})') + elif backup.device.platform is None: + logger.error(f'Backup disabled for {backup} due to platform not set ({backup.device.status})') + elif backup.device.platform.napalm_driver == '' or backup.device.platform.napalm_driver is None: + logger.error(f'Backup disabled for {backup} due to napalm driver not set ({backup.device.status})') + return False + + return True + def enqueue(backup, delay=None): from netbox_config_backup.models import BackupJob @@ -64,24 +89,10 @@ def needs_enqueue(backup, job_id=None): scheduled_jobs = scheduled.get_job_ids() started_jobs = started.get_job_ids() - if backup.device is None: - print(f'No device for {backup}') - return False - elif backup.status == StatusChoices.STATUS_DISABLED: - print(f'Backup disabled for {backup}') - return False - elif backup.device.status in [DeviceStatusChoices.STATUS_OFFLINE, - DeviceStatusChoices.STATUS_FAILED, - DeviceStatusChoices.STATUS_INVENTORY, - DeviceStatusChoices.STATUS_PLANNED]: - print(f'Backup disabled for {backup} due to device status ({backup.device.status})') - return False - elif (backup.ip is None and backup.device.primary_ip is None) or backup.device.platform is None or \ - backup.device.platform.napalm_driver == '' or backup.device.platform.napalm_driver is None: - print(f'Backup disabled for {backup} due to napalm drive or no primary IP ({backup.device.status})') + if not can_backup(backup): return False elif is_queued(backup, job_id): - print(f'Backup already queued for {backup}') + logger.info(f'Backup already queued for {backup}') return False return True From baacd792eba806ef4049ed59397f68fca0afbfac Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Thu, 24 Aug 2023 15:13:00 -0500 Subject: [PATCH 4/7] Improve error handling --- netbox_config_backup/__init__.py | 2 +- netbox_config_backup/git.py | 1 + .../management/commands/runbackup.py | 20 ++++++-- netbox_config_backup/models/backups.py | 5 +- netbox_config_backup/tables.py | 16 ++++--- netbox_config_backup/tasks.py | 4 +- .../netbox_config_backup/backups.html | 12 +++++ netbox_config_backup/urls.py | 10 ++-- netbox_config_backup/views.py | 46 ++++++++++++++----- 9 files changed, 85 insertions(+), 31 deletions(-) diff --git a/netbox_config_backup/__init__.py b/netbox_config_backup/__init__.py index 0ec2cff..6d3d6f6 100644 --- a/netbox_config_backup/__init__.py +++ b/netbox_config_backup/__init__.py @@ -12,7 +12,7 @@ class NetboxConfigBackup(PluginConfig): author = metadata.get('Author') author_email = metadata.get('Author-email') base_url = 'configbackup' - min_version = '3.5.0' + min_version = '3.5.8' max_version = '3.6.99' required_settings = [ 'repository', diff --git a/netbox_config_backup/git.py b/netbox_config_backup/git.py index 1687709..e0f29aa 100644 --- a/netbox_config_backup/git.py +++ b/netbox_config_backup/git.py @@ -3,6 +3,7 @@ from datetime import datetime from time import sleep +from deepdiff import DeepDiff from dulwich import repo, porcelain, object_store from pydriller import Git diff --git a/netbox_config_backup/management/commands/runbackup.py b/netbox_config_backup/management/commands/runbackup.py index 224506b..7b170f9 100644 --- a/netbox_config_backup/management/commands/runbackup.py +++ b/netbox_config_backup/management/commands/runbackup.py @@ -1,7 +1,10 @@ -import datetime +import uuid + from django.core.management.base import BaseCommand from django.db import transaction +from django.utils import timezone +from netbox_config_backup.models import BackupJob from netbox_config_backup.tasks import backup_job from netbox_config_backup.utils import remove_queued from netbox_config_backup.utils.rq import can_backup @@ -14,15 +17,24 @@ def add_arguments(self, parser): def run_backup(self, backup): if can_backup(backup): - backupjob = backup.jobs.filter(backup__device__name=backup.device).last() + backupjob = backup.jobs.filter(backup__device=backup.device).last() + if backupjob is None: + backupjob = BackupJob.objects.create( + backup=backup, + scheduled=timezone.now(), + uuid=uuid.uuid4() + ) backup_job(backupjob.pk) remove_queued(backup) def handle(self, *args, **options): from netbox_config_backup.models import Backup if options['device']: - backup = Backup.objects.filter(device=options['device']) - self.run_backup(backup) + backup = Backup.objects.filter(device__name=options['device']).first() + if backup: + self.run_backup(backup) + else: + raise Exception('Device not found') else: for backup in Backup.objects.all(): self.run_backup(backup) diff --git a/netbox_config_backup/models/backups.py b/netbox_config_backup/models/backups.py index 224471a..b6ebdad 100644 --- a/netbox_config_backup/models/backups.py +++ b/netbox_config_backup/models/backups.py @@ -226,13 +226,16 @@ class BackupCommitTreeChange(BigIDModel): type = models.CharField(max_length=10) old = models.ForeignKey(to=BackupObject, on_delete=models.PROTECT, related_name='previous', null=True) new = models.ForeignKey(to=BackupObject, on_delete=models.PROTECT, related_name='changes', null=True) - + def __str__(self): return f'{self.commit.sha}-{self.type}' def filename(self): return f'{self.backup.uuid}.{self.type}' + def get_absolute_url(self): + return reverse('plugins:netbox_config_backup:backup_config', kwargs={'backup': self.backup.pk, 'current': self.pk}) + @property def previous(self): return self.backup.changes.filter(file__type=self.file.type, commit__time__lt=self.commit.time).last() \ No newline at end of file diff --git a/netbox_config_backup/tables.py b/netbox_config_backup/tables.py index cf8aa4e..bbc511d 100644 --- a/netbox_config_backup/tables.py +++ b/netbox_config_backup/tables.py @@ -2,17 +2,17 @@ from django_tables2.utils import Accessor from netbox_config_backup.models import Backup, BackupCommitTreeChange -from netbox.tables import columns, BaseTable +from netbox.tables import columns, BaseTable, NetBoxTable class ActionButtonsColumn(tables.TemplateColumn): attrs = {'td': {'class': 'text-end text-nowrap noprint min-width'}} template_code = """ - + {% if record.previous %} - + {% else %} @@ -28,7 +28,9 @@ def header(self): class BackupTable(BaseTable): - pk = columns.ToggleColumn() + pk = columns.ToggleColumn( + + ) name = tables.Column( linkify=True, verbose_name='Backup Name' @@ -56,7 +58,7 @@ class Meta(BaseTable.Meta): ) -class BackupsTable(BaseTable): +class BackupsTable(NetBoxTable): date = tables.Column( accessor='commit__time' ) @@ -68,10 +70,10 @@ class BackupsTable(BaseTable): class Meta: model = BackupCommitTreeChange fields = ( - 'date', 'type', 'backup', 'commit', 'file', 'actions' + 'pk', 'id', 'date', 'type', 'backup', 'commit', 'file', 'actions' ) default_columns = ( - 'date', 'type', 'actions' + 'pk', 'id', 'date', 'type', 'actions' ) attrs = { 'class': 'table table-hover object-list', diff --git a/netbox_config_backup/tasks.py b/netbox_config_backup/tasks.py index a85bfcf..fbb97f0 100644 --- a/netbox_config_backup/tasks.py +++ b/netbox_config_backup/tasks.py @@ -142,13 +142,13 @@ def backup_job(pk): logger.error(f'\tException: {e}') except netmiko.exceptions.ReadTimeout as e: BackupJob.enqueue_if_needed(backup, delay=delay, job_id=job_result.job_id) - logger.error(f'Netmiko read timeout on job: {backup}') + logger.warning(f'Netmiko read timeout on job: {backup}') except ServiceUnavailable as e: logger.info(f'Napalm service read failure on job: {backup}') BackupJob.enqueue_if_needed(backup, delay=delay, job_id=job_result.job_id) except Exception as e: logger.error(f'Exception at line 148 on job: {backup}') - logger.error(e.with_traceback()) + logger.error(e) job_result.set_status(JobResultStatusChoices.STATUS_FAILED) BackupJob.enqueue_if_needed(backup, delay=delay, job_id=job_result.job_id) diff --git a/netbox_config_backup/templates/netbox_config_backup/backups.html b/netbox_config_backup/templates/netbox_config_backup/backups.html index 0536030..43439db 100644 --- a/netbox_config_backup/templates/netbox_config_backup/backups.html +++ b/netbox_config_backup/templates/netbox_config_backup/backups.html @@ -18,3 +18,15 @@ {% endblock %} + +{% block bulk_edit_controls %} + {{ block.super }} + {% with diff_view=object|viewname:"diff" %} + + + {% endwith %} +{% endblock bulk_edit_controls %} diff --git a/netbox_config_backup/urls.py b/netbox_config_backup/urls.py index fbf4f5c..b09180e 100644 --- a/netbox_config_backup/urls.py +++ b/netbox_config_backup/urls.py @@ -9,9 +9,9 @@ path('devices//edit/', views.BackupEditView.as_view(), name='backup_edit'), path('devices//delete/', views.BackupDeleteView.as_view(), name='backup_delete'), path('devices//backups/', views.BackupBackupsView.as_view(), name='backup_backups'), - path('devices//config/', views.ConfigView.as_view(), name='backup_config'), - path('devices//config//', views.ConfigView.as_view(), name='backup_config'), - path('devices//diff/', views.DiffView.as_view(), name='backup_diff'), - path('devices//diff//', views.DiffView.as_view(), name='backup_diff'), - path('devices//diff///', views.DiffView.as_view(), name='backup_diff'), + path('devices//config/', views.ConfigView.as_view(), name='backup_config'), + path('devices//config//', views.ConfigView.as_view(), name='backup_config'), + path('devices//diff/', views.DiffView.as_view(), name='backup_diff'), + path('devices//diff//', views.DiffView.as_view(), name='backup_diff'), + path('devices//diff///', views.DiffView.as_view(), name='backup_diff'), ] diff --git a/netbox_config_backup/views.py b/netbox_config_backup/views.py index 805ecdf..cb5dc24 100644 --- a/netbox_config_backup/views.py +++ b/netbox_config_backup/views.py @@ -77,10 +77,11 @@ class BackupBackupsView(ObjectChildrenView): child_model = BackupCommitTreeChange table = BackupsTable filterset = BackupsFilterSet - actions = ['config', 'diff'] + actions = ['config', 'diff', 'bulk_diff'] action_perms = { 'config': {'view'}, 'diff': {'view'}, + 'bulk_diff': {'view'}, } tab = ViewTab( label='View Backups', @@ -145,8 +146,8 @@ class ConfigView(ObjectView): label='Configuration', ) - def get(self, request, pk, current=None): - backup = get_object_or_404(Backup.objects.all(), pk=pk) + def get(self, request, backup, current=None): + backup = get_object_or_404(Backup.objects.all(), pk=backup) if current: current = get_object_or_404(BackupCommitTreeChange.objects.all(), pk=current) else: @@ -187,8 +188,31 @@ class DiffView(ObjectView): label='Diff', ) - def get(self, request, pk, current=None, previous=None): - backup = get_object_or_404(Backup.objects.all(), pk=pk) + def post(self, request, backup, *args, **kwargs): + if request.POST.get('_all') and self.filterset is not None: + queryset = self.filterset(request.GET, self.parent_model.objects.only('pk'), request=request).qs + pk_list = [obj.pk for obj in queryset] + else: + pk_list = [int(pk) for pk in request.POST.getlist('pk')] + + backups = pk_list[:2] + print(pk_list) + print(backups) + + if len(backups) == 2: + current = int(backups[0]) + previous = int(backups[1]) + elif len(backups) == 1: + current = int(backups[0]) + previous = None + else: + current = None + previous = None + + return self.get(request=request, backup=backup, current=current, previous=previous) + + def get(self, request, backup, current=None, previous=None): + backup = get_object_or_404(Backup.objects.all(), pk=backup) if current: current = get_object_or_404(BackupCommitTreeChange.objects.all(), pk=current) else: @@ -210,21 +234,21 @@ def get(self, request, pk, current=None, previous=None): "No Previous Commit" ) - path = f'{current.file.path}' - repo = GitBackup() previous_sha = previous.commit.sha if previous.commit is not None else 'HEAD' current_sha = current.commit.sha if current.commit is not None else None + print(f'Diffs: {current_sha}:{previous_sha}') + if backup.device and backup.device.platform.napalm_driver in ['ios', 'nxos']: - new = repo.read(path, current_sha) - old = repo.read(path, previous_sha) + new = repo.read(current.file.path, current_sha) + old = repo.read(previous.file.path, previous_sha) differ = Differ(old, new) diff = differ.cisco_compare() else: - new = repo.read(path, current_sha) - old = repo.read(path, previous_sha) + new = repo.read(current.file.path, current_sha) + old = repo.read(previous.file.path, previous_sha) differ = Differ(old, new) diff = differ.compare() From df15e62444882a68c971332d678969ce49103ac7 Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Wed, 30 Aug 2023 22:26:13 -0500 Subject: [PATCH 5/7] Updated for NetBox 3.6 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1f254fb..30261df 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='netbox_config_backup', - version='1.4.9', + version='1.5.0', description='NetBox Configuration Backup', long_description='Plugin to backup device configuration', url='https://github.com/dansheps/netbox-config-backup/', From 38e3d5b59bea1898ac1a895311ab6b7796bf6c58 Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Thu, 31 Aug 2023 23:29:44 -0500 Subject: [PATCH 6/7] Update napalm for 3.6 --- netbox_config_backup/tasks.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/netbox_config_backup/tasks.py b/netbox_config_backup/tasks.py index fbb97f0..cf9f85d 100644 --- a/netbox_config_backup/tasks.py +++ b/netbox_config_backup/tasks.py @@ -26,11 +26,12 @@ def get_logger(): def napalm_init(device, ip=None, extra_args={}): - config = get_config() - username = config.NAPALM_USERNAME - password = config.NAPALM_PASSWORD - timeout = config.NAPALM_TIMEOUT - optional_args = config.NAPALM_ARGS.copy() + from netbox import settings + username = settings.PLUGINS_CONFIG.get('netbox_napalm_plugin', {}).get('NAPALM_USERNAME', None) + password = settings.PLUGINS_CONFIG.get('netbox_napalm_plugin', {}).get('NAPALM_PASSWORD', None) + timeout = settings.PLUGINS_CONFIG.get('netbox_napalm_plugin', {}).get('NAPALM_TIMEOUT', None) + optional_args = settings.PLUGINS_CONFIG.get('netbox_napalm_plugin', {}).get('NAPALM_ARGS', []).copy() + if device and device.platform and device.platform.napalm_args is not None: optional_args.update(device.platform.napalm_args) if extra_args != {}: From dfb88f905787c6076e53136e36fcb97d374c1bb6 Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Thu, 7 Sep 2023 13:21:09 -0500 Subject: [PATCH 7/7] More 3.6 compatibility fixes --- .../management/commands/runbackup.py | 1 + netbox_config_backup/models/backups.py | 10 +++++- netbox_config_backup/tasks.py | 32 +++++++++++-------- netbox_config_backup/utils/rq.py | 16 +++++++--- netbox_config_backup/views.py | 4 +-- 5 files changed, 41 insertions(+), 22 deletions(-) diff --git a/netbox_config_backup/management/commands/runbackup.py b/netbox_config_backup/management/commands/runbackup.py index 7b170f9..b3a478c 100644 --- a/netbox_config_backup/management/commands/runbackup.py +++ b/netbox_config_backup/management/commands/runbackup.py @@ -30,6 +30,7 @@ def run_backup(self, backup): def handle(self, *args, **options): from netbox_config_backup.models import Backup if options['device']: + print(f'Running:{options.get("device")}| ') backup = Backup.objects.filter(device__name=options['device']).first() if backup: self.run_backup(backup) diff --git a/netbox_config_backup/models/backups.py b/netbox_config_backup/models/backups.py index b6ebdad..5652172 100644 --- a/netbox_config_backup/models/backups.py +++ b/netbox_config_backup/models/backups.py @@ -3,6 +3,7 @@ import uuid as uuid from django.db import models +from django.db.models import Q from django.urls import reverse from django_rq import get_queue @@ -89,7 +90,14 @@ def enqueue_if_needed(self): return enqueue_if_needed(self) def requeue(self): - self.jobs.all().delete() + self.jobs.filter( + ~Q(status=JobResultStatusChoices.STATUS_COMPLETED) & + ~Q(status=JobResultStatusChoices.STATUS_FAILED) & + ~Q(status=JobResultStatusChoices.STATUS_ERRORED) + ).update( + status=JobResultStatusChoices.STATUS_FAILED + ) + remove_queued(self) self.enqueue_if_needed() def get_config(self, index='HEAD'): diff --git a/netbox_config_backup/tasks.py b/netbox_config_backup/tasks.py index cf9f85d..de22acf 100644 --- a/netbox_config_backup/tasks.py +++ b/netbox_config_backup/tasks.py @@ -12,6 +12,8 @@ from netbox.api.exceptions import ServiceUnavailable from netbox.config import get_config from netbox_config_backup.models import Backup, BackupJob, BackupCommit +from netbox_config_backup.utils.rq import can_backup + def get_logger(): # Setup logging to Stdout @@ -32,8 +34,8 @@ def napalm_init(device, ip=None, extra_args={}): timeout = settings.PLUGINS_CONFIG.get('netbox_napalm_plugin', {}).get('NAPALM_TIMEOUT', None) optional_args = settings.PLUGINS_CONFIG.get('netbox_napalm_plugin', {}).get('NAPALM_ARGS', []).copy() - if device and device.platform and device.platform.napalm_args is not None: - optional_args.update(device.platform.napalm_args) + if device and device.platform and device.platform.napalm.napalm_args is not None: + optional_args.update(device.platform.napalm.napalm_args) if extra_args != {}: optional_args.update(extra_args) @@ -58,10 +60,10 @@ def napalm_init(device, ip=None, extra_args={}): # Validate the configured driver try: - driver = napalm.get_network_driver(device.platform.napalm_driver) + driver = napalm.get_network_driver(device.platform.napalm.napalm_driver) except ModuleImportError: raise ServiceUnavailable("NAPALM driver for platform {} not found: {}.".format( - device.platform, device.platform.napalm_driver + device.platform, device.platform.napalm.napalm_driver )) # Connect to the device @@ -90,6 +92,9 @@ def backup_config(backup, pk=None): ip = backup.ip if backup.ip is not None else backup.device.primary_ip else: ip = None + if not can_backup(backup): + raise Exception(f'Cannot backup {backup}') + if backup.device is not None and ip is not None: logger.info(f'{backup}: Backup started') #logger.debug(f'[{pk}] Connecting') @@ -120,6 +125,10 @@ def backup_job(pk): logger.error(f'Cannot locate job (Id: {pk}) in DB') raise Exception(f'Cannot locate job (Id: {pk}) in DB') backup = job_result.backup + + if not can_backup(backup): + logger.warning(f'Cannot backup due to additional factors') + return 1 delay = timedelta(seconds=settings.PLUGINS_CONFIG.get('netbox_config_backup', {}).get('frequency')) job_result.started = timezone.now() @@ -136,6 +145,11 @@ def backup_job(pk): # Enqueue next job if one doesn't exist try: #logger.debug(f'[{pk}] Starting Enqueue') + BackupJob.objects.filter( + backup=backup + ).exclude( + status__in=JobResultStatusChoices.TERMINAL_STATE_CHOICES + ).update(status=JobResultStatusChoices.STATUS_FAILED) BackupJob.enqueue_if_needed(backup, delay=delay, job_id=job_result.job_id) #logger.debug(f'[{pk}] Finished Enqueue') except Exception as e: @@ -150,19 +164,11 @@ def backup_job(pk): except Exception as e: logger.error(f'Exception at line 148 on job: {backup}') logger.error(e) - job_result.set_status(JobResultStatusChoices.STATUS_FAILED) + job_result.set_status(JobResultStatusChoices.STATUS_ERRORED) BackupJob.enqueue_if_needed(backup, delay=delay, job_id=job_result.job_id) #logger.debug(f'[{pk}] Saving result') job_result.save() - # Clear queue of old jobs - BackupJob.objects.filter( - backup=backup, - status__in=JobResultStatusChoices.TERMINAL_STATE_CHOICES - ).exclude( - pk=job_result.pk - ).delete() - logger = get_logger() diff --git a/netbox_config_backup/utils/rq.py b/netbox_config_backup/utils/rq.py index 348c487..5e87aea 100644 --- a/netbox_config_backup/utils/rq.py +++ b/netbox_config_backup/utils/rq.py @@ -14,6 +14,7 @@ def can_backup(backup): + logger.debug(f'Checking backup status for {backup}') if backup.device is None: logger.info(f'No device for {backup}') return False @@ -27,13 +28,18 @@ def can_backup(backup): logger.info(f'Backup disabled for {backup} due to device status ({backup.device.status})') return False elif (backup.ip is None and backup.device.primary_ip is None) or backup.device.platform is None or \ - backup.device.platform.napalm_driver == '' or backup.device.platform.napalm_driver is None: + hasattr(backup.device.platform, 'napalm') is False or backup.device.platform.napalm is None or \ + backup.device.platform.napalm.napalm_driver == '' or backup.device.platform.napalm.napalm_driver is None: if backup.ip is None and backup.device.primary_ip is None: - logger.error(f'Backup disabled for {backup} due to no primary IP ({backup.device.status})') + logger.warning(f'Backup disabled for {backup} due to no primary IP ({backup.device.status})') elif backup.device.platform is None: - logger.error(f'Backup disabled for {backup} due to platform not set ({backup.device.status})') - elif backup.device.platform.napalm_driver == '' or backup.device.platform.napalm_driver is None: - logger.error(f'Backup disabled for {backup} due to napalm driver not set ({backup.device.status})') + logger.warning(f'Backup disabled for {backup} due to platform not set ({backup.device.status})') + elif hasattr(backup.device.platform, 'napalm') is False or backup.device.platform.napalm is None: + logger.warning( + f'Backup disabled for {backup} due to platform having no napalm config ({backup.device.status})' + ) + elif backup.device.platform.napalm.napalm_driver == '' or backup.device.platform.napalm.napalm_driver is None: + logger.warning(f'Backup disabled for {backup} due to napalm driver not set ({backup.device.status})') return False return True diff --git a/netbox_config_backup/views.py b/netbox_config_backup/views.py index cb5dc24..5e6a983 100644 --- a/netbox_config_backup/views.py +++ b/netbox_config_backup/views.py @@ -239,9 +239,7 @@ def get(self, request, backup, current=None, previous=None): previous_sha = previous.commit.sha if previous.commit is not None else 'HEAD' current_sha = current.commit.sha if current.commit is not None else None - print(f'Diffs: {current_sha}:{previous_sha}') - - if backup.device and backup.device.platform.napalm_driver in ['ios', 'nxos']: + if backup.device and backup.device.platform.napalm.napalm_driver in ['ios', 'nxos']: new = repo.read(current.file.path, current_sha) old = repo.read(previous.file.path, previous_sha) differ = Differ(old, new)