fix: crashing bug on partial backup (#39)

This commit is contained in:
Gareth
2023-12-23 01:49:39 -08:00
committed by GitHub
parent df9e0aec90
commit fba6c8da86
7 changed files with 35 additions and 36 deletions

View File

@@ -193,11 +193,9 @@ func (s *Server) GetOperationEvents(ctx context.Context, req *connect.Request[em
return
}
go func() {
if err := resp.Send(event); err != nil {
errorChan <- fmt.Errorf("failed to send event: %w", err)
}
}()
}
s.oplog.Subscribe(&callback)
defer s.oplog.Unsubscribe(&callback)

View File

@@ -85,7 +85,7 @@ func (r *RepoOrchestrator) Backup(ctx context.Context, plan *v1.Plan, progressCa
summary, err := r.repo.Backup(ctx, progressCallback, opts...)
if err != nil {
return nil, fmt.Errorf("failed to backup: %w", err)
return summary, fmt.Errorf("failed to backup: %w", err)
}
r.l.Debug("Backup completed", zap.String("repo", r.repoConfig.Id), zap.Duration("duration", time.Since(startTime)))
@@ -115,16 +115,12 @@ func (r *RepoOrchestrator) Forget(ctx context.Context, plan *v1.Plan) ([]*v1.Res
return nil, fmt.Errorf("plan %q has no retention policy", plan.Id)
}
l := r.l.With(zap.String("plan", plan.Id))
l.Debug("Forget snapshots", zap.Any("policy", policy))
result, err := r.repo.Forget(
ctx, protoutil.RetentionPolicyFromProto(plan.Retention),
restic.WithFlags("--tag", tagForPlan(plan)), restic.WithFlags("--group-by", "tag"))
if err != nil {
return nil, fmt.Errorf("get snapshots for repo %v: %w", r.repoConfig.Id, err)
}
l.Debug("Forget result", zap.Any("result", result))
var forgotten []*v1.ResticSnapshot
for _, snapshot := range result.Remove {
@@ -135,6 +131,8 @@ func (r *RepoOrchestrator) Forget(ctx context.Context, plan *v1.Plan) ([]*v1.Res
forgotten = append(forgotten, snapshotProto)
}
zap.L().Debug("Forgot snapshots", zap.String("plan", plan.Id), zap.Int("count", len(forgotten)), zap.Any("policy", policy))
return forgotten, nil
}

View File

@@ -137,7 +137,7 @@ func (r *Repo) Backup(ctx context.Context, progressCallback func(*BackupProgress
if err := cmd.Wait(); err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
if exitErr.ExitCode() == 1 {
if exitErr.ExitCode() == 3 {
cmdErr = ErrPartialBackup
} else {
cmdErr = fmt.Errorf("exit code %v: %w", exitErr.ExitCode(), ErrBackupFailed)
@@ -151,7 +151,7 @@ func (r *Repo) Backup(ctx context.Context, progressCallback func(*BackupProgress
wg.Wait()
if cmdErr != nil || readErr != nil {
return nil, newCmdErrorPreformatted(cmd, output.String(), errors.Join(cmdErr, readErr))
return summary, newCmdErrorPreformatted(cmd, output.String(), errors.Join(cmdErr, readErr))
}
return summary, nil

View File

@@ -126,7 +126,8 @@ export const OperationList = ({
size="small"
dataSource={backups}
renderItem={(backup) => {
const ops = backup.operations;
const ops = [...backup.operations];
ops.reverse();
return (
<Card size="small" style={{ margin: "5px" }}>
{ops.map((op) => {

View File

@@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from "react";
import {
BackupInfo,
BackupInfoCollector,
colorForStatus,
displayTypeToString,
getOperations,
getTypeForDisplay,
@@ -260,27 +261,9 @@ const buildTreeDay = (keyPrefix: string, operations: BackupInfo[]): OpTreeNode[]
const buildTreeLeaf = (operations: BackupInfo[]): OpTreeNode[] => {
const entries = _.map(operations, (b): OpTreeNode => {
let iconColor = "grey";
let iconColor = colorForStatus(b.status);
let icon: React.ReactNode | null = <QuestionOutlined />;
switch (b.status) {
case OperationStatus.STATUS_PENDING:
iconColor = "grey";
break;
case OperationStatus.STATUS_SUCCESS:
iconColor = "green";
break;
case OperationStatus.STATUS_ERROR:
iconColor = "red";
break;
case OperationStatus.STATUS_INPROGRESS:
iconColor = "blue";
break;
case OperationStatus.STATUS_USER_CANCELLED:
iconColor = "orange";
break;
}
if (b.status === OperationStatus.STATUS_ERROR) {
icon = <ExclamationOutlined style={{ color: iconColor }} />;
} else {

View File

@@ -91,7 +91,7 @@ export class BackupInfoCollector {
private createBackup(operations: Operation[]): BackupInfo {
// deduplicate and sort operations.
operations.sort((a, b) => {
return Number(b.unixTimeStartMs - a.unixTimeStartMs);
return Number(a.unixTimeStartMs - b.unixTimeStartMs);
});
// use the lowest ID of all operations as the ID of the backup, this will be the first created operation.
@@ -258,6 +258,26 @@ export const displayTypeToString = (type: DisplayType) => {
return "Unknown";
}
};
export const colorForStatus = (status: OperationStatus) => {
switch (status) {
case OperationStatus.STATUS_PENDING:
return "grey";
case OperationStatus.STATUS_INPROGRESS:
return "blue";
case OperationStatus.STATUS_ERROR:
return "red";
case OperationStatus.STATUS_WARNING:
return "orange";
case OperationStatus.STATUS_SUCCESS:
return "green";
case OperationStatus.STATUS_USER_CANCELLED:
return "orange";
default:
return "grey";
}
}
// detailsForOperation returns derived display information for a given operation.
export const detailsForOperation = (
op: Operation

View File

@@ -49,7 +49,6 @@ export const AddPlanModal = ({
try {
let config = await fetchConfig();
config.plans = config.plans || [];
if (!template) {
throw new Error("template not found");
@@ -82,10 +81,9 @@ export const AddPlanModal = ({
setConfirmLoading(true);
try {
let plan = await validateForm<Plan>(form);
let plan = new Plan(await validateForm<Plan>(form));
let config = await fetchConfig();
config.plans = config.plans || [];
// Merge the new plan (or update) into the config
if (template) {
@@ -103,6 +101,7 @@ export const AddPlanModal = ({
showModal(null);
} catch (e: any) {
alertsApi.error("Operation failed: " + e.message, 15);
console.error(e);
} finally {
setConfirmLoading(false);
}