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  }