github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/cdc/redo/writer/file/file_test.go (about)

     1  //  Copyright 2023 PingCAP, Inc.
     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  //  See the License for the specific language governing permissions and
    12  //  limitations under the License.
    13  
    14  package file
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"os"
    20  	"path/filepath"
    21  	"testing"
    22  
    23  	backuppb "github.com/pingcap/kvproto/pkg/brpb"
    24  	mockstorage "github.com/pingcap/tidb/br/pkg/mock/storage"
    25  	"github.com/pingcap/tidb/br/pkg/storage"
    26  	"github.com/pingcap/tiflow/cdc/model"
    27  	"github.com/pingcap/tiflow/cdc/redo/common"
    28  	"github.com/pingcap/tiflow/cdc/redo/writer"
    29  	"github.com/pingcap/tiflow/pkg/fsutil"
    30  	"github.com/pingcap/tiflow/pkg/redo"
    31  	"github.com/pingcap/tiflow/pkg/uuid"
    32  	"github.com/stretchr/testify/require"
    33  	"github.com/uber-go/atomic"
    34  	"go.uber.org/mock/gomock"
    35  )
    36  
    37  func TestWriterWrite(t *testing.T) {
    38  	t.Parallel()
    39  
    40  	dir := t.TempDir()
    41  	cfs := []model.ChangeFeedID{
    42  		model.DefaultChangeFeedID("test-cf"),
    43  		{
    44  			Namespace: "abcd",
    45  			ID:        "test-cf",
    46  		},
    47  	}
    48  
    49  	cf11s := []model.ChangeFeedID{
    50  		model.DefaultChangeFeedID("test-cf11"),
    51  		{
    52  			Namespace: "abcd",
    53  			ID:        "test-cf11",
    54  		},
    55  	}
    56  
    57  	for idx, cf := range cfs {
    58  		uuidGen := uuid.NewConstGenerator("const-uuid")
    59  		w := &Writer{
    60  			cfg: &writer.LogWriterConfig{
    61  				MaxLogSizeInBytes: 10,
    62  				Dir:               dir,
    63  				ChangeFeedID:      cf,
    64  				CaptureID:         "cp",
    65  				LogType:           redo.RedoRowLogFileType,
    66  			},
    67  			uint64buf: make([]byte, 8),
    68  			running:   *atomic.NewBool(true),
    69  			metricWriteBytes: common.RedoWriteBytesGauge.
    70  				WithLabelValues("default", "test-cf"),
    71  			metricFsyncDuration: common.RedoFsyncDurationHistogram.
    72  				WithLabelValues("default", "test-cf"),
    73  			metricFlushAllDuration: common.RedoFlushAllDurationHistogram.
    74  				WithLabelValues("default", "test-cf"),
    75  			uuidGenerator: uuidGen,
    76  		}
    77  
    78  		w.eventCommitTS.Store(1)
    79  		_, err := w.Write([]byte("tes1t11111"))
    80  		require.Nil(t, err)
    81  		var fileName string
    82  		// create a .tmp file
    83  		if w.cfg.ChangeFeedID.Namespace == model.DefaultNamespace {
    84  			fileName = fmt.Sprintf(redo.RedoLogFileFormatV1, w.cfg.CaptureID,
    85  				w.cfg.ChangeFeedID.ID,
    86  				w.cfg.LogType, 1, uuidGen.NewString(), redo.LogEXT) + redo.TmpEXT
    87  		} else {
    88  			fileName = fmt.Sprintf(redo.RedoLogFileFormatV2, w.cfg.CaptureID,
    89  				w.cfg.ChangeFeedID.Namespace, w.cfg.ChangeFeedID.ID,
    90  				w.cfg.LogType, 1, uuidGen.NewString(), redo.LogEXT) + redo.TmpEXT
    91  		}
    92  		path := filepath.Join(w.cfg.Dir, fileName)
    93  		info, err := os.Stat(path)
    94  		require.Nil(t, err)
    95  		require.Equal(t, fileName, info.Name())
    96  
    97  		w.eventCommitTS.Store(12)
    98  		_, err = w.Write([]byte("tt"))
    99  		require.Nil(t, err)
   100  		w.eventCommitTS.Store(22)
   101  		_, err = w.Write([]byte("t"))
   102  		require.Nil(t, err)
   103  
   104  		// after rotate, rename to .log
   105  		if w.cfg.ChangeFeedID.Namespace == model.DefaultNamespace {
   106  			fileName = fmt.Sprintf(redo.RedoLogFileFormatV1, w.cfg.CaptureID,
   107  				w.cfg.ChangeFeedID.ID,
   108  				w.cfg.LogType, 1, uuidGen.NewString(), redo.LogEXT)
   109  		} else {
   110  			fileName = fmt.Sprintf(redo.RedoLogFileFormatV2, w.cfg.CaptureID,
   111  				w.cfg.ChangeFeedID.Namespace, w.cfg.ChangeFeedID.ID,
   112  				w.cfg.LogType, 1, uuidGen.NewString(), redo.LogEXT)
   113  		}
   114  		path = filepath.Join(w.cfg.Dir, fileName)
   115  		info, err = os.Stat(path)
   116  		require.Nil(t, err)
   117  		require.Equal(t, fileName, info.Name())
   118  		// create a .tmp file with first eventCommitTS as name
   119  		if w.cfg.ChangeFeedID.Namespace == model.DefaultNamespace {
   120  			fileName = fmt.Sprintf(redo.RedoLogFileFormatV1, w.cfg.CaptureID,
   121  				w.cfg.ChangeFeedID.ID,
   122  				w.cfg.LogType, 12, uuidGen.NewString(), redo.LogEXT) + redo.TmpEXT
   123  		} else {
   124  			fileName = fmt.Sprintf(redo.RedoLogFileFormatV2, w.cfg.CaptureID,
   125  				w.cfg.ChangeFeedID.Namespace, w.cfg.ChangeFeedID.ID,
   126  				w.cfg.LogType, 12, uuidGen.NewString(), redo.LogEXT) + redo.TmpEXT
   127  		}
   128  		path = filepath.Join(w.cfg.Dir, fileName)
   129  		info, err = os.Stat(path)
   130  		require.Nil(t, err)
   131  		require.Equal(t, fileName, info.Name())
   132  		err = w.Close()
   133  		require.Nil(t, err)
   134  		require.False(t, w.IsRunning())
   135  		// safe close, rename to .log with max eventCommitTS as name
   136  		if w.cfg.ChangeFeedID.Namespace == model.DefaultNamespace {
   137  			fileName = fmt.Sprintf(redo.RedoLogFileFormatV1, w.cfg.CaptureID,
   138  				w.cfg.ChangeFeedID.ID,
   139  				w.cfg.LogType, 22, uuidGen.NewString(), redo.LogEXT)
   140  		} else {
   141  			fileName = fmt.Sprintf(redo.RedoLogFileFormatV2, w.cfg.CaptureID,
   142  				w.cfg.ChangeFeedID.Namespace, w.cfg.ChangeFeedID.ID,
   143  				w.cfg.LogType, 22, uuidGen.NewString(), redo.LogEXT)
   144  		}
   145  		path = filepath.Join(w.cfg.Dir, fileName)
   146  		info, err = os.Stat(path)
   147  		require.Nil(t, err)
   148  		require.Equal(t, fileName, info.Name())
   149  
   150  		w1 := &Writer{
   151  			cfg: &writer.LogWriterConfig{
   152  				MaxLogSizeInBytes: 10,
   153  				Dir:               dir,
   154  				ChangeFeedID:      cf11s[idx],
   155  				CaptureID:         "cp",
   156  				LogType:           redo.RedoRowLogFileType,
   157  			},
   158  			uint64buf: make([]byte, 8),
   159  			running:   *atomic.NewBool(true),
   160  			metricWriteBytes: common.RedoWriteBytesGauge.
   161  				WithLabelValues("default", "test-cf11"),
   162  			metricFsyncDuration: common.RedoFsyncDurationHistogram.
   163  				WithLabelValues("default", "test-cf11"),
   164  			metricFlushAllDuration: common.RedoFlushAllDurationHistogram.
   165  				WithLabelValues("default", "test-cf11"),
   166  			uuidGenerator: uuidGen,
   167  		}
   168  
   169  		w1.eventCommitTS.Store(1)
   170  		_, err = w1.Write([]byte("tes1t11111"))
   171  		require.Nil(t, err)
   172  		// create a .tmp file
   173  		if w1.cfg.ChangeFeedID.Namespace == model.DefaultNamespace {
   174  			fileName = fmt.Sprintf(redo.RedoLogFileFormatV1, w1.cfg.CaptureID,
   175  				w1.cfg.ChangeFeedID.ID,
   176  				w1.cfg.LogType, 1, uuidGen.NewString(), redo.LogEXT) + redo.TmpEXT
   177  		} else {
   178  			fileName = fmt.Sprintf(redo.RedoLogFileFormatV2, w1.cfg.CaptureID,
   179  				w1.cfg.ChangeFeedID.Namespace, w1.cfg.ChangeFeedID.ID,
   180  				w1.cfg.LogType, 1, uuidGen.NewString(), redo.LogEXT) + redo.TmpEXT
   181  		}
   182  		path = filepath.Join(w1.cfg.Dir, fileName)
   183  		info, err = os.Stat(path)
   184  		require.Nil(t, err)
   185  		require.Equal(t, fileName, info.Name())
   186  		// change the file name, should cause CLose err
   187  		err = os.Rename(path, path+"new")
   188  		require.Nil(t, err)
   189  		err = w1.Close()
   190  		require.NotNil(t, err)
   191  		// closed anyway
   192  		require.False(t, w1.IsRunning())
   193  	}
   194  }
   195  
   196  func TestAdvanceTs(t *testing.T) {
   197  	t.Parallel()
   198  
   199  	w := &Writer{}
   200  	w.AdvanceTs(111)
   201  	require.EqualValues(t, 111, w.eventCommitTS.Load())
   202  }
   203  
   204  func TestNewFileWriter(t *testing.T) {
   205  	t.Parallel()
   206  
   207  	_, err := NewFileWriter(context.Background(), nil)
   208  	require.NotNil(t, err)
   209  
   210  	storageDir := t.TempDir()
   211  	dir := t.TempDir()
   212  
   213  	uuidGen := uuid.NewConstGenerator("const-uuid")
   214  	w, err := NewFileWriter(context.Background(), &writer.LogWriterConfig{
   215  		Dir:                "sdfsf",
   216  		UseExternalStorage: false,
   217  	},
   218  		writer.WithUUIDGenerator(func() uuid.Generator { return uuidGen }),
   219  	)
   220  	require.Nil(t, err)
   221  	backend := &backuppb.StorageBackend{
   222  		Backend: &backuppb.StorageBackend_Local{Local: &backuppb.Local{Path: storageDir}},
   223  	}
   224  	localStorage, err := storage.New(context.Background(), backend, &storage.ExternalStorageOptions{
   225  		SendCredentials: false,
   226  		HTTPClient:      nil,
   227  	})
   228  	w.storage = localStorage
   229  	require.Nil(t, err)
   230  	err = w.Close()
   231  	require.Nil(t, err)
   232  	require.False(t, w.IsRunning())
   233  
   234  	controller := gomock.NewController(t)
   235  	mockStorage := mockstorage.NewMockExternalStorage(controller)
   236  	mockStorage.EXPECT().WriteFile(gomock.Any(), "cp_abcd_test_ddl_0_const-uuid.log",
   237  		gomock.Any()).Return(nil).Times(1)
   238  
   239  	changefeed := model.ChangeFeedID{
   240  		Namespace: "abcd",
   241  		ID:        "test",
   242  	}
   243  	w = &Writer{
   244  		cfg: &writer.LogWriterConfig{
   245  			Dir:          dir,
   246  			CaptureID:    "cp",
   247  			ChangeFeedID: changefeed,
   248  			LogType:      redo.RedoDDLLogFileType,
   249  
   250  			UseExternalStorage: true,
   251  			MaxLogSizeInBytes:  redo.DefaultMaxLogSize * redo.Megabyte,
   252  		},
   253  		uint64buf: make([]byte, 8),
   254  		storage:   mockStorage,
   255  		metricWriteBytes: common.RedoWriteBytesGauge.
   256  			WithLabelValues("default", "test"),
   257  		metricFsyncDuration: common.RedoFsyncDurationHistogram.
   258  			WithLabelValues("default", "test"),
   259  		metricFlushAllDuration: common.RedoFlushAllDurationHistogram.
   260  			WithLabelValues("default", "test"),
   261  		uuidGenerator: uuidGen,
   262  	}
   263  	w.running.Store(true)
   264  	_, err = w.Write([]byte("test"))
   265  	require.Nil(t, err)
   266  	err = w.Flush()
   267  	require.Nil(t, err)
   268  
   269  	err = w.Close()
   270  	require.Nil(t, err)
   271  	require.Equal(t, w.running.Load(), false)
   272  }
   273  
   274  func TestRotateFileWithFileAllocator(t *testing.T) {
   275  	t.Parallel()
   276  
   277  	ctx, cancel := context.WithCancel(context.Background())
   278  	defer cancel()
   279  	_, err := NewFileWriter(ctx, nil)
   280  	require.NotNil(t, err)
   281  
   282  	controller := gomock.NewController(t)
   283  	mockStorage := mockstorage.NewMockExternalStorage(controller)
   284  
   285  	mockStorage.EXPECT().WriteFile(gomock.Any(), "cp_abcd_test_row_0_uuid-1.log",
   286  		gomock.Any()).Return(nil).Times(1)
   287  	mockStorage.EXPECT().WriteFile(gomock.Any(), "cp_abcd_test_row_100_uuid-2.log",
   288  		gomock.Any()).Return(nil).Times(1)
   289  
   290  	dir := t.TempDir()
   291  	uuidGen := uuid.NewMock()
   292  	uuidGen.Push("uuid-1")
   293  	uuidGen.Push("uuid-2")
   294  	uuidGen.Push("uuid-3")
   295  	uuidGen.Push("uuid-4")
   296  	uuidGen.Push("uuid-5")
   297  	changefeed := model.ChangeFeedID{
   298  		Namespace: "abcd",
   299  		ID:        "test",
   300  	}
   301  	w := &Writer{
   302  		cfg: &writer.LogWriterConfig{
   303  			Dir:          dir,
   304  			CaptureID:    "cp",
   305  			ChangeFeedID: changefeed,
   306  			LogType:      redo.RedoRowLogFileType,
   307  
   308  			UseExternalStorage: true,
   309  			MaxLogSizeInBytes:  redo.DefaultMaxLogSize * redo.Megabyte,
   310  		},
   311  		uint64buf: make([]byte, 8),
   312  		metricWriteBytes: common.RedoWriteBytesGauge.
   313  			WithLabelValues("default", "test"),
   314  		metricFsyncDuration: common.RedoFsyncDurationHistogram.
   315  			WithLabelValues("default", "test"),
   316  		metricFlushAllDuration: common.RedoFlushAllDurationHistogram.
   317  			WithLabelValues("default", "test"),
   318  		storage:       mockStorage,
   319  		uuidGenerator: uuidGen,
   320  	}
   321  	w.allocator = fsutil.NewFileAllocator(
   322  		w.cfg.Dir, redo.RedoRowLogFileType, redo.DefaultMaxLogSize*redo.Megabyte)
   323  
   324  	w.running.Store(true)
   325  	_, err = w.Write([]byte("test"))
   326  	require.Nil(t, err)
   327  
   328  	err = w.rotate()
   329  	require.Nil(t, err)
   330  
   331  	w.AdvanceTs(100)
   332  	_, err = w.Write([]byte("test"))
   333  	require.Nil(t, err)
   334  	err = w.rotate()
   335  	require.Nil(t, err)
   336  
   337  	w.Close()
   338  }
   339  
   340  func TestRotateFileWithoutFileAllocator(t *testing.T) {
   341  	t.Parallel()
   342  
   343  	ctx, cancel := context.WithCancel(context.Background())
   344  	defer cancel()
   345  	_, err := NewFileWriter(ctx, nil)
   346  	require.NotNil(t, err)
   347  
   348  	controller := gomock.NewController(t)
   349  	mockStorage := mockstorage.NewMockExternalStorage(controller)
   350  
   351  	mockStorage.EXPECT().WriteFile(gomock.Any(), "cp_abcd_test_ddl_0_uuid-2.log",
   352  		gomock.Any()).Return(nil).Times(1)
   353  	mockStorage.EXPECT().WriteFile(gomock.Any(), "cp_abcd_test_ddl_100_uuid-4.log",
   354  		gomock.Any()).Return(nil).Times(1)
   355  
   356  	dir := t.TempDir()
   357  	uuidGen := uuid.NewMock()
   358  	uuidGen.Push("uuid-1")
   359  	uuidGen.Push("uuid-2")
   360  	uuidGen.Push("uuid-3")
   361  	uuidGen.Push("uuid-4")
   362  	uuidGen.Push("uuid-5")
   363  	uuidGen.Push("uuid-6")
   364  	changefeed := model.ChangeFeedID{
   365  		Namespace: "abcd",
   366  		ID:        "test",
   367  	}
   368  	w := &Writer{
   369  		cfg: &writer.LogWriterConfig{
   370  			Dir:          dir,
   371  			CaptureID:    "cp",
   372  			ChangeFeedID: changefeed,
   373  			LogType:      redo.RedoDDLLogFileType,
   374  
   375  			UseExternalStorage: true,
   376  			MaxLogSizeInBytes:  redo.DefaultMaxLogSize * redo.Megabyte,
   377  		},
   378  		uint64buf: make([]byte, 8),
   379  		metricWriteBytes: common.RedoWriteBytesGauge.
   380  			WithLabelValues("default", "test"),
   381  		metricFsyncDuration: common.RedoFsyncDurationHistogram.
   382  			WithLabelValues("default", "test"),
   383  		metricFlushAllDuration: common.RedoFlushAllDurationHistogram.
   384  			WithLabelValues("default", "test"),
   385  		storage:       mockStorage,
   386  		uuidGenerator: uuidGen,
   387  	}
   388  	w.running.Store(true)
   389  	_, err = w.Write([]byte("test"))
   390  	require.Nil(t, err)
   391  
   392  	err = w.rotate()
   393  	require.Nil(t, err)
   394  
   395  	w.AdvanceTs(100)
   396  	_, err = w.Write([]byte("test"))
   397  	require.Nil(t, err)
   398  	err = w.rotate()
   399  	require.Nil(t, err)
   400  
   401  	w.Close()
   402  }