-
Notifications
You must be signed in to change notification settings - Fork 3
/
backup.go
146 lines (124 loc) · 3.4 KB
/
backup.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package entity
import (
"errors"
"fmt"
"html"
"regexp"
"strings"
"time"
)
// a regexp to parse backup filename
// from a path like .../host-prefix/MM-DD-YYYY-HH-mm/...
var remoteBackupFromPathRegexp = regexp.MustCompile(`(?P<Host>[^/]+)/(?P<date>\d\d-\d\d-\d\d\d\d-\d\d-\d\d)`)
// RemoteBackup a backup info in remote storage
type RemoteBackup struct {
Path string
HostPrefix string
DateCreated time.Time
Removed bool
RemoveError error
}
type RemoteBackupsByHost map[string][]RemoteBackup
// NewRemoteBackupFromPath parses a backup info from its path in remote storage
func NewRemoteBackupFromPath(path string) (RemoteBackup, error) {
matches := remoteBackupFromPathRegexp.FindStringSubmatch(path)
if len(matches) < 3 {
return RemoteBackup{}, errors.New("path is not a backup")
}
date, err := time.Parse(SnapshotTagDateFormat, matches[2])
if err != nil {
return RemoteBackup{}, err
}
return RemoteBackup{
Path: path,
HostPrefix: matches[1],
DateCreated: date,
}, nil
}
// IsExpired whether a backup has expired
func (r RemoteBackup) IsExpired(now time.Time, retention time.Duration) bool {
if retention.Seconds() < 1 {
// sanity check
return false
}
return r.DateCreated.Before(now.Add(-retention))
}
func (r RemoteBackup) String() string {
return r.Path
}
// BackupResult a result of running a backup on a single database node
type BackupResult struct {
Error error
DateStarted time.Time
Duration time.Duration
SnapshotTag string
Keyspaces []string
Uploaded bool
CleanupResult CleanupResult
}
// BackupResults a list of backup results on multiple database nodes
type BackupResults struct {
TotalNodes int
BackedUpNodes int
ByHost map[string]BackupResult
Error error
}
// Report creates a human-readable report about backup results
func (b BackupResults) Report() string {
lines := []string{
fmt.Sprintf("Total nodes: %d", b.TotalNodes),
fmt.Sprintf("Backed up nodes: %d", b.BackedUpNodes),
"",
}
lines = append(lines, "Details:")
for host, result := range b.ByHost {
lines = append(lines, fmt.Sprintf("%s", host))
if result.Error != nil {
lines = append(
lines,
"Error:",
fmt.Sprintf(
"%s",
html.EscapeString(result.Error.Error()),
),
)
}
lines = append(lines, fmt.Sprintf(
"Backup uploaded: %t",
result.Uploaded,
))
lines = append(lines, fmt.Sprintf(
"Duration: %s",
result.Duration.String(),
))
lines = append(lines, fmt.Sprintf(
"Snapshot tag: %s",
result.SnapshotTag,
))
lines = append(lines, fmt.Sprintf(
"Expired backups removed: %d",
len(result.CleanupResult.RemovedRemoteBackups),
))
if result.CleanupResult.RemoteError != nil {
lines = append(lines, fmt.Sprintf(
"error while removing expired backups: %s",
html.EscapeString(result.CleanupResult.RemoteError.Error()),
))
}
if result.CleanupResult.LocalError != nil {
lines = append(lines, fmt.Sprintf(
"error while removing a snapshot on database node: %s",
html.EscapeString(result.CleanupResult.LocalError.Error()),
))
}
lines = append(lines, "")
}
return strings.Join(lines, "\n")
}
type CleanupResult struct {
// an error that occurred while cleaning up files on a database node, if any
LocalError error
// an error that occurred while cleaning up a backup in remote storage, if any
RemoteError error
RemovedRemoteBackups []RemoteBackup
}