From 6033e39466c593454e95c37ca4a2bb1252999c65 Mon Sep 17 00:00:00 2001 From: sagitchu Date: Sat, 2 May 2026 00:02:23 +0800 Subject: [PATCH] fix: preserve probe targets in backups --- go-backend/internal/store/model/model.go | 28 +++++---- go-backend/internal/store/repo/repository.go | 29 +++++---- .../store/repo/repository_backup_test.go | 59 +++++++++++++++++++ 3 files changed, 90 insertions(+), 26 deletions(-) create mode 100644 go-backend/internal/store/repo/repository_backup_test.go diff --git a/go-backend/internal/store/model/model.go b/go-backend/internal/store/model/model.go index e5b0f59..965c6fe 100644 --- a/go-backend/internal/store/model/model.go +++ b/go-backend/internal/store/model/model.go @@ -405,19 +405,21 @@ type NodeBackup struct { } type TunnelBackup struct { - ID int64 `json:"id"` - Name string `json:"name"` - TrafficRatio float64 `json:"trafficRatio"` - Type int `json:"type"` - Protocol string `json:"protocol"` - Flow int64 `json:"flow"` - CreatedTime int64 `json:"createdTime"` - UpdatedTime int64 `json:"updatedTime"` - Status int `json:"status"` - InIP string `json:"inIp,omitempty"` - Inx int `json:"inx"` - IPPreference string `json:"ipPreference,omitempty"` - ChainTunnels []ChainTunnelBackup `json:"chainTunnels,omitempty"` + ID int64 `json:"id"` + Name string `json:"name"` + TrafficRatio float64 `json:"trafficRatio"` + Type int `json:"type"` + Protocol string `json:"protocol"` + Flow int64 `json:"flow"` + CreatedTime int64 `json:"createdTime"` + UpdatedTime int64 `json:"updatedTime"` + Status int `json:"status"` + InIP string `json:"inIp,omitempty"` + Inx int `json:"inx"` + IPPreference string `json:"ipPreference,omitempty"` + ProbeTargetHost string `json:"probeTargetHost,omitempty"` + ProbeTargetPort int `json:"probeTargetPort,omitempty"` + ChainTunnels []ChainTunnelBackup `json:"chainTunnels,omitempty"` } type ChainTunnelBackup struct { diff --git a/go-backend/internal/store/repo/repository.go b/go-backend/internal/store/repo/repository.go index dea9e02..4206ec8 100644 --- a/go-backend/internal/store/repo/repository.go +++ b/go-backend/internal/store/repo/repository.go @@ -2063,6 +2063,7 @@ func (r *Repository) exportTunnels() ([]model.TunnelBackup, error) { Type: t.Type, Protocol: t.Protocol, Flow: t.Flow, CreatedTime: t.CreatedTime, UpdatedTime: t.UpdatedTime, Status: t.Status, Inx: t.Inx, IPPreference: t.IPPreference, + ProbeTargetHost: t.ProbeTargetHost, ProbeTargetPort: t.ProbeTargetPort, } if t.InIP.Valid { b.InIP = t.InIP.String @@ -2458,23 +2459,25 @@ func importTunnels(tx *gorm.DB, tunnels []model.TunnelBackup, now int64) (int, e count := 0 for _, t := range tunnels { item := model.Tunnel{ - ID: t.ID, - Name: t.Name, - TrafficRatio: t.TrafficRatio, - Type: t.Type, - Protocol: t.Protocol, - Flow: t.Flow, - CreatedTime: t.CreatedTime, - UpdatedTime: now, - Status: t.Status, - InIP: sql.NullString{String: t.InIP, Valid: true}, - Inx: t.Inx, - IPPreference: t.IPPreference, + ID: t.ID, + Name: t.Name, + TrafficRatio: t.TrafficRatio, + Type: t.Type, + Protocol: t.Protocol, + Flow: t.Flow, + CreatedTime: t.CreatedTime, + UpdatedTime: now, + Status: t.Status, + InIP: sql.NullString{String: t.InIP, Valid: true}, + Inx: t.Inx, + IPPreference: t.IPPreference, + ProbeTargetHost: t.ProbeTargetHost, + ProbeTargetPort: t.ProbeTargetPort, } err := tx.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "id"}}, DoUpdates: clause.AssignmentColumns([]string{ - "name", "traffic_ratio", "type", "protocol", "flow", "updated_time", "status", "in_ip", "inx", "ip_preference", + "name", "traffic_ratio", "type", "protocol", "flow", "updated_time", "status", "in_ip", "inx", "ip_preference", "probe_target_host", "probe_target_port", }), }).Create(&item).Error if err != nil { diff --git a/go-backend/internal/store/repo/repository_backup_test.go b/go-backend/internal/store/repo/repository_backup_test.go new file mode 100644 index 0000000..e205459 --- /dev/null +++ b/go-backend/internal/store/repo/repository_backup_test.go @@ -0,0 +1,59 @@ +package repo + +import ( + "path/filepath" + "testing" + "time" +) + +func TestBackupRoundTripsTunnelProbeTarget(t *testing.T) { + source, err := Open(filepath.Join(t.TempDir(), "source.db")) + if err != nil { + t.Fatalf("open source repo: %v", err) + } + defer source.Close() + + now := time.Now().UnixMilli() + if err := source.DB().Exec(` + INSERT INTO tunnel(id, name, traffic_ratio, type, protocol, flow, created_time, updated_time, status, in_ip, inx, probe_target_host, probe_target_port) + VALUES(20, 'backup-target', 1, 2, 'tls', 1, ?, ?, 1, '', 1, 'speed.example.com', 8443) + `, now, now).Error; err != nil { + t.Fatalf("insert source tunnel: %v", err) + } + + backup, err := source.ExportAll() + if err != nil { + t.Fatalf("export backup: %v", err) + } + if len(backup.Tunnels) != 1 { + t.Fatalf("expected one exported tunnel, got %d", len(backup.Tunnels)) + } + if backup.Tunnels[0].ProbeTargetHost != "speed.example.com" || backup.Tunnels[0].ProbeTargetPort != 8443 { + t.Fatalf("unexpected exported probe target: %+v", backup.Tunnels[0]) + } + + dest, err := Open(filepath.Join(t.TempDir(), "dest.db")) + if err != nil { + t.Fatalf("open dest repo: %v", err) + } + defer dest.Close() + + result, err := dest.Import(backup, []string{"tunnels"}) + if err != nil { + t.Fatalf("import backup: %v", err) + } + if result.TunnelsImported != 1 { + t.Fatalf("expected one imported tunnel, got %d", result.TunnelsImported) + } + + items, err := dest.ListTunnels() + if err != nil { + t.Fatalf("list imported tunnels: %v", err) + } + if len(items) != 1 { + t.Fatalf("expected one imported tunnel item, got %d", len(items)) + } + if items[0]["probeTargetHost"] != "speed.example.com" || items[0]["probeTargetPort"] != 8443 { + t.Fatalf("unexpected imported probe target: %+v", items[0]) + } +}