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  })