github.com/cloudberrydb/gpbackup@v1.0.3-0.20240118031043-5410fd45eed6/history/history_test.go (about) 1 package history_test 2 3 import ( 4 "errors" 5 "io/ioutil" 6 "os" 7 "testing" 8 "time" 9 10 "github.com/cloudberrydb/gp-common-go-libs/operating" 11 "github.com/cloudberrydb/gp-common-go-libs/structmatcher" 12 "github.com/cloudberrydb/gp-common-go-libs/testhelper" 13 "github.com/cloudberrydb/gpbackup/backup" 14 "github.com/cloudberrydb/gpbackup/filepath" 15 "github.com/cloudberrydb/gpbackup/history" 16 "github.com/cloudberrydb/gpbackup/report" 17 "github.com/cloudberrydb/gpbackup/utils" 18 "gopkg.in/yaml.v2" 19 20 . "github.com/onsi/ginkgo/v2" 21 . "github.com/onsi/gomega" 22 . "github.com/onsi/gomega/gbytes" 23 ) 24 25 var ( 26 testLogfile *Buffer 27 ) 28 29 func TestBackupHistory(t *testing.T) { 30 RegisterFailHandler(Fail) 31 RunSpecs(t, "History Suite") 32 } 33 34 var _ = BeforeSuite(func() { 35 _, _, testLogfile = testhelper.SetupTestLogger() 36 }) 37 38 var _ = Describe("backup/history tests", func() { 39 var testConfig1, testConfig2, testConfig3, testConfigSucceed, testConfigFailed history.BackupConfig 40 var historyFilePath = "/tmp/history_file.yaml" 41 42 BeforeEach(func() { 43 testConfig1 = history.BackupConfig{ 44 DatabaseName: "testdb1", 45 ExcludeRelations: []string{}, 46 ExcludeSchemas: []string{}, 47 IncludeRelations: []string{"testschema.testtable1", "testschema.testtable2"}, 48 IncludeSchemas: []string{}, 49 RestorePlan: []history.RestorePlanEntry{}, 50 Timestamp: "timestamp1", 51 } 52 testConfig2 = history.BackupConfig{ 53 DatabaseName: "testdb2", 54 ExcludeRelations: []string{}, 55 ExcludeSchemas: []string{"public"}, 56 IncludeRelations: []string{}, 57 IncludeSchemas: []string{}, 58 RestorePlan: []history.RestorePlanEntry{}, 59 Timestamp: "timestamp2", 60 } 61 testConfig3 = history.BackupConfig{ 62 DatabaseName: "testdb3", 63 ExcludeRelations: []string{}, 64 ExcludeSchemas: []string{"public"}, 65 IncludeRelations: []string{}, 66 IncludeSchemas: []string{}, 67 RestorePlan: []history.RestorePlanEntry{}, 68 Timestamp: "timestamp3", 69 } 70 testConfigSucceed = history.BackupConfig{ 71 DatabaseName: "testdb3", 72 ExcludeRelations: []string{}, 73 ExcludeSchemas: []string{"public"}, 74 IncludeRelations: []string{}, 75 IncludeSchemas: []string{}, 76 RestorePlan: []history.RestorePlanEntry{}, 77 Timestamp: "timestampSucceed", 78 Status: history.BackupStatusSucceed, 79 } 80 testConfigFailed = history.BackupConfig{ 81 DatabaseName: "testdb3", 82 ExcludeRelations: []string{}, 83 ExcludeSchemas: []string{"public"}, 84 IncludeRelations: []string{}, 85 IncludeSchemas: []string{}, 86 RestorePlan: []history.RestorePlanEntry{}, 87 Timestamp: "timestampFailed", 88 Status: history.BackupStatusFailed, 89 } 90 _ = os.Remove(historyFilePath) 91 }) 92 93 AfterEach(func() { 94 _ = os.Remove(historyFilePath) 95 }) 96 Describe("CurrentTimestamp", func() { 97 It("returns the current timestamp", func() { 98 operating.System.Now = func() time.Time { return time.Date(2017, time.January, 1, 1, 1, 1, 1, time.Local) } 99 expected := "20170101010101" 100 actual := history.CurrentTimestamp() 101 Expect(actual).To(Equal(expected)) 102 }) 103 }) 104 Describe("WriteToFileAndMakeReadOnly", func() { 105 var fileInfo os.FileInfo 106 var historyWithEntries history.History 107 BeforeEach(func() { 108 historyWithEntries = history.History{ 109 BackupConfigs: []history.BackupConfig{testConfig1, testConfig2}, 110 } 111 }) 112 AfterEach(func() { 113 _ = os.Remove(historyFilePath) 114 }) 115 It("makes the file readonly after it is written", func() { 116 err := historyWithEntries.WriteToFileAndMakeReadOnly(historyFilePath) 117 Expect(err).ToNot(HaveOccurred()) 118 119 fileInfo, err = os.Stat(historyFilePath) 120 Expect(err).ToNot(HaveOccurred()) 121 Expect(fileInfo.Mode().Perm()).To(Equal(os.FileMode(0444))) 122 }) 123 It("writes file when file does not exist", func() { 124 err := historyWithEntries.WriteToFileAndMakeReadOnly(historyFilePath) 125 Expect(err).ToNot(HaveOccurred()) 126 127 _, err = os.Stat(historyFilePath) 128 Expect(err).ToNot(HaveOccurred()) 129 }) 130 It("writes file when file exists and is writeable", func() { 131 err := ioutil.WriteFile(historyFilePath, []byte{}, 0644) 132 Expect(err).ToNot(HaveOccurred()) 133 134 err = historyWithEntries.WriteToFileAndMakeReadOnly(historyFilePath) 135 Expect(err).ToNot(HaveOccurred()) 136 137 fileHash, err := utils.GetFileHash(historyFilePath) 138 Expect(err).ToNot(HaveOccurred()) 139 140 resultHistory, historyHash, err := history.NewHistory(historyFilePath) 141 Expect(err).ToNot(HaveOccurred()) 142 Expect(historyWithEntries).To(structmatcher.MatchStruct(resultHistory)) 143 Expect(fileHash).To(Equal(historyHash)) 144 }) 145 It("writes file when file exists and is readonly ", func() { 146 err := ioutil.WriteFile(historyFilePath, []byte{}, 0444) 147 Expect(err).ToNot(HaveOccurred()) 148 149 err = historyWithEntries.WriteToFileAndMakeReadOnly(historyFilePath) 150 Expect(err).ToNot(HaveOccurred()) 151 152 fileHash, err := utils.GetFileHash(historyFilePath) 153 Expect(err).ToNot(HaveOccurred()) 154 155 156 resultHistory, historyHash, err := history.NewHistory(historyFilePath) 157 Expect(err).ToNot(HaveOccurred()) 158 Expect(historyWithEntries).To(structmatcher.MatchStruct(resultHistory)) 159 Expect(fileHash).To(Equal(historyHash)) 160 }) 161 }) 162 Describe("NewHistory", func() { 163 It("creates a history object with entries from the file when history file exists", func() { 164 historyWithEntries := history.History{ 165 BackupConfigs: []history.BackupConfig{testConfig1, testConfig2}, 166 } 167 historyFileContents, _ := yaml.Marshal(historyWithEntries) 168 fileHandle, err := utils.OpenFileForWrite(historyFilePath) 169 Expect(err).ToNot(HaveOccurred()) 170 _, err = fileHandle.Write(historyFileContents) 171 Expect(err).ToNot(HaveOccurred()) 172 err = fileHandle.Close() 173 Expect(err).ToNot(HaveOccurred()) 174 175 fileHash, err := utils.GetFileHash(historyFilePath) 176 Expect(err).ToNot(HaveOccurred()) 177 178 resultHistory, historyHash, err := history.NewHistory(historyFilePath) 179 Expect(err).ToNot(HaveOccurred()) 180 Expect(historyWithEntries).To(structmatcher.MatchStruct(resultHistory)) 181 Expect(fileHash).To(Equal(historyHash)) 182 }) 183 Context("fatals when", func() { 184 BeforeEach(func() { 185 operating.System.Stat = func(string) (os.FileInfo, error) { return nil, nil } 186 operating.System.OpenFileRead = func(string, int, os.FileMode) (operating.ReadCloserAt, error) { return nil, nil } 187 }) 188 AfterEach(func() { 189 operating.System = operating.InitializeSystemFunctions() 190 }) 191 It("gpbackup_history.yaml can't be read", func() { 192 operating.System.ReadFile = func(string) ([]byte, error) { return nil, errors.New("read error") } 193 194 _, _, err := history.NewHistory("/tempfile") 195 Expect(err).To(HaveOccurred()) 196 Expect(err.Error()).To(Equal("read error")) 197 }) 198 It("gpbackup_history.yaml is an invalid format", func() { 199 operating.System.ReadFile = func(string) ([]byte, error) { return []byte("not yaml"), nil } 200 201 _, _, err := history.NewHistory("/tempfile") 202 Expect(err).To(HaveOccurred()) 203 Expect(err.Error()).To(ContainSubstring("not yaml")) 204 }) 205 It("NewHistory returns an empty History", func() { 206 backup.SetFPInfo(filepath.FilePathInfo{UserSpecifiedBackupDir: "/tmp", UserSpecifiedSegPrefix: "/test-prefix"}) 207 backup.SetReport(&report.Report{}) 208 operating.System.ReadFile = func(string) ([]byte, error) { return []byte(""), nil } 209 210 contents, _, err := history.NewHistory("/tempfile") 211 Expect(err).ToNot(HaveOccurred()) 212 Expect(contents).To(Equal(&history.History{BackupConfigs: make([]history.BackupConfig, 0)})) 213 }) 214 }) 215 }) 216 Describe("AddBackupConfig", func() { 217 It("adds the most recent history entry and keeps the list sorted", func() { 218 testHistory := history.History{ 219 BackupConfigs: []history.BackupConfig{testConfig3, testConfig1}, 220 } 221 222 testHistory.AddBackupConfig(&testConfig2) 223 224 expectedHistory := history.History{ 225 BackupConfigs: []history.BackupConfig{testConfig3, testConfig2, testConfig1}, 226 } 227 Expect(expectedHistory).To(structmatcher.MatchStruct(testHistory)) 228 }) 229 }) 230 Describe("WriteBackupHistory", func() { 231 It("appends new config when file exists", func() { 232 Expect(testConfig3.EndTime).To(BeEmpty()) 233 simulatedEndTime := time.Now() 234 operating.System.Now = func() time.Time { 235 return simulatedEndTime 236 } 237 historyWithEntries := history.History{ 238 BackupConfigs: []history.BackupConfig{testConfig2, testConfig1}, 239 } 240 historyFileContents, _ := yaml.Marshal(historyWithEntries) 241 fileHandle, err := utils.OpenFileForWrite(historyFilePath) 242 Expect(err).ToNot(HaveOccurred()) 243 _, err = fileHandle.Write(historyFileContents) 244 Expect(err).ToNot(HaveOccurred()) 245 err = fileHandle.Close() 246 Expect(err).ToNot(HaveOccurred()) 247 err = history.WriteBackupHistory(historyFilePath, &testConfig3) 248 Expect(err).ToNot(HaveOccurred()) 249 250 fileHash, err := utils.GetFileHash(historyFilePath) 251 Expect(err).ToNot(HaveOccurred()) 252 253 resultHistory, historyHash, err := history.NewHistory(historyFilePath) 254 Expect(err).ToNot(HaveOccurred()) 255 Expect(fileHash).To(Equal(historyHash)) 256 257 testConfig3.EndTime = simulatedEndTime.Format("20060102150405") 258 expectedHistory := history.History{ 259 BackupConfigs: []history.BackupConfig{testConfig3, testConfig2, testConfig1}, 260 } 261 Expect(expectedHistory).To(structmatcher.MatchStruct(resultHistory)) 262 }) 263 It("writes file with new config when file does not exist", func() { 264 Expect(testConfig3.EndTime).To(BeEmpty()) 265 simulatedEndTime := time.Now() 266 operating.System.Now = func() time.Time { 267 return simulatedEndTime 268 } 269 err := history.WriteBackupHistory(historyFilePath, &testConfig3) 270 Expect(err).ToNot(HaveOccurred()) 271 272 fileHash, err := utils.GetFileHash(historyFilePath) 273 Expect(err).ToNot(HaveOccurred()) 274 275 resultHistory, historyHash, err := history.NewHistory(historyFilePath) 276 Expect(err).ToNot(HaveOccurred()) 277 Expect(fileHash).To(Equal(historyHash)) 278 279 expectedHistory := history.History{BackupConfigs: []history.BackupConfig{testConfig3}} 280 Expect(expectedHistory).To(structmatcher.MatchStruct(resultHistory)) 281 Expect(testLogfile).To(Say("No existing backups found. Creating new backup history file.")) 282 Expect(testConfig3.EndTime).To(Equal(simulatedEndTime.Format("20060102150405"))) 283 }) 284 }) 285 Describe("FindBackupConfig", func() { 286 var resultHistory *history.History 287 BeforeEach(func() { 288 err := history.WriteBackupHistory(historyFilePath, &testConfig1) 289 Expect(err).ToNot(HaveOccurred()) 290 resultHistory, _, err = history.NewHistory(historyFilePath) 291 Expect(err).ToNot(HaveOccurred()) 292 err = history.WriteBackupHistory(historyFilePath, &testConfig2) 293 Expect(err).ToNot(HaveOccurred()) 294 resultHistory, _, err = history.NewHistory(historyFilePath) 295 Expect(err).ToNot(HaveOccurred()) 296 err = history.WriteBackupHistory(historyFilePath, &testConfig3) 297 Expect(err).ToNot(HaveOccurred()) 298 err = history.WriteBackupHistory(historyFilePath, &testConfigSucceed) 299 Expect(err).ToNot(HaveOccurred()) 300 resultHistory, _, err = history.NewHistory(historyFilePath) 301 Expect(err).ToNot(HaveOccurred()) 302 err = history.WriteBackupHistory(historyFilePath, &testConfigFailed) 303 Expect(err).ToNot(HaveOccurred()) 304 resultHistory, _, err = history.NewHistory(historyFilePath) 305 Expect(err).ToNot(HaveOccurred()) 306 }) 307 It("finds a backup config for the given timestamp", func() { 308 foundConfig := resultHistory.FindBackupConfig("timestamp2") 309 Expect(foundConfig).To(Equal(&testConfig2)) 310 311 foundConfig = resultHistory.FindBackupConfig("timestampSucceed") 312 Expect(foundConfig).To(Equal(&testConfigSucceed)) 313 }) 314 It("returns nil when timestamp not found", func() { 315 foundConfig := resultHistory.FindBackupConfig("foo") 316 Expect(foundConfig).To(BeNil()) 317 }) 318 It("returns nil when timestamp is there but status is failed", func() { 319 foundConfig := resultHistory.FindBackupConfig("timestampFailed") 320 Expect(foundConfig).To(BeNil()) 321 }) 322 }) 323 })