fix: backup validation display and debug logging
- Add 4-branch template guard to handle zero-value DumpValidation (shows "–" instead of misleading "Hiba" when validation never ran) - Add debug logging to ValidateDump for all code paths - Add cross-check re-validation in RefreshCache to heal stale lastDBDump validation state from disk Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -420,7 +421,8 @@ func (m *Manager) RefreshCache(nextDBDump, nextBackup time.Time) {
|
|||||||
if stats, err := m.restic.Stats(); err == nil {
|
if stats, err := m.restic.Stats(); err == nil {
|
||||||
status.RepoStats = stats
|
status.RepoStats = stats
|
||||||
}
|
}
|
||||||
if files, err := ListDumpFiles(m.cfg.Paths.DBDumpDir); err == nil {
|
files, filesErr := ListDumpFiles(m.cfg.Paths.DBDumpDir)
|
||||||
|
if filesErr == nil {
|
||||||
status.DumpFiles = files
|
status.DumpFiles = files
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
@@ -429,6 +431,25 @@ func (m *Manager) RefreshCache(nextDBDump, nextBackup time.Time) {
|
|||||||
status.DiscoveredDBs = dbs
|
status.DiscoveredDBs = dbs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cross-check: if LastDBDump results have empty validation but files exist,
|
||||||
|
// re-validate from disk. This handles controller restarts and race conditions.
|
||||||
|
if m.lastDBDump != nil && filesErr == nil {
|
||||||
|
fileValidation := make(map[string]DumpValidation) // keyed by filename
|
||||||
|
for _, f := range files {
|
||||||
|
fileValidation[f.FileName] = f.Validation
|
||||||
|
}
|
||||||
|
for i, r := range m.lastDBDump.Results {
|
||||||
|
if !r.Validation.Valid && r.Validation.Error == "" && r.FilePath != "" {
|
||||||
|
filename := filepath.Base(r.FilePath)
|
||||||
|
if fv, ok := fileValidation[filename]; ok {
|
||||||
|
m.lastDBDump.Results[i].Validation = fv
|
||||||
|
m.logger.Printf("[INFO] Re-validated %s from disk: valid=%v tables=%d",
|
||||||
|
filename, fv.Valid, fv.TableCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fill in dynamic fields under lock
|
// Fill in dynamic fields under lock
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
status.Running = m.running
|
status.Running = m.running
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ func DumpOne(ctx context.Context, db DiscoveredDB, dumpDir string, logger *log.L
|
|||||||
|
|
||||||
// ValidateDump checks a SQL dump file for basic structural integrity.
|
// ValidateDump checks a SQL dump file for basic structural integrity.
|
||||||
func ValidateDump(filePath string, dbType DBType) DumpValidation {
|
func ValidateDump(filePath string, dbType DBType) DumpValidation {
|
||||||
|
log.Printf("[DEBUG] ValidateDump: %s (type=%s)", filePath, dbType)
|
||||||
stat, err := os.Stat(filePath)
|
stat, err := os.Stat(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return DumpValidation{Error: fmt.Sprintf("stat failed: %v", err)}
|
return DumpValidation{Error: fmt.Sprintf("stat failed: %v", err)}
|
||||||
@@ -243,12 +244,14 @@ func ValidateDump(filePath string, dbType DBType) DumpValidation {
|
|||||||
|
|
||||||
if stat.Size() < 100 {
|
if stat.Size() < 100 {
|
||||||
v.Error = "dump file too small (< 100 bytes)"
|
v.Error = "dump file too small (< 100 bytes)"
|
||||||
|
log.Printf("[WARN] ValidateDump FAIL: %s — %s", filePath, v.Error)
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := os.ReadFile(filePath)
|
data, err := os.ReadFile(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
v.Error = fmt.Sprintf("read failed: %v", err)
|
v.Error = fmt.Sprintf("read failed: %v", err)
|
||||||
|
log.Printf("[WARN] ValidateDump FAIL: %s — %s", filePath, v.Error)
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,15 +298,18 @@ func ValidateDump(filePath string, dbType DBType) DumpValidation {
|
|||||||
case DBTypePostgres:
|
case DBTypePostgres:
|
||||||
v.Error = "PostgreSQL dump missing comment header"
|
v.Error = "PostgreSQL dump missing comment header"
|
||||||
}
|
}
|
||||||
|
log.Printf("[WARN] ValidateDump FAIL: %s — %s", filePath, v.Error)
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
if tableCount == 0 {
|
if tableCount == 0 {
|
||||||
v.Error = "no CREATE TABLE statements found"
|
v.Error = "no CREATE TABLE statements found"
|
||||||
|
log.Printf("[WARN] ValidateDump FAIL: %s — %s (header was found, scanned %d lines)", filePath, v.Error, len(strings.Split(content, "\n")))
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
v.Valid = true
|
v.Valid = true
|
||||||
|
log.Printf("[DEBUG] ValidateDump OK: %s — %d tables, header found", filePath, tableCount)
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -136,8 +136,10 @@
|
|||||||
<span class="validation-badge validation-na">–</span>
|
<span class="validation-badge validation-na">–</span>
|
||||||
{{else if .Validation.Valid}}
|
{{else if .Validation.Valid}}
|
||||||
<span class="validation-badge validation-ok">{{.Validation.TableCount}} tábla</span>
|
<span class="validation-badge validation-ok">{{.Validation.TableCount}} tábla</span>
|
||||||
{{else}}
|
{{else if .Validation.Error}}
|
||||||
<span class="validation-badge validation-fail" title="{{.Validation.Error}}">Hiba</span>
|
<span class="validation-badge validation-fail" title="{{.Validation.Error}}">Hiba</span>
|
||||||
|
{{else}}
|
||||||
|
<span class="validation-badge validation-na" title="Az érvényesítés nem futott le">–</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
Reference in New Issue
Block a user