github.com/tuhaihe/gpbackup@v1.0.3/integration/helper_test.go (about) 1 package integration 2 3 import ( 4 "bytes" 5 "compress/gzip" 6 "fmt" 7 "io/ioutil" 8 "math" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "strings" 13 "time" 14 15 "github.com/tuhaihe/gp-common-go-libs/operating" 16 "github.com/klauspost/compress/zstd" 17 "golang.org/x/sys/unix" 18 19 . "github.com/onsi/ginkgo/v2" 20 . "github.com/onsi/gomega" 21 ) 22 23 var ( 24 testDir = "/tmp/helper_test/20180101/20180101010101" 25 pluginDir = "/tmp/plugin_dest/20180101/20180101010101" 26 tocFile = fmt.Sprintf("%s/test_toc.yaml", testDir) 27 oidFile = fmt.Sprintf("%s/test_oids", testDir) 28 pipeFile = fmt.Sprintf("%s/test_pipe", testDir) 29 dataFileFullPath = filepath.Join(testDir, "test_data") 30 pluginBackupPath = filepath.Join(pluginDir, "test_data") 31 errorFile = fmt.Sprintf("%s_error", pipeFile) 32 pluginConfigPath = fmt.Sprintf("%s/src/github.com/tuhaihe/gpbackup/plugins/example_plugin_config.yaml", os.Getenv("GOPATH")) 33 ) 34 35 const ( 36 defaultData = "here is some data\n" 37 expectedData = `here is some data 38 here is some data 39 here is some data 40 ` 41 expectedTOC = `dataentries: 42 1: 43 startbyte: 0 44 endbyte: 18 45 2: 46 startbyte: 18 47 endbyte: 36 48 3: 49 startbyte: 36 50 endbyte: 54 51 ` 52 ) 53 54 func gpbackupHelper(helperPath string, args ...string) *exec.Cmd { 55 args = append([]string{"--toc-file", tocFile, "--oid-file", oidFile, "--pipe-file", pipeFile, "--content", "1", "--single-data-file"}, args...) 56 command := exec.Command(helperPath, args...) 57 err := command.Start() 58 Expect(err).ToNot(HaveOccurred()) 59 return command 60 } 61 62 func buildAndInstallBinaries() string { 63 _ = os.Chdir("..") 64 command := exec.Command("make", "build") 65 output, err := command.CombinedOutput() 66 if err != nil { 67 fmt.Printf("%s", output) 68 Fail(fmt.Sprintf("%v", err)) 69 } 70 _ = os.Chdir("integration") 71 binDir := fmt.Sprintf("%s/bin", operating.System.Getenv("GOPATH")) 72 return fmt.Sprintf("%s/gpbackup_helper", binDir) 73 } 74 75 var _ = Describe("gpbackup_helper end to end integration tests", func() { 76 BeforeEach(func() { 77 err := os.RemoveAll(testDir) 78 Expect(err).ToNot(HaveOccurred()) 79 err = os.MkdirAll(testDir, 0777) 80 Expect(err).ToNot(HaveOccurred()) 81 err = os.RemoveAll(pluginDir) 82 Expect(err).ToNot(HaveOccurred()) 83 err = os.MkdirAll(pluginDir, 0777) 84 Expect(err).ToNot(HaveOccurred()) 85 86 err = unix.Mkfifo(fmt.Sprintf("%s_%d", pipeFile, 1), 0777) 87 if err != nil { 88 Fail(fmt.Sprintf("%v", err)) 89 } 90 }) 91 Context("backup tests", func() { 92 BeforeEach(func() { 93 f, _ := os.Create(oidFile) 94 _, _ = f.WriteString("1\n2\n3\n") 95 }) 96 It("runs backup gpbackup_helper without compression", func() { 97 helperCmd := gpbackupHelper(gpbackupHelperPath, "--backup-agent", "--compression-level", "0", "--data-file", dataFileFullPath) 98 writeToPipes(defaultData) 99 err := helperCmd.Wait() 100 printHelperLogOnError(err) 101 Expect(err).ToNot(HaveOccurred()) 102 assertBackupArtifacts(false) 103 }) 104 It("runs backup gpbackup_helper with data exceeding pipe buffer size", func() { 105 helperCmd := gpbackupHelper(gpbackupHelperPath, "--backup-agent", "--compression-level", "0", "--data-file", dataFileFullPath) 106 writeToPipes(strings.Repeat("a", int(math.Pow(2, 17)))) 107 err := helperCmd.Wait() 108 printHelperLogOnError(err) 109 Expect(err).ToNot(HaveOccurred()) 110 }) 111 It("runs backup gpbackup_helper with gzip compression", func() { 112 helperCmd := gpbackupHelper(gpbackupHelperPath, "--backup-agent", "--compression-type", "gzip", "--compression-level", "1", "--data-file", dataFileFullPath+".gz") 113 writeToPipes(defaultData) 114 err := helperCmd.Wait() 115 printHelperLogOnError(err) 116 Expect(err).ToNot(HaveOccurred()) 117 assertBackupArtifactsWithCompression("gzip", false) 118 }) 119 It("runs backup gpbackup_helper with zstd compression", func() { 120 helperCmd := gpbackupHelper(gpbackupHelperPath, "--backup-agent", "--compression-type", "zstd", "--compression-level", "1", "--data-file", dataFileFullPath+".zst") 121 writeToPipes(defaultData) 122 err := helperCmd.Wait() 123 printHelperLogOnError(err) 124 Expect(err).ToNot(HaveOccurred()) 125 assertBackupArtifactsWithCompression("zstd", false) 126 }) 127 It("runs backup gpbackup_helper without compression with plugin", func() { 128 helperCmd := gpbackupHelper(gpbackupHelperPath, "--backup-agent", "--compression-level", "0", "--data-file", dataFileFullPath, "--plugin-config", pluginConfigPath) 129 writeToPipes(defaultData) 130 err := helperCmd.Wait() 131 printHelperLogOnError(err) 132 Expect(err).ToNot(HaveOccurred()) 133 assertBackupArtifacts(true) 134 }) 135 It("runs backup gpbackup_helper with gzip compression with plugin", func() { 136 helperCmd := gpbackupHelper(gpbackupHelperPath, "--backup-agent", "--compression-type", "gzip", "--compression-level", "1", "--data-file", dataFileFullPath+".gz", "--plugin-config", pluginConfigPath) 137 writeToPipes(defaultData) 138 err := helperCmd.Wait() 139 printHelperLogOnError(err) 140 Expect(err).ToNot(HaveOccurred()) 141 assertBackupArtifactsWithCompression("gzip", true) 142 }) 143 It("runs backup gpbackup_helper with zstd compression with plugin", func() { 144 helperCmd := gpbackupHelper(gpbackupHelperPath, "--backup-agent", "--compression-type", "zstd", "--compression-level", "1", "--data-file", dataFileFullPath+".zst", "--plugin-config", pluginConfigPath) 145 writeToPipes(defaultData) 146 err := helperCmd.Wait() 147 printHelperLogOnError(err) 148 Expect(err).ToNot(HaveOccurred()) 149 assertBackupArtifactsWithCompression("zstd", true) 150 }) 151 It("Generates error file when backup agent interrupted", func() { 152 helperCmd := gpbackupHelper(gpbackupHelperPath, "--backup-agent", "--compression-level", "0", "--data-file", dataFileFullPath) 153 waitForPipeCreation() 154 err := helperCmd.Process.Signal(unix.SIGINT) 155 Expect(err).ToNot(HaveOccurred()) 156 err = helperCmd.Wait() 157 Expect(err).To(HaveOccurred()) 158 assertErrorsHandled() 159 }) 160 }) 161 Context("restore tests", func() { 162 It("runs restore gpbackup_helper without compression", func() { 163 setupRestoreFiles("", false) 164 helperCmd := gpbackupHelper(gpbackupHelperPath, "--restore-agent", "--data-file", dataFileFullPath) 165 for _, i := range []int{1, 3} { 166 contents, _ := ioutil.ReadFile(fmt.Sprintf("%s_%d", pipeFile, i)) 167 Expect(string(contents)).To(Equal("here is some data\n")) 168 } 169 err := helperCmd.Wait() 170 printHelperLogOnError(err) 171 Expect(err).ToNot(HaveOccurred()) 172 assertNoErrors() 173 }) 174 It("runs restore gpbackup_helper with gzip compression", func() { 175 setupRestoreFiles("gzip", false) 176 helperCmd := gpbackupHelper(gpbackupHelperPath, "--restore-agent", "--data-file", dataFileFullPath+".gz") 177 for _, i := range []int{1, 3} { 178 contents, _ := ioutil.ReadFile(fmt.Sprintf("%s_%d", pipeFile, i)) 179 Expect(string(contents)).To(Equal("here is some data\n")) 180 } 181 err := helperCmd.Wait() 182 printHelperLogOnError(err) 183 Expect(err).ToNot(HaveOccurred()) 184 assertNoErrors() 185 }) 186 It("runs restore gpbackup_helper with zstd compression", func() { 187 setupRestoreFiles("zstd", false) 188 helperCmd := gpbackupHelper(gpbackupHelperPath, "--restore-agent", "--data-file", dataFileFullPath+".zst") 189 for _, i := range []int{1, 3} { 190 contents, _ := ioutil.ReadFile(fmt.Sprintf("%s_%d", pipeFile, i)) 191 Expect(string(contents)).To(Equal("here is some data\n")) 192 } 193 err := helperCmd.Wait() 194 printHelperLogOnError(err) 195 Expect(err).ToNot(HaveOccurred()) 196 assertNoErrors() 197 }) 198 It("runs restore gpbackup_helper without compression with plugin", func() { 199 setupRestoreFiles("", true) 200 helperCmd := gpbackupHelper(gpbackupHelperPath, "--restore-agent", "--data-file", dataFileFullPath, "--plugin-config", pluginConfigPath) 201 for _, i := range []int{1, 3} { 202 contents, _ := ioutil.ReadFile(fmt.Sprintf("%s_%d", pipeFile, i)) 203 Expect(string(contents)).To(Equal("here is some data\n")) 204 } 205 err := helperCmd.Wait() 206 printHelperLogOnError(err) 207 Expect(err).ToNot(HaveOccurred()) 208 assertNoErrors() 209 }) 210 It("runs restore gpbackup_helper with gzip compression with plugin", func() { 211 setupRestoreFiles("gzip", true) 212 helperCmd := gpbackupHelper(gpbackupHelperPath, "--restore-agent", "--data-file", dataFileFullPath+".gz", "--plugin-config", pluginConfigPath) 213 for _, i := range []int{1, 3} { 214 contents, _ := ioutil.ReadFile(fmt.Sprintf("%s_%d", pipeFile, i)) 215 Expect(string(contents)).To(Equal("here is some data\n")) 216 } 217 err := helperCmd.Wait() 218 printHelperLogOnError(err) 219 Expect(err).ToNot(HaveOccurred()) 220 assertNoErrors() 221 }) 222 It("runs restore gpbackup_helper with zstd compression with plugin", func() { 223 setupRestoreFiles("zstd", true) 224 helperCmd := gpbackupHelper(gpbackupHelperPath, "--restore-agent", "--data-file", dataFileFullPath+".zst", "--plugin-config", pluginConfigPath) 225 for _, i := range []int{1, 3} { 226 contents, _ := ioutil.ReadFile(fmt.Sprintf("%s_%d", pipeFile, i)) 227 Expect(string(contents)).To(Equal("here is some data\n")) 228 } 229 err := helperCmd.Wait() 230 printHelperLogOnError(err) 231 Expect(err).ToNot(HaveOccurred()) 232 assertNoErrors() 233 }) 234 It("Generates error file when restore agent interrupted", func() { 235 setupRestoreFiles("gzip", false) 236 helperCmd := gpbackupHelper(gpbackupHelperPath, "--restore-agent", "--data-file", dataFileFullPath+".gz", "--single-data-file") 237 waitForPipeCreation() 238 err := helperCmd.Process.Signal(unix.SIGINT) 239 Expect(err).ToNot(HaveOccurred()) 240 err = helperCmd.Wait() 241 Expect(err).To(HaveOccurred()) 242 assertErrorsHandled() 243 }) 244 It("Continues restore process when encountering an error with flag --on-error-continue", func() { 245 // Write data file 246 dataFile := dataFileFullPath 247 f, _ := os.Create(dataFile + ".gz") 248 gzipf := gzip.NewWriter(f) 249 // Named pipes can buffer, so we need to write more than the buffer size to trigger flush error 250 customData := "here is some data\n" 251 dataLength := 128*1024 + 1 252 customData += strings.Repeat("a", dataLength) 253 customData += "here is some data\n" 254 255 _, _ = gzipf.Write([]byte(customData)) 256 _ = gzipf.Close() 257 258 // Write oid file 259 fOid, _ := os.Create(oidFile) 260 _, _ = fOid.WriteString("1\n2\n3\n") 261 defer func() { 262 _ = os.Remove(oidFile) 263 }() 264 265 // Write custom TOC 266 customTOC := fmt.Sprintf(`dataentries: 267 1: 268 startbyte: 0 269 endbyte: 18 270 2: 271 startbyte: 18 272 endbyte: %[1]d 273 3: 274 startbyte: %[1]d 275 endbyte: %d 276 `, dataLength+18, dataLength+18+18) 277 fToc, _ := os.Create(tocFile) 278 _, _ = fToc.WriteString(customTOC) 279 defer func() { 280 _ = os.Remove(tocFile) 281 }() 282 283 helperCmd := gpbackupHelper(gpbackupHelperPath, "--restore-agent", "--data-file", dataFileFullPath+".gz", "--on-error-continue") 284 285 for k, v := range []int{1, 2, 3} { 286 currentPipe := fmt.Sprintf("%s_%d", pipeFile, v) 287 288 if k == 1 { 289 // Do not read from the pipe to cause data load error on the helper by interrupting the write. 290 file, errOpen := os.Open(currentPipe) 291 Expect(errOpen).ToNot(HaveOccurred()) 292 errClose := file.Close() 293 Expect(errClose).ToNot(HaveOccurred()) 294 } else { 295 contents, err := ioutil.ReadFile(currentPipe) 296 Expect(err).ToNot(HaveOccurred()) 297 Expect(string(contents)).To(Equal("here is some data\n")) 298 } 299 } 300 301 // Block here until gpbackup_helper finishes (cleaning up pipes) 302 _ = helperCmd.Wait() 303 for _, i := range []int{1, 2, 3} { 304 currentPipe := fmt.Sprintf("%s_%d", pipeFile, i) 305 Expect(currentPipe).ToNot(BeAnExistingFile()) 306 } 307 308 // Check that an error file was created 309 Expect(errorFile).To(BeAnExistingFile()) 310 }) 311 }) 312 }) 313 314 func setupRestoreFiles(compressionType string, withPlugin bool) { 315 dataFile := dataFileFullPath 316 if withPlugin { 317 dataFile = pluginBackupPath 318 } 319 320 f, _ := os.Create(oidFile) 321 _, _ = f.WriteString("1\n3\n") 322 323 if compressionType == "gzip" { 324 f, _ := os.Create(dataFile + ".gz") 325 defer f.Close() 326 gzipf := gzip.NewWriter(f) 327 defer gzipf.Close() 328 _, _ = gzipf.Write([]byte(expectedData)) 329 } else if compressionType == "zstd" { 330 f, _ := os.Create(dataFile + ".zst") 331 defer f.Close() 332 zstdf, _ := zstd.NewWriter(f) 333 defer zstdf.Close() 334 _, _ = zstdf.Write([]byte(expectedData)) 335 } else { 336 f, _ := os.Create(dataFile) 337 _, _ = f.WriteString(expectedData) 338 } 339 340 f, _ = os.Create(tocFile) 341 _, _ = f.WriteString(expectedTOC) 342 } 343 344 func assertNoErrors() { 345 Expect(errorFile).To(Not(BeARegularFile())) 346 pipes, err := filepath.Glob(pipeFile + "_[1-9]*") 347 Expect(err).ToNot(HaveOccurred()) 348 Expect(pipes).To(BeEmpty()) 349 } 350 351 func assertErrorsHandled() { 352 Expect(errorFile).To(BeARegularFile()) 353 pipes, err := filepath.Glob(pipeFile + "_[1-9]*") 354 Expect(err).ToNot(HaveOccurred()) 355 Expect(pipes).To(BeEmpty()) 356 } 357 358 func assertBackupArtifacts(withPlugin bool) { 359 var contents []byte 360 var err error 361 dataFile := dataFileFullPath 362 if withPlugin { 363 dataFile = pluginBackupPath 364 } 365 contents, err = ioutil.ReadFile(dataFile) 366 Expect(err).ToNot(HaveOccurred()) 367 Expect(string(contents)).To(Equal(expectedData)) 368 369 contents, err = ioutil.ReadFile(tocFile) 370 Expect(err).ToNot(HaveOccurred()) 371 Expect(string(contents)).To(Equal(expectedTOC)) 372 assertNoErrors() 373 } 374 375 func assertBackupArtifactsWithCompression(compressionType string, withPlugin bool) { 376 var contents []byte 377 var err error 378 379 dataFile := dataFileFullPath 380 if withPlugin { 381 dataFile = pluginBackupPath 382 } 383 384 if compressionType == "gzip" { 385 contents, err = ioutil.ReadFile(dataFile + ".gz") 386 } else if compressionType == "zstd" { 387 contents, err = ioutil.ReadFile(dataFile + ".zst") 388 } else { 389 Fail("unknown compression type " + compressionType) 390 } 391 Expect(err).ToNot(HaveOccurred()) 392 393 if compressionType == "gzip" { 394 r, _ := gzip.NewReader(bytes.NewReader(contents)) 395 contents, _ = ioutil.ReadAll(r) 396 } else if compressionType == "zstd" { 397 r, _ := zstd.NewReader(bytes.NewReader(contents)) 398 contents, _ = ioutil.ReadAll(r) 399 } else { 400 Fail("unknown compression type " + compressionType) 401 } 402 Expect(string(contents)).To(Equal(expectedData)) 403 404 contents, err = ioutil.ReadFile(tocFile) 405 Expect(err).ToNot(HaveOccurred()) 406 Expect(string(contents)).To(Equal(expectedTOC)) 407 408 assertNoErrors() 409 } 410 411 func printHelperLogOnError(helperErr error) { 412 if helperErr != nil { 413 homeDir := os.Getenv("HOME") 414 helperFiles, _ := filepath.Glob(filepath.Join(homeDir, "gpAdminLogs/gpbackup_helper_*")) 415 command := exec.Command("tail", "-n 20", helperFiles[len(helperFiles)-1]) 416 output, _ := command.CombinedOutput() 417 fmt.Println(string(output)) 418 } 419 } 420 421 func writeToPipes(data string) { 422 for i := 1; i <= 3; i++ { 423 currentPipe := fmt.Sprintf("%s_%d", pipeFile, i) 424 _, err := os.Stat(currentPipe) 425 if err != nil { 426 Fail(fmt.Sprintf("%v", err)) 427 } 428 f, _ := os.Create("/tmp/tmpdata.txt") 429 _, _ = f.WriteString(data) 430 output, err := exec.Command("bash", "-c", fmt.Sprintf("cat %s > %s", "/tmp/tmpdata.txt", currentPipe)).CombinedOutput() 431 _ = f.Close() 432 _ = os.Remove("/tmp/tmpdata.txt") 433 if err != nil { 434 fmt.Printf("%s", output) 435 Fail(fmt.Sprintf("%v", err)) 436 } 437 } 438 } 439 440 func waitForPipeCreation() { 441 // wait up to 5 seconds for two pipe files to have been created 442 tries := 0 443 for tries < 1000 { 444 pipes, err := filepath.Glob(pipeFile + "_[1-9]*") 445 Expect(err).ToNot(HaveOccurred()) 446 if len(pipes) > 1 { 447 return 448 } 449 450 tries += 1 451 time.Sleep(5 * time.Millisecond) 452 } 453 }