feat/add backup scheduling functionality

This commit is contained in:
biersoeckli
2025-01-05 15:18:03 +00:00
parent 2e44f96b63
commit ca55985fea
6 changed files with 64 additions and 63 deletions

View File

@@ -82,22 +82,19 @@ export const saveBackupVolume = async (prevState: any, inputData: VolumeBackupEd
if (validatedData.retention < 1) {
throw new ServiceException('Retention must be at least 1');
}
if (validatedData.id) {
await backupService.unregisterBackupJob(validatedData.id);
}
const savedVolumeBackup = await volumeBackupService.save({
...validatedData,
id: validatedData.id ?? undefined,
});
await backupService.registerBackupJob(savedVolumeBackup);
await backupService.registerAllBackups();
return new SuccessActionResult();
});
export const deleteBackupVolume = async (backupVolumeId: string) =>
simpleAction(async () => {
await getAuthUserSession();
await backupService.unregisterBackupJob(backupVolumeId);
await volumeBackupService.deleteById(backupVolumeId);
await backupService.registerAllBackups();
return new SuccessActionResult(undefined, 'Successfully deleted backup schedule');
});

View File

@@ -1,13 +0,0 @@
import dataAccess from "./server/adapter/db.client";
import backupService from "./server/services/standalone-services/backup.service";
export default async function registreAllBackupSchedules() {
const volumeBackups = await dataAccess.client.volumeBackup.findMany();
console.log(`Registering ${volumeBackups.length} backup schedules...`);
for (const volumeBackup of volumeBackups) {
await backupService.registerBackupJob(volumeBackup);
}
console.log('Backup schedules registered.');
}

View File

@@ -7,7 +7,7 @@ import { CommandExecutorUtils } from './server/utils/command-executor.utils'
import dataAccess from './server/adapter/db.client'
import { FancyConsoleUtils } from './shared/utils/fancy-console.utils'
import { Constants } from './shared/utils/constants'
import registreAllBackupSchedules from './backup.server'
import backupService from './server/services/standalone-services/backup.service'
// Source: https://nextjs.org/docs/app/building-your-application/configuring/custom-server
@@ -51,10 +51,10 @@ async function initializeNextJs() {
}
}
await registreAllBackupSchedules();
await backupService.registerAllBackups();
const app = next({ dev })
const handle = app.getRequestHandler()
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {

View File

@@ -3,31 +3,45 @@ import { ServiceException } from "../../../shared/model/service.exception.model"
import { PathUtils } from "../../utils/path.utils";
import { FsUtils } from "../../utils/fs.utils";
import s3Service from "../aws-s3.service";
import { VolumeBackup } from "@prisma/client";
import scheduleService from "./schedule.service";
import standalonePodService from "./standalone-pod.service";
import { ListUtils } from "../../../shared/utils/list.utils";
class BackupService {
async registerBackupJob(volumeBackup: VolumeBackup) {
const cron = volumeBackup.cron;
const jobName = `backup-volume-${volumeBackup.id}`;
scheduleService.scheduleJob(jobName, cron, async () => {
try {
await this.runBackupForVolume(volumeBackup.id);
} catch (e) {
console.error(`Error during backup for volume ${volumeBackup.id}`);
console.error(e);
}
});
async registerAllBackups() {
const allVolumeBackups = await dataAccess.client.volumeBackup.findMany();
console.log(`Deregistering existing backup schedules...`);
this.unregisterAllBackups();
console.log(`Registering ${allVolumeBackups.length} backup schedules...`);
const groupedByCron = ListUtils.groupBy(allVolumeBackups, vb => vb.cron);
for (const [cron, volumeBackups] of Array.from(groupedByCron.entries())) {
scheduleService.scheduleJob(cron, cron, async () => {
console.log(`Running backup for ${volumeBackups.length} volumes...`);
for (const volumeBackup of volumeBackups) {
try {
await this.runBackupForVolume(volumeBackup.id);
} catch (e) {
console.error(`Error during backup for volume ${volumeBackup.volumeId} and backup ${volumeBackup.id}`);
console.error(e);
}
}
console.log(`Backup for ${volumeBackups.length} volumes finished.`);
});
}
}
async unregisterBackupJob(volumeBackupId: string) {
const jobName = `backup-volume-${volumeBackupId}`;
scheduleService.cancelJob(jobName);
async unregisterAllBackups() {
const allJobs = scheduleService.getAlJobs();
for (const jobName of allJobs) {
scheduleService.cancelJob(jobName);
}
}
async runBackupForVolume(backupVolumeId: string) {
console.log(`Running backup for backupVolume ${backupVolumeId}`);
const backupVolume = await dataAccess.client.volumeBackup.findFirstOrThrow({
where: {

View File

@@ -21,7 +21,7 @@ class ScheduleService {
scheduleJob(jobName: string, cronExpression: string, callback: schedule.JobCallback) {
const job = new this.schedule.Job(jobName, callback);
job.schedule(cronExpression);
console.log(`[${ScheduleService.name}] Job ${jobName} scheduled with cron ${cronExpression}`);
console.log(`[${ScheduleService.name}] Job scheduled with cron ${cronExpression}`);
}
cancelJob(jobName: string) {
@@ -32,6 +32,10 @@ class ScheduleService {
}
}
getAlJobs() {
return Object.keys(this.schedule.scheduledJobs);
}
printScheduledJobs() {
console.log(`[${ScheduleService.name}] Scheduled jobs: \n- ${Object.keys(this.schedule.scheduledJobs).join('\n- ')}`);
}

View File

@@ -114,32 +114,31 @@ class SetupPodService {
return new Promise<void>((resolve, reject) => {
const exec = new k8s.Exec(k3s.getKubeConfig());
exec
.exec(
namespace,
podName,
containerName,
command,
writerStream,
stderrStream,
null,
false,
async ({ status }) => {
try {
writerStream.close();
if (status === 'Failure') {
return reject(
new Error(
`Error from cpFromPod - details: \n ${stderrStream.read().toString()}`,
),
);
}
resolve();
} catch (e) {
reject(e);
exec.exec(
namespace,
podName,
containerName,
command,
writerStream,
stderrStream,
null,
false,
async ({ status }) => {
try {
writerStream.close();
if (status === 'Failure') {
return reject(
new Error(
`Error from cpFromPod - details: \n ${stderrStream.read().toString()}`,
),
);
}
},
)
resolve();
} catch (e) {
reject(e);
}
},
)
.catch(reject);
});
}