From aaed53ec5f7c2acf2d3ab2dcd4e584f454e162a8 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Wed, 3 Apr 2024 11:12:11 -0300 Subject: [PATCH] Perform backup tasks --- backup_data/views.py | 109 +++++++++++++++++- .../0011_routerstatus_backup_lock.py | 18 +++ .../0012_alter_router_router_type.py | 18 +++ router_manager/models.py | 3 +- router_manager/views.py | 25 ++++ routerfleet/urls.py | 9 +- routerlib/backup_functions.py | 8 ++ templates/router_manager/router_details.html | 9 ++ 8 files changed, 189 insertions(+), 10 deletions(-) create mode 100644 router_manager/migrations/0011_routerstatus_backup_lock.py create mode 100644 router_manager/migrations/0012_alter_router_router_type.py diff --git a/backup_data/views.py b/backup_data/views.py index 74b3e6b..e4d8a6d 100644 --- a/backup_data/views.py +++ b/backup_data/views.py @@ -1,11 +1,14 @@ -from django.shortcuts import render +import time +from datetime import datetime, timedelta + +from django.db.models import Q from django.http import JsonResponse +from django.utils import timezone + from backup.models import BackupProfile from backup_data.models import RouterBackup from router_manager.models import Router, BackupSchedule - -from datetime import datetime, timedelta -from django.utils import timezone +from routerlib.backup_functions import perform_backup def next_weekday(now, weekday, hour): @@ -69,7 +72,7 @@ def calculate_next_backup(backup_profile): return next_daily_backup, next_weekly_backup, next_monthly_backup -def generate_backup_schedule(request): +def view_generate_backup_schedule(request): data = { 'backup_schedule_created': 0, 'daily_backup_schedule_created': 0, @@ -137,4 +140,98 @@ def generate_backup_schedule(request): schedule.next_monthly_backup = None schedule.save() data['monthly_backup_schedule_removed'] += 1 - return JsonResponse(data) \ No newline at end of file + return JsonResponse(data) + + +def create_backup_tasks_from_schedule_list(schedule_list, schedule_type): + tasks_created = 0 + for schedule in schedule_list: + if schedule_type == 'daily': + schedule_time = schedule.next_daily_backup + schedule.next_daily_backup = None + elif schedule_type == 'weekly': + schedule_time = schedule.next_weekly_backup + schedule.next_weekly_backup = None + elif schedule_type == 'monthly': + schedule_time = schedule.next_monthly_backup + schedule.next_monthly_backup = None + else: + return + schedule.save() + + backup = RouterBackup.objects.create( + router=schedule.router, schedule_time=schedule_time, schedule_type=schedule_type + ) + tasks_created += 1 + backup.save() + backup.router.routerstatus.backup_lock = backup.schedule_time + backup.router.routerstatus.save() + + return tasks_created + + +def view_create_backup_tasks(request): + data = { + 'daily_backup_tasks_created': 0, + 'weekly_backup_tasks_created': 0, + 'monthly_backup_tasks_created': 0 + } + # Priorize monthly, then weekly, then daily. + monthly_pending_schedule_list = BackupSchedule.objects.filter( + next_monthly_backup__lte=timezone.now(), router__enabled=True, router__routerstatus__backup_lock__isnull=True + ).filter( + Q(router__monitoring=False) | Q(router__monitoring=True, router__routerstatus__status_online=True) + ) + data['monthly_backup_tasks_created'] = create_backup_tasks_from_schedule_list( + monthly_pending_schedule_list, 'monthly' + ) + + weekly_pending_schedule_list = BackupSchedule.objects.filter( + next_weekly_backup__lte=timezone.now(), router__enabled=True, router__routerstatus__backup_lock__isnull=True + ).filter( + Q(router__monitoring=False) | Q(router__monitoring=True, router__routerstatus__status_online=True) + ) + data['weekly_backup_tasks_created'] = create_backup_tasks_from_schedule_list( + weekly_pending_schedule_list, 'weekly' + ) + + daily_pending_schedule_list = BackupSchedule.objects.filter( + next_daily_backup__lte=timezone.now(), router__enabled=True, router__routerstatus__backup_lock__isnull=True + ).filter( + Q(router__monitoring=False) | Q(router__monitoring=True, router__routerstatus__status_online=True) + ) + data['daily_backup_tasks_created'] = create_backup_tasks_from_schedule_list( + daily_pending_schedule_list, 'daily' + ) + + return JsonResponse(data) + + +def view_perform_backup_tasks(request): + data = { + 'backup_tasks_performed': 0 + } + max_execution_time = 45 # seconds + execution_start_time = timezone.now() + pending_backup_list = RouterBackup.objects.filter(success=False, error=False).filter( + Q(schedule_time__lte=timezone.now(), next_retry__isnull=True) | Q(next_retry__lte=timezone.now()) + ).filter( + Q(router__monitoring=False) | Q(router__monitoring=True, router__routerstatus__status_online=True) + ) + + for backup in pending_backup_list: + perform_backup(backup) + data['backup_tasks_performed'] += 1 + if backup.router.backup_profile.backup_interval >= 60: + break + else: + if timezone.now() - execution_start_time > timedelta(seconds=max_execution_time): + break + else: + if backup.router.backup_profile.backup_interval > 0: + time.sleep(backup.router.backup_profile.backup_interval) + if timezone.now() - execution_start_time > timedelta(seconds=max_execution_time): + break + + return JsonResponse(data) + diff --git a/router_manager/migrations/0011_routerstatus_backup_lock.py b/router_manager/migrations/0011_routerstatus_backup_lock.py new file mode 100644 index 0000000..ae89d73 --- /dev/null +++ b/router_manager/migrations/0011_routerstatus_backup_lock.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.3 on 2024-04-03 11:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('router_manager', '0010_alter_router_router_type'), + ] + + operations = [ + migrations.AddField( + model_name='routerstatus', + name='backup_lock', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/router_manager/migrations/0012_alter_router_router_type.py b/router_manager/migrations/0012_alter_router_router_type.py new file mode 100644 index 0000000..810218f --- /dev/null +++ b/router_manager/migrations/0012_alter_router_router_type.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.3 on 2024-04-03 14:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('router_manager', '0011_routerstatus_backup_lock'), + ] + + operations = [ + migrations.AlterField( + model_name='router', + name='router_type', + field=models.CharField(choices=[('monitoring', 'Monitoring Only'), ('routeros', 'Mikrotik (RouterOS)')], max_length=100), + ), + ] diff --git a/router_manager/models.py b/router_manager/models.py index 6884a57..1851569 100644 --- a/router_manager/models.py +++ b/router_manager/models.py @@ -26,7 +26,7 @@ class Router(models.Model): monitoring = models.BooleanField(default=True) backup_profile = models.ForeignKey(BackupProfile, on_delete=models.SET_NULL, null=True, blank=True) - router_type = models.CharField(max_length=100, choices=(('monitoring', 'Monitoring Only'), ('routeros', 'Mikrotik (RouterOS)'), ('openwrt', 'OpenWRT'))) + router_type = models.CharField(max_length=100, choices=(('monitoring', 'Monitoring Only'), ('routeros', 'Mikrotik (RouterOS)'))) enabled = models.BooleanField(default=True) updated = models.DateTimeField(auto_now=True) @@ -43,6 +43,7 @@ class RouterStatus(models.Model): last_status_change = models.DateTimeField(blank=True, null=True) last_backup = models.DateTimeField(blank=True, null=True) last_backup_failed = models.DateTimeField(blank=True, null=True) + backup_lock = models.DateTimeField(blank=True, null=True) updated = models.DateTimeField(auto_now=True) created = models.DateTimeField(auto_now_add=True) diff --git a/router_manager/views.py b/router_manager/views.py index 8da2f1e..ce52419 100644 --- a/router_manager/views.py +++ b/router_manager/views.py @@ -1,6 +1,9 @@ from django.contrib import messages +from django.utils import timezone from django.shortcuts import render, get_object_or_404, redirect from django.contrib.auth.decorators import login_required + +from backup_data.models import RouterBackup from .models import Router, RouterGroup, RouterStatus, SSHKey, BackupSchedule from .forms import RouterForm, RouterGroupForm, SSHKeyForm @@ -145,3 +148,25 @@ def view_manage_sshkey(request): 'instance': sshkey } return render(request, 'generic_form.html', context=context) + + +@login_required() +def view_create_instant_backup_task(request): + router = get_object_or_404(Router, uuid=request.GET.get('uuid')) + router_details_url = f'/router/details/?uuid={router.uuid}' + if RouterBackup.objects.filter(router=router, success=False, error=False).exists(): + messages.warning(request, 'Backup task not created|Active router backup task already exists') + return redirect(router_details_url) + if router.routerstatus.backup_lock is not None: + messages.warning(request, 'Backup task not created|Router backup is currently locked') + return redirect(router_details_url) + if not router.backup_profile: + messages.warning(request, 'Backup task not created|Router has no backup profile') + return redirect(router_details_url) + + router_backup = RouterBackup.objects.create(router=router, schedule_time=timezone.now(), schedule_type='instant') + router.routerstatus.backup_lock = router_backup.schedule_time + router.routerstatus.save() + messages.success(request, 'Backup task created successfully') + return redirect(router_details_url) + diff --git a/routerfleet/urls.py b/routerfleet/urls.py index a51352d..6e1f03c 100644 --- a/routerfleet/urls.py +++ b/routerfleet/urls.py @@ -3,10 +3,10 @@ from django.urls import path from dashboard.views import view_dashboard, view_status from user_manager.views import view_manage_user, view_user_list from accounts.views import view_login, view_logout, view_create_first_user -from router_manager.views import view_router_list, view_manage_router, view_router_group_list, view_ssh_key_list, view_manage_router_group, view_manage_sshkey, view_router_details +from router_manager.views import view_router_list, view_manage_router, view_router_group_list, view_ssh_key_list, view_manage_router_group, view_manage_sshkey, view_router_details, view_create_instant_backup_task from backup.views import view_backup_profile_list, view_manage_backup_profile, view_backup_list, view_backup_details, view_debug_run_backups, view_compare_backups, view_backup_download, view_backup_delete from monitoring.views import view_export_router_list, view_update_router_status -from backup_data.views import generate_backup_schedule +from backup_data.views import view_generate_backup_schedule, view_create_backup_tasks, view_perform_backup_tasks urlpatterns = [ @@ -26,6 +26,7 @@ urlpatterns = [ path('router/ssh_keys/', view_ssh_key_list, name='ssh_keys_list'), path('router/manage_group/', view_manage_router_group, name='manage_router_group'), path('router/manage_sshkey/', view_manage_sshkey, name='manage_sshkey'), + path('router/create_instant_backup/', view_create_instant_backup_task, name='create_instant_backup_task'), path('backup/profile_list/', view_backup_profile_list, name='backup_profile_list'), path('backup/manage_profile/', view_manage_backup_profile, name='manage_backup_profile'), path('backup/backup_list/', view_backup_list, name='backup_list'), @@ -35,5 +36,7 @@ urlpatterns = [ path('backup/delete/', view_backup_delete, name='delete_backup'), path('monitoring/export_router_list/', view_export_router_list, name='export_router_list'), path('monitoring/update_router_status/', view_update_router_status, name='update_router_status'), - path('cron/generate_backup_schedule/', generate_backup_schedule, name='generate_backup_schedule'), + path('cron/generate_backup_schedule/', view_generate_backup_schedule, name='generate_backup_schedule'), + path('cron/create_backup_tasks/', view_create_backup_tasks, name='create_backup_tasks'), + path('cron/perform_backup_tasks/', view_perform_backup_tasks, name='perform_backup_tasks'), ] diff --git a/routerlib/backup_functions.py b/routerlib/backup_functions.py index 6e5f2f8..254f6aa 100644 --- a/routerlib/backup_functions.py +++ b/routerlib/backup_functions.py @@ -10,15 +10,21 @@ from routerlib.functions import gen_backup_name def perform_backup(router_backup: RouterBackup): if router_backup.success or router_backup.error: + router_backup.router.routerstatus.backup_lock = None + router_backup.router.routerstatus.save() return if not router_backup.router.backup_profile: router_backup.error = True router_backup.error_message = "No backup profile assigned" router_backup.save() + router_backup.router.routerstatus.backup_lock = None + router_backup.router.routerstatus.save() return if router_backup.retry_count > router_backup.router.backup_profile.max_retry: router_backup.error = True router_backup.save() + router_backup.router.routerstatus.backup_lock = None + router_backup.router.routerstatus.save() return if router_backup.backup_pending_retrieval: @@ -39,6 +45,8 @@ def perform_backup(router_backup: RouterBackup): router_backup.error_message = '' router_backup.success = True router_backup.save() + router_backup.router.routerstatus.backup_lock = None + router_backup.router.routerstatus.save() else: handle_backup_failure(router_backup, error_message) else: diff --git a/templates/router_manager/router_details.html b/templates/router_manager/router_details.html index 7d76dbd..a3de2df 100644 --- a/templates/router_manager/router_details.html +++ b/templates/router_manager/router_details.html @@ -85,6 +85,15 @@ {% endfor %} + +
  • + Instant Backup + + + Backup now + + +
  • Notes {{ router.internal_notes|default_if_none:""|linebreaksbr }}