github.com/matrixorigin/matrixone@v1.2.0/pkg/backup/backup_test.go (about)

     1  // Copyright 2023 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //	http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package backup
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"path"
    21  	"sync"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/matrixorigin/matrixone/pkg/container/types"
    26  	"github.com/matrixorigin/matrixone/pkg/defines"
    27  	"github.com/matrixorigin/matrixone/pkg/fileservice"
    28  	"github.com/matrixorigin/matrixone/pkg/logservice"
    29  	pb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
    30  	"github.com/matrixorigin/matrixone/pkg/sql/parsers/tree"
    31  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/catalog"
    32  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/common"
    33  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/db/testutil"
    34  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/iface/handle"
    35  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/testutils"
    36  	"github.com/matrixorigin/matrixone/pkg/vm/engine/tae/testutils/config"
    37  	"github.com/panjf2000/ants/v2"
    38  	"github.com/prashantv/gostub"
    39  	"github.com/stretchr/testify/assert"
    40  )
    41  
    42  const (
    43  	ModuleName = "Backup"
    44  )
    45  
    46  func TestBackupData(t *testing.T) {
    47  	defer testutils.AfterTest(t)()
    48  	testutils.EnsureNoLeak(t)
    49  	ctx := context.Background()
    50  
    51  	opts := config.WithLongScanAndCKPOptsAndQuickGC(nil)
    52  	db := testutil.NewTestEngine(ctx, ModuleName, t, opts)
    53  	defer db.Close()
    54  	defer opts.Fs.Close()
    55  
    56  	schema := catalog.MockSchemaAll(13, 3)
    57  	schema.BlockMaxRows = 10
    58  	schema.ObjectMaxBlocks = 10
    59  	db.BindSchema(schema)
    60  	testutil.CreateRelation(t, db.DB, "db", schema, true)
    61  
    62  	totalRows := uint64(schema.BlockMaxRows * 30)
    63  	bat := catalog.MockBatch(schema, int(totalRows))
    64  	defer bat.Close()
    65  	bats := bat.Split(100)
    66  
    67  	var wg sync.WaitGroup
    68  	pool, _ := ants.NewPool(80)
    69  	defer pool.Release()
    70  
    71  	start := time.Now()
    72  	for _, data := range bats {
    73  		wg.Add(1)
    74  		err := pool.Submit(testutil.AppendClosure(t, data, schema.Name, db.DB, &wg))
    75  		assert.Nil(t, err)
    76  	}
    77  	wg.Wait()
    78  	t.Logf("Append %d rows takes: %s", totalRows, time.Since(start))
    79  	{
    80  		txn, rel := testutil.GetDefaultRelation(t, db.DB, schema.Name)
    81  		testutil.CheckAllColRowsByScan(t, rel, int(totalRows), false)
    82  		assert.NoError(t, txn.Commit(context.Background()))
    83  	}
    84  	t.Log(db.Catalog.SimplePPString(common.PPL1))
    85  	db.ForceLongCheckpoint()
    86  
    87  	dir := path.Join(db.Dir, "/local")
    88  	c := fileservice.Config{
    89  		Name:    defines.LocalFileServiceName,
    90  		Backend: "DISK",
    91  		DataDir: dir,
    92  	}
    93  	service, err := fileservice.NewFileService(ctx, c, nil)
    94  	assert.Nil(t, err)
    95  	defer service.Close()
    96  	for _, data := range bats {
    97  		txn, rel := db.GetRelation()
    98  		v := testutil.GetSingleSortKeyValue(data, schema, 2)
    99  		filter := handle.NewEQFilter(v)
   100  		err := rel.DeleteByFilter(context.Background(), filter)
   101  		assert.NoError(t, err)
   102  		assert.NoError(t, txn.Commit(context.Background()))
   103  	}
   104  	backupTime := time.Now().UTC()
   105  	currTs := types.BuildTS(backupTime.UnixNano(), 0)
   106  	locations := make([]string, 0)
   107  	locations = append(locations, backupTime.Format(time.DateTime))
   108  	location, err := db.ForceCheckpointForBackup(ctx, currTs, 20*time.Second)
   109  	assert.Nil(t, err)
   110  	db.BGCheckpointRunner.DisableCheckpoint()
   111  	locations = append(locations, location)
   112  	checkpoints := db.BGCheckpointRunner.GetAllCheckpoints()
   113  	files := make(map[string]string, 0)
   114  	for _, candidate := range checkpoints {
   115  		if files[candidate.GetLocation().Name().String()] == "" {
   116  			var loc string
   117  			loc = candidate.GetLocation().String()
   118  			loc += ":"
   119  			loc += fmt.Sprintf("%d", candidate.GetVersion())
   120  			files[candidate.GetLocation().Name().String()] = loc
   121  		}
   122  	}
   123  	for _, location := range files {
   124  		locations = append(locations, location)
   125  	}
   126  	err = execBackup(ctx, db.Opts.Fs, service, locations, 1, types.TS{}, "full")
   127  	assert.Nil(t, err)
   128  	db.Opts.Fs = service
   129  	db.Restart(ctx)
   130  	txn, rel := testutil.GetDefaultRelation(t, db.DB, schema.Name)
   131  	testutil.CheckAllColRowsByScan(t, rel, int(totalRows-100), true)
   132  	assert.NoError(t, txn.Commit(context.Background()))
   133  }
   134  
   135  func Test_saveTaeFilesList(t *testing.T) {
   136  	type args struct {
   137  		ctx        context.Context
   138  		Fs         fileservice.FileService
   139  		taeFiles   []*taeFile
   140  		backupTime string
   141  	}
   142  
   143  	Fs := getTestFs(t, true)
   144  	Fs2 := getTestFs(t, true)
   145  	ts := time.Now().Format(time.DateTime)
   146  	tests := []struct {
   147  		name    string
   148  		args    args
   149  		wantErr assert.ErrorAssertionFunc
   150  	}{
   151  		{
   152  			name: "t1",
   153  			args: args{
   154  				ctx:        context.Background(),
   155  				Fs:         Fs,
   156  				taeFiles:   nil,
   157  				backupTime: "",
   158  			},
   159  			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
   160  				assert.Error(t, err)
   161  				return true
   162  			},
   163  		},
   164  		{
   165  			name: "t2",
   166  			args: args{
   167  				ctx:        context.Background(),
   168  				Fs:         Fs,
   169  				taeFiles:   nil,
   170  				backupTime: ts,
   171  			},
   172  			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
   173  				assert.NoError(t, err)
   174  				//check file
   175  				check, err2 := readFileAndCheck(context.Background(), Fs, taeList)
   176  				assert.NoError(t, err2)
   177  				assert.Equal(t, check, []byte(""))
   178  				check, err2 = readFileAndCheck(context.Background(), Fs, taeSum)
   179  				assert.NoError(t, err2)
   180  				lines, err2 := fromCsvBytes(check)
   181  				assert.NoError(t, err2)
   182  				assert.Equal(t, lines[0][0], ts)
   183  				assert.Equal(t, lines[0][1], "0")
   184  				return false
   185  			},
   186  		},
   187  		{
   188  			name: "t3",
   189  			args: args{
   190  				ctx:        context.Background(),
   191  				Fs:         nil,
   192  				taeFiles:   nil,
   193  				backupTime: "",
   194  			},
   195  			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
   196  				assert.Error(t, err)
   197  				return true
   198  			},
   199  		},
   200  		{
   201  			name: "t4",
   202  			args: args{
   203  				ctx: context.Background(),
   204  				Fs:  Fs2,
   205  				taeFiles: []*taeFile{
   206  					{
   207  						path:     "t1",
   208  						size:     1,
   209  						checksum: []byte{1},
   210  					},
   211  				},
   212  				backupTime: ts,
   213  			},
   214  			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
   215  				assert.NoError(t, err)
   216  				//check file
   217  				check, err2 := readFileAndCheck(context.Background(), Fs2, taeList)
   218  				assert.NoError(t, err2)
   219  				lines, err2 := fromCsvBytes(check)
   220  				assert.NoError(t, err2)
   221  				assert.Equal(t, lines[0][0], "t1")
   222  				assert.Equal(t, lines[0][1], "1")
   223  				assert.Equal(t, lines[0][2], hexStr([]byte{1}))
   224  				check, err2 = readFileAndCheck(context.Background(), Fs2, taeSum)
   225  				assert.NoError(t, err2)
   226  				lines, err2 = fromCsvBytes(check)
   227  				assert.NoError(t, err2)
   228  				assert.Equal(t, lines[0][0], ts)
   229  				assert.Equal(t, lines[0][1], "1")
   230  				return false
   231  			},
   232  		},
   233  	}
   234  	for _, tt := range tests {
   235  		t.Run(tt.name, func(t *testing.T) {
   236  			tt.wantErr(t, saveTaeFilesList(tt.args.ctx, tt.args.Fs, tt.args.taeFiles, tt.args.backupTime, tt.args.backupTime, ""), fmt.Sprintf("saveTaeFilesList(%v, %v, %v, %v)", tt.args.ctx, tt.args.Fs, tt.args.taeFiles, tt.args.backupTime))
   237  		})
   238  	}
   239  }
   240  
   241  func Test_saveMetas(t *testing.T) {
   242  	type args struct {
   243  		ctx context.Context
   244  		cfg *Config
   245  	}
   246  
   247  	Fs := getTestFs(t, true)
   248  
   249  	tests := []struct {
   250  		name    string
   251  		args    args
   252  		wantErr assert.ErrorAssertionFunc
   253  	}{
   254  		{
   255  			name: "t1",
   256  			args: args{
   257  				ctx: context.Background(),
   258  				cfg: nil,
   259  			},
   260  			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
   261  				assert.Error(t, err)
   262  				return false
   263  			},
   264  		},
   265  		{
   266  			name: "t2",
   267  			args: args{
   268  				ctx: context.Background(),
   269  				cfg: &Config{
   270  					Timestamp:  types.TS{},
   271  					GeneralDir: Fs,
   272  					SharedFs:   nil,
   273  					TaeDir:     nil,
   274  					HAkeeper:   nil,
   275  					Metas: &Metas{
   276  						metas: []*Meta{
   277  							{
   278  								Typ:     TypeVersion,
   279  								Version: "version",
   280  							},
   281  							{
   282  								Typ:       TypeBuildinfo,
   283  								Buildinfo: "build_info",
   284  							},
   285  							{
   286  								Typ:              TypeLaunchconfig,
   287  								LaunchConfigFile: "launch_conf",
   288  							},
   289  							{
   290  								Typ:              TypeLaunchconfig,
   291  								SubTyp:           CnConfig,
   292  								LaunchConfigFile: "launch_cn_conf",
   293  							},
   294  						},
   295  					},
   296  				},
   297  			},
   298  			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
   299  				assert.NoError(t, err)
   300  				check, err2 := readFileAndCheck(context.Background(), Fs, moMeta)
   301  				assert.NoError(t, err2)
   302  				lines, err2 := fromCsvBytes(check)
   303  				assert.NoError(t, err2)
   304  				assert.Equal(t, lines[0][0], "version")
   305  				assert.Equal(t, lines[0][1], "version")
   306  				assert.Equal(t, lines[1][0], "buildinfo")
   307  				assert.Equal(t, lines[1][1], "build_info")
   308  				assert.Equal(t, lines[2][0], "launchconfig")
   309  				assert.Equal(t, lines[2][1], "")
   310  				assert.Equal(t, lines[2][2], "launch_conf")
   311  				assert.Equal(t, lines[3][0], "launchconfig")
   312  				assert.Equal(t, lines[3][1], CnConfig)
   313  				assert.Equal(t, lines[3][2], "launch_cn_conf")
   314  				return false
   315  			},
   316  		},
   317  	}
   318  	for _, tt := range tests {
   319  		t.Run(tt.name, func(t *testing.T) {
   320  			tt.wantErr(t, saveMetas(tt.args.ctx, tt.args.cfg), fmt.Sprintf("saveMetas(%v, %v)", tt.args.ctx, tt.args.cfg))
   321  		})
   322  	}
   323  }
   324  
   325  func Test_backupConfigFile(t *testing.T) {
   326  	type args struct {
   327  		ctx        context.Context
   328  		typ        string
   329  		configPath string
   330  		cfg        *Config
   331  	}
   332  
   333  	Fs := getTestFs(t, true)
   334  	file := getTempFile(t, "", "t1", "test_t1")
   335  
   336  	tests := []struct {
   337  		name    string
   338  		args    args
   339  		wantErr assert.ErrorAssertionFunc
   340  	}{
   341  		{
   342  			name: "t1",
   343  			args: args{
   344  				ctx:        context.Background(),
   345  				typ:        "",
   346  				configPath: "",
   347  				cfg:        nil,
   348  			},
   349  			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
   350  				assert.Error(t, err)
   351  				return true
   352  			},
   353  		},
   354  		{
   355  			name: "t2",
   356  			args: args{
   357  				ctx:        context.Background(),
   358  				typ:        CnConfig,
   359  				configPath: file.Name(),
   360  				cfg: &Config{
   361  					Timestamp:  types.TS{},
   362  					GeneralDir: Fs,
   363  					SharedFs:   nil,
   364  					TaeDir:     nil,
   365  					HAkeeper:   nil,
   366  					Metas:      &Metas{},
   367  				},
   368  			},
   369  			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
   370  				assert.NoError(t, err)
   371  				list, err2 := Fs.List(context.Background(), configDir)
   372  				assert.NoError(t, err2)
   373  				var configFile string
   374  				for _, entry := range list {
   375  					if entry.IsDir {
   376  						continue
   377  					}
   378  					configFile = entry.Name
   379  					break
   380  				}
   381  				check, err2 := readFileAndCheck(context.Background(), Fs, configDir+"/"+configFile)
   382  				assert.NoError(t, err2)
   383  				assert.Equal(t, check, []byte("test_t1"))
   384  				return true
   385  			},
   386  		},
   387  	}
   388  	for _, tt := range tests {
   389  		t.Run(tt.name, func(t *testing.T) {
   390  			tt.wantErr(t, backupConfigFile(tt.args.ctx, tt.args.typ, tt.args.configPath, tt.args.cfg), fmt.Sprintf("backupConfigFile(%v, %v, %v, %v)", tt.args.ctx, tt.args.typ, tt.args.configPath, tt.args.cfg))
   391  		})
   392  	}
   393  }
   394  
   395  var _ logservice.CNHAKeeperClient = new(dumpHakeeper)
   396  
   397  const (
   398  	backupData = "backup_data"
   399  )
   400  
   401  type dumpHakeeper struct {
   402  }
   403  
   404  func (d *dumpHakeeper) Close() error {
   405  	//TODO implement me
   406  	panic("implement me")
   407  }
   408  
   409  func (d *dumpHakeeper) AllocateID(ctx context.Context) (uint64, error) {
   410  	//TODO implement me
   411  	panic("implement me")
   412  }
   413  
   414  func (d *dumpHakeeper) AllocateIDByKey(ctx context.Context, key string) (uint64, error) {
   415  	//TODO implement me
   416  	panic("implement me")
   417  }
   418  
   419  func (d *dumpHakeeper) AllocateIDByKeyWithBatch(ctx context.Context, key string, batch uint64) (uint64, error) {
   420  	//TODO implement me
   421  	panic("implement me")
   422  }
   423  
   424  func (d *dumpHakeeper) GetClusterDetails(ctx context.Context) (pb.ClusterDetails, error) {
   425  	//TODO implement me
   426  	panic("implement me")
   427  }
   428  
   429  func (d *dumpHakeeper) GetClusterState(ctx context.Context) (pb.CheckerState, error) {
   430  	//TODO implement me
   431  	panic("implement me")
   432  }
   433  
   434  func (d *dumpHakeeper) GetBackupData(ctx context.Context) ([]byte, error) {
   435  	return []byte(backupData), nil
   436  }
   437  
   438  func (d *dumpHakeeper) SendCNHeartbeat(ctx context.Context, hb pb.CNStoreHeartbeat) (pb.CommandBatch, error) {
   439  	//TODO implement me
   440  	panic("implement me")
   441  }
   442  
   443  func Test_backupHakeeper(t *testing.T) {
   444  	type args struct {
   445  		ctx    context.Context
   446  		config *Config
   447  	}
   448  	etlFs := getTestFs(t, true)
   449  	taeFs := getTestFs(t, false)
   450  
   451  	tests := []struct {
   452  		name    string
   453  		args    args
   454  		wantErr assert.ErrorAssertionFunc
   455  	}{
   456  		{
   457  			name: "t1",
   458  			args: args{
   459  				ctx:    context.Background(),
   460  				config: nil,
   461  			},
   462  			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
   463  				assert.Error(t, err)
   464  				return false
   465  			},
   466  		},
   467  		{
   468  			name: "t2",
   469  			args: args{
   470  				ctx: context.Background(),
   471  				config: &Config{
   472  					Timestamp:  types.TS{},
   473  					GeneralDir: nil,
   474  					SharedFs:   nil,
   475  					TaeDir:     nil,
   476  					HAkeeper:   nil,
   477  					Metas:      &Metas{},
   478  				},
   479  			},
   480  			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
   481  				assert.Error(t, err)
   482  				return false
   483  			},
   484  		},
   485  		{
   486  			name: "t3",
   487  			args: args{
   488  				ctx: context.Background(),
   489  				config: &Config{
   490  					Timestamp:  types.TS{},
   491  					GeneralDir: etlFs,
   492  					SharedFs:   nil,
   493  					TaeDir:     etlFs,
   494  					HAkeeper:   &dumpHakeeper{},
   495  					Metas:      &Metas{},
   496  				},
   497  			},
   498  			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
   499  				assert.NoError(t, err)
   500  				check, err2 := readFileAndCheck(context.Background(), etlFs, hakeeperDir+"/"+HakeeperFile)
   501  				assert.NoError(t, err2)
   502  				assert.Equal(t, check, []byte(backupData))
   503  				return false
   504  			},
   505  		},
   506  		{
   507  			name: "t4",
   508  			args: args{
   509  				ctx: context.Background(),
   510  				config: &Config{
   511  					Timestamp:  types.TS{},
   512  					GeneralDir: etlFs,
   513  					SharedFs:   nil,
   514  					TaeDir:     taeFs,
   515  					HAkeeper:   &dumpHakeeper{},
   516  					Metas:      &Metas{},
   517  				},
   518  			},
   519  			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
   520  				assert.NoError(t, err)
   521  				check, err2 := readFileAndCheck(context.Background(), taeFs, hakeeperDir+"/"+HakeeperFile)
   522  				assert.NoError(t, err2)
   523  				assert.Equal(t, check, []byte(backupData))
   524  				return false
   525  			},
   526  		},
   527  	}
   528  	for _, tt := range tests {
   529  		t.Run(tt.name, func(t *testing.T) {
   530  			tt.wantErr(t, backupHakeeper(tt.args.ctx, tt.args.config), fmt.Sprintf("backupHakeeper(%v, %v)", tt.args.ctx, tt.args.config))
   531  		})
   532  	}
   533  }
   534  
   535  func TestBackup(t *testing.T) {
   536  	type args struct {
   537  		ctx context.Context
   538  		bs  *tree.BackupStart
   539  		cfg *Config
   540  	}
   541  
   542  	stubs := gostub.StubFunc(&backupTae, nil)
   543  	defer stubs.Reset()
   544  
   545  	tDir := getTempDir(t, "test")
   546  	tf1 := getTempFile(t, "", "t1", "test_t1")
   547  
   548  	bs := &tree.BackupStart{
   549  		IsS3:        false,
   550  		Dir:         tDir,
   551  		Parallelism: "10",
   552  	}
   553  
   554  	//backup configs
   555  	SaveLaunchConfigPath(CnConfig, []string{tf1.Name()})
   556  
   557  	tests := []struct {
   558  		name    string
   559  		args    args
   560  		wantErr assert.ErrorAssertionFunc
   561  	}{
   562  		{
   563  			name: "t1",
   564  			args: args{
   565  				ctx: nil,
   566  				bs:  bs,
   567  				cfg: nil,
   568  			},
   569  			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
   570  				assert.Error(t, err)
   571  				return false
   572  			},
   573  		},
   574  		{
   575  			name: "t2",
   576  			args: args{
   577  				ctx: context.Background(),
   578  				bs:  bs,
   579  				cfg: &Config{
   580  					Timestamp:  types.TS{},
   581  					GeneralDir: nil,
   582  					SharedFs:   nil,
   583  					TaeDir:     nil,
   584  					HAkeeper:   &dumpHakeeper{},
   585  					Metas:      NewMetas(),
   586  				},
   587  			},
   588  			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
   589  				cfg := i[0].(*Config)
   590  				assert.NoError(t, err)
   591  				assert.NotNil(t, cfg)
   592  
   593  				//checkup config files
   594  				list, err2 := cfg.GeneralDir.List(context.Background(), configDir)
   595  				assert.NoError(t, err2)
   596  				var configFile string
   597  				for _, entry := range list {
   598  					if entry.IsDir {
   599  						continue
   600  					}
   601  					configFile = entry.Name
   602  					break
   603  				}
   604  				check, err2 := readFileAndCheck(context.Background(), cfg.GeneralDir, configDir+"/"+configFile)
   605  				assert.NoError(t, err2)
   606  				assert.Equal(t, check, []byte("test_t1"))
   607  
   608  				//check hakeeper files
   609  				check, err2 = readFileAndCheck(context.Background(), cfg.TaeDir, hakeeperDir+"/"+HakeeperFile)
   610  				assert.NoError(t, err2)
   611  				assert.Equal(t, check, []byte(backupData))
   612  
   613  				//check metas
   614  				check, err2 = readFileAndCheck(context.Background(), cfg.GeneralDir, moMeta)
   615  				assert.NoError(t, err2)
   616  				lines, err2 := fromCsvBytes(check)
   617  				assert.NoError(t, err2)
   618  				assert.Equal(t, lines[0][0], "version")
   619  				assert.Equal(t, lines[0][1], Version)
   620  				assert.Equal(t, lines[1][0], "buildinfo")
   621  				assert.Equal(t, lines[1][1], buildInfo())
   622  				assert.Equal(t, lines[2][0], "launchconfig")
   623  				assert.Equal(t, lines[2][1], CnConfig)
   624  				assert.Equal(t, lines[2][2], cfg.Metas.metas[2].LaunchConfigFile)
   625  				return false
   626  			},
   627  		},
   628  	}
   629  	for _, tt := range tests {
   630  		t.Run(tt.name, func(t *testing.T) {
   631  			tt.wantErr(t, Backup(tt.args.ctx, tt.args.bs, tt.args.cfg), tt.args.cfg, fmt.Sprintf("Backup(%v, %v, %v)", tt.args.ctx, tt.args.bs, tt.args.cfg))
   632  		})
   633  	}
   634  }