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 }