Perform backup tasks

This commit is contained in:
Eduardo Silva 2024-04-03 11:12:11 -03:00
parent a12a126d38
commit aaed53ec5f
8 changed files with 189 additions and 10 deletions

View file

@ -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.http import JsonResponse
from django.utils import timezone
from backup.models import BackupProfile from backup.models import BackupProfile
from backup_data.models import RouterBackup from backup_data.models import RouterBackup
from router_manager.models import Router, BackupSchedule from router_manager.models import Router, BackupSchedule
from routerlib.backup_functions import perform_backup
from datetime import datetime, timedelta
from django.utils import timezone
def next_weekday(now, weekday, hour): 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 return next_daily_backup, next_weekly_backup, next_monthly_backup
def generate_backup_schedule(request): def view_generate_backup_schedule(request):
data = { data = {
'backup_schedule_created': 0, 'backup_schedule_created': 0,
'daily_backup_schedule_created': 0, 'daily_backup_schedule_created': 0,
@ -138,3 +141,97 @@ def generate_backup_schedule(request):
schedule.save() schedule.save()
data['monthly_backup_schedule_removed'] += 1 data['monthly_backup_schedule_removed'] += 1
return JsonResponse(data) 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)

View file

@ -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),
),
]

View file

@ -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),
),
]

View file

@ -26,7 +26,7 @@ class Router(models.Model):
monitoring = models.BooleanField(default=True) monitoring = models.BooleanField(default=True)
backup_profile = models.ForeignKey(BackupProfile, on_delete=models.SET_NULL, null=True, blank=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) enabled = models.BooleanField(default=True)
updated = models.DateTimeField(auto_now=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_status_change = models.DateTimeField(blank=True, null=True)
last_backup = models.DateTimeField(blank=True, null=True) last_backup = models.DateTimeField(blank=True, null=True)
last_backup_failed = 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) updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)

View file

@ -1,6 +1,9 @@
from django.contrib import messages from django.contrib import messages
from django.utils import timezone
from django.shortcuts import render, get_object_or_404, redirect from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from backup_data.models import RouterBackup
from .models import Router, RouterGroup, RouterStatus, SSHKey, BackupSchedule from .models import Router, RouterGroup, RouterStatus, SSHKey, BackupSchedule
from .forms import RouterForm, RouterGroupForm, SSHKeyForm from .forms import RouterForm, RouterGroupForm, SSHKeyForm
@ -145,3 +148,25 @@ def view_manage_sshkey(request):
'instance': sshkey 'instance': sshkey
} }
return render(request, 'generic_form.html', context=context) 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)

View file

@ -3,10 +3,10 @@ from django.urls import path
from dashboard.views import view_dashboard, view_status from dashboard.views import view_dashboard, view_status
from user_manager.views import view_manage_user, view_user_list from user_manager.views import view_manage_user, view_user_list
from accounts.views import view_login, view_logout, view_create_first_user 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 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 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 = [ urlpatterns = [
@ -26,6 +26,7 @@ urlpatterns = [
path('router/ssh_keys/', view_ssh_key_list, name='ssh_keys_list'), 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_group/', view_manage_router_group, name='manage_router_group'),
path('router/manage_sshkey/', view_manage_sshkey, name='manage_sshkey'), 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/profile_list/', view_backup_profile_list, name='backup_profile_list'),
path('backup/manage_profile/', view_manage_backup_profile, name='manage_backup_profile'), path('backup/manage_profile/', view_manage_backup_profile, name='manage_backup_profile'),
path('backup/backup_list/', view_backup_list, name='backup_list'), 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('backup/delete/', view_backup_delete, name='delete_backup'),
path('monitoring/export_router_list/', view_export_router_list, name='export_router_list'), 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('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'),
] ]

View file

@ -10,15 +10,21 @@ from routerlib.functions import gen_backup_name
def perform_backup(router_backup: RouterBackup): def perform_backup(router_backup: RouterBackup):
if router_backup.success or router_backup.error: if router_backup.success or router_backup.error:
router_backup.router.routerstatus.backup_lock = None
router_backup.router.routerstatus.save()
return return
if not router_backup.router.backup_profile: if not router_backup.router.backup_profile:
router_backup.error = True router_backup.error = True
router_backup.error_message = "No backup profile assigned" router_backup.error_message = "No backup profile assigned"
router_backup.save() router_backup.save()
router_backup.router.routerstatus.backup_lock = None
router_backup.router.routerstatus.save()
return return
if router_backup.retry_count > router_backup.router.backup_profile.max_retry: if router_backup.retry_count > router_backup.router.backup_profile.max_retry:
router_backup.error = True router_backup.error = True
router_backup.save() router_backup.save()
router_backup.router.routerstatus.backup_lock = None
router_backup.router.routerstatus.save()
return return
if router_backup.backup_pending_retrieval: if router_backup.backup_pending_retrieval:
@ -39,6 +45,8 @@ def perform_backup(router_backup: RouterBackup):
router_backup.error_message = '' router_backup.error_message = ''
router_backup.success = True router_backup.success = True
router_backup.save() router_backup.save()
router_backup.router.routerstatus.backup_lock = None
router_backup.router.routerstatus.save()
else: else:
handle_backup_failure(router_backup, error_message) handle_backup_failure(router_backup, error_message)
else: else:

View file

@ -85,6 +85,15 @@
{% endfor %} {% endfor %}
</span> </span>
</li> </li>
<li class="list-group-item">
<b>Instant Backup</b>
<span class="float-right">
<a href="/router/create_instant_backup/?uuid={{ router.uuid }}">
Backup now
</a>
</span>
</li>
<li class="list-group-item"> <li class="list-group-item">
<b>Notes</b> <b>Notes</b>
<span class="float-right">{{ router.internal_notes|default_if_none:""|linebreaksbr }}</span> <span class="float-right">{{ router.internal_notes|default_if_none:""|linebreaksbr }}</span>