github.com/uber/kraken@v0.1.4/lib/store/base/file_op_test.go (about) 1 // Copyright (c) 2016-2019 Uber Technologies, 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 // 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 package base 15 16 import ( 17 "io/ioutil" 18 "log" 19 "os" 20 "path/filepath" 21 "reflect" 22 "runtime" 23 "strings" 24 "sync" 25 "sync/atomic" 26 "testing" 27 28 "github.com/uber/kraken/core" 29 "github.com/stretchr/testify/require" 30 ) 31 32 // These tests should pass for all FileStore/FileOp implementations 33 func TestFileOp(t *testing.T) { 34 stores := []struct { 35 name string 36 fixture func() (storeBundle *fileStoreTestBundle, cleanup func()) 37 }{ 38 {"LocalFileStoreDefault", fileStoreDefaultFixture}, 39 {"LocalFileStoreCAS", fileStoreCASFixture}, 40 {"LocalFileStoreLRU", func() (storeBundle *fileStoreTestBundle, cleanup func()) { 41 return fileStoreLRUFixture(2) 42 }}, 43 } 44 45 tests := []func(require *require.Assertions, storeBundle *fileStoreTestBundle){ 46 testCreateFile, 47 testCreateFileFail, 48 testReloadFileEntry, 49 testMoveFile, 50 testLinkFileTo, 51 testDeleteFile, 52 testGetFileReader, 53 testGetFileReadWriter, 54 testGetOrSetFileMetadataConcurrently, 55 testSetFileMetadataAtConcurrently, 56 testDeleteFileMetadata, 57 } 58 59 for _, store := range stores { 60 t.Run(store.name, func(t *testing.T) { 61 for _, test := range tests { 62 testName := runtime.FuncForPC(reflect.ValueOf(test).Pointer()).Name() 63 t.Run(testName, func(t *testing.T) { 64 require := require.New(t) 65 s, cleanup := store.fixture() 66 defer cleanup() 67 test(require, s) 68 }) 69 } 70 }) 71 } 72 } 73 74 func testCreateFile(require *require.Assertions, storeBundle *fileStoreTestBundle) { 75 store := storeBundle.store 76 77 fn := core.DigestFixture().Hex() 78 s1 := storeBundle.state1 79 s2 := storeBundle.state2 80 81 var wg sync.WaitGroup 82 var successCount, existsErrorCount uint32 83 for i := 0; i < 100; i++ { 84 wg.Add(1) 85 go func() { 86 defer wg.Done() 87 // Create empty file. 88 if err := store.NewFileOp().AcceptState(s1).CreateFile(fn, s1, 5); err == nil { 89 atomic.AddUint32(&successCount, 1) 90 } else if os.IsExist(err) { 91 atomic.AddUint32(&existsErrorCount, 1) 92 } 93 }() 94 } 95 wg.Wait() 96 97 require.Equal(successCount, uint32(1)) 98 require.Equal(existsErrorCount, uint32(99)) 99 100 // Verify file exists. 101 _, err := os.Stat(filepath.Join(s1.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn))) 102 require.NoError(err) 103 104 // Create file again with different target state, but include state of existing file as an acceptable state. 105 err = store.NewFileOp().AcceptState(s1).CreateFile(fn, s2, 5) 106 require.Error(err) 107 require.True(os.IsExist(err)) 108 _, err = os.Stat(filepath.Join(s1.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn))) 109 require.NoError(err) 110 } 111 112 func testCreateFileFail(require *require.Assertions, storeBundle *fileStoreTestBundle) { 113 store := storeBundle.store 114 115 fn := core.DigestFixture().Hex() 116 s1 := storeBundle.state1 117 s2 := storeBundle.state2 118 s3 := storeBundle.state3 119 120 // Create empty file 121 err := store.NewFileOp().AcceptState(s1).CreateFile(fn, s1, 5) 122 require.NoError(err) 123 _, err = os.Stat(filepath.Join(s1.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn))) 124 require.NoError(err) 125 126 // Create file again with different target state 127 err = store.NewFileOp().AcceptState(s3).CreateFile(fn, s2, 5) 128 require.Error(err) 129 require.True(IsFileStateError(err)) 130 require.True(strings.HasPrefix(err.Error(), "failed to perform")) 131 _, err = os.Stat(filepath.Join(s1.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn))) 132 require.NoError(err) 133 } 134 135 func testReloadFileEntry(require *require.Assertions, storeBundle *fileStoreTestBundle) { 136 store := storeBundle.store 137 138 fn := core.DigestFixture().Hex() 139 m := getMockMetadataOne() 140 m.content = []byte("foo") 141 s1 := storeBundle.state1 142 143 // Create file 144 require.NoError(store.NewFileOp().CreateFile(fn, s1, 5)) 145 _, err := os.Stat(filepath.Join(s1.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn))) 146 require.NoError(err) 147 _, err = store.NewFileOp().AcceptState(s1).GetFileStat(fn) 148 require.NoError(err) 149 ok := store.fileMap.Contains(fn) 150 require.True(ok) 151 _, err = store.NewFileOp().AcceptState(s1).SetFileMetadata(fn, m) 152 require.NoError(err) 153 154 // Recreate store nukes store's in memory map 155 storeBundle.recreateStore() 156 store = storeBundle.store 157 ok = store.fileMap.Contains(fn) 158 require.False(ok) 159 160 // GetFileReader should load file from disk into map, including metadata. 161 _, err = store.NewFileOp().AcceptState(s1).GetFileReader(fn) 162 require.NoError(err) 163 ok = store.fileMap.Contains(fn) 164 require.True(ok) 165 result := getMockMetadataOne() 166 require.NoError(store.NewFileOp().AcceptState(s1).GetFileMetadata(fn, result)) 167 require.Equal(m.content, result.content) 168 } 169 170 func testMoveFile(require *require.Assertions, storeBundle *fileStoreTestBundle) { 171 store := storeBundle.store 172 173 s1 := storeBundle.state1 174 s2 := storeBundle.state2 175 s3 := storeBundle.state3 176 fn, ok := storeBundle.files[s1] 177 if !ok { 178 log.Fatal("file not found in state1") 179 } 180 181 // Update content 182 readWriterState2, err := store.NewFileOp().AcceptState(s1).GetFileReadWriter(fn) 183 require.NoError(err) 184 _, err = readWriterState2.Write([]byte{'t', 'e', 's', 't', '\n'}) 185 require.NoError(err) 186 readWriterState2.Close() 187 readWriterState2, err = store.NewFileOp().AcceptState(s1).GetFileReadWriter(fn) 188 require.NoError(err) 189 190 // Move from state1 to state2 191 err = store.NewFileOp().AcceptState(s1).MoveFile(fn, s2) 192 require.NoError(err) 193 _, err = os.Stat(filepath.Join(s2.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn))) 194 require.NoError(err) 195 _, err = os.Stat(filepath.Join(s1.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn))) 196 require.True(os.IsNotExist(err)) 197 _, err = store.NewFileOp().AcceptState(s2).GetFileReader(fn) 198 require.NoError(err) 199 200 // Move from state1 to state3 would fail with state error 201 err = store.NewFileOp().AcceptState(s1).MoveFile(fn, s3) 202 require.Error(err) 203 require.True(IsFileStateError(err)) 204 205 // Create new readWriter at new state 206 readWriterState1, err := store.NewFileOp().AcceptState(s2).GetFileReadWriter(fn) 207 require.NoError(err) 208 // Check content 209 dataState1, err := ioutil.ReadAll(readWriterState1) 210 require.NoError(err) 211 dataState2, err := ioutil.ReadAll(readWriterState2) 212 require.NoError(err) 213 require.Equal(dataState1, dataState2) 214 require.Equal([]byte{'t', 'e', 's', 't', '\n'}, dataState1) 215 // Write with old readWriter 216 _, err = readWriterState1.WriteAt([]byte{'1'}, 0) 217 require.NoError(err) 218 // Check content again 219 readWriterState1.Seek(0, 0) 220 readWriterState2.Seek(0, 0) 221 dataState1, err = ioutil.ReadAll(readWriterState1) 222 require.NoError(err) 223 dataState2, err = ioutil.ReadAll(readWriterState2) 224 require.NoError(err) 225 require.Equal(dataState1, dataState2) 226 require.Equal([]byte{'1', 'e', 's', 't', '\n'}, dataState1) 227 // Close on last opened readwriter removes hardlink 228 readWriterState2.Close() 229 _, err = os.Stat(filepath.Join(s1.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn))) 230 require.True(os.IsNotExist(err)) 231 readWriterState1.Close() 232 _, err = os.Stat(filepath.Join(s2.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn))) 233 require.NoError(err) 234 // Check content again 235 readWriterStateMoved, err := store.NewFileOp().AcceptState(s2).GetFileReadWriter(fn) 236 require.NoError(err) 237 dataMoved, err := ioutil.ReadAll(readWriterStateMoved) 238 require.NoError(err) 239 require.Equal([]byte{'1', 'e', 's', 't', '\n'}, dataMoved) 240 readWriterStateMoved.Close() 241 242 // Move back to state1 243 err = store.NewFileOp().AcceptState(s2).MoveFile(fn, s1) 244 require.NoError(err) 245 _, err = store.NewFileOp().AcceptState(s1).GetFileReader(fn) 246 require.NoError(err) 247 } 248 249 func testLinkFileTo(require *require.Assertions, storeBundle *fileStoreTestBundle) { 250 store := storeBundle.store 251 252 s1 := storeBundle.state1 253 s3 := storeBundle.state3 254 fn, ok := storeBundle.files[s1] 255 if !ok { 256 log.Fatal("file not found in state1") 257 } 258 259 dst := filepath.Join(s3.GetDirectory(), "test_dst") 260 require.NoError(store.NewFileOp().AcceptState(s1).LinkFileTo(fn, dst)) 261 _, err := os.Stat(dst) 262 require.NoError(err) 263 } 264 265 func testDeleteFile(require *require.Assertions, storeBundle *fileStoreTestBundle) { 266 store := storeBundle.store 267 268 s1 := storeBundle.state1 269 fn, ok := storeBundle.files[s1] 270 if !ok { 271 log.Fatal("file not found in state1") 272 } 273 content := "this a test for read after delete" 274 275 // Write to file 276 rw, err := store.NewFileOp().AcceptState(s1).GetFileReadWriter(fn) 277 require.NoError(err) 278 rw.Write([]byte(content)) 279 280 // Confirm deletion 281 err = store.NewFileOp().AcceptState(s1).DeleteFile(fn) 282 require.NoError(err) 283 _, err = os.Stat(filepath.Join(s1.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn))) 284 require.True(os.IsNotExist(err)) 285 286 // Existing readwriter should still work after deletion 287 rw.Seek(0, 0) 288 data, err := ioutil.ReadAll(rw) 289 require.NoError(err) 290 require.Equal(content, string(data)) 291 292 rw.Write([]byte(content)) 293 rw.Seek(0, 0) 294 data, err = ioutil.ReadAll(rw) 295 require.NoError(err) 296 require.Equal(content+content, string(data)) 297 298 rw.Close() 299 300 // Get deleted file should fail 301 _, err = store.NewFileOp().AcceptState(s1).GetFileReader(fn) 302 require.True(os.IsNotExist(err)) 303 } 304 305 func testGetFileReader(require *require.Assertions, storeBundle *fileStoreTestBundle) { 306 store := storeBundle.store 307 308 s1 := storeBundle.state1 309 fn, ok := storeBundle.files[s1] 310 if !ok { 311 log.Fatal("file not found in state1") 312 } 313 314 // Get ReadWriter and modify the file. 315 readWriter, err := store.NewFileOp().AcceptState(s1).GetFileReadWriter(fn) 316 require.NoError(err) 317 defer readWriter.Close() 318 _, err = readWriter.Write([]byte{'t', 'e', 's', 't', '\n'}) 319 require.NoError(err) 320 321 // Test getFileReader. 322 var wg sync.WaitGroup 323 for i := 0; i < 100; i++ { 324 wg.Add(1) 325 go func() { 326 defer wg.Done() 327 reader, err := store.NewFileOp().AcceptState(s1).GetFileReader(fn) 328 require.NoError(err) 329 330 b := make([]byte, 5) 331 _, err = reader.Seek(0, 0) 332 require.NoError(err) 333 l, err := reader.ReadAt(b, 0) 334 require.NoError(err) 335 require.Equal(l, 5) 336 require.Equal(string(b[:l]), "test\n") 337 338 err = reader.Close() 339 require.NoError(err) 340 }() 341 } 342 wg.Wait() 343 344 reader, err := store.NewFileOp().AcceptState(s1).GetFileReader(fn) 345 require.NoError(err) 346 reader.Close() 347 } 348 349 func testGetFileReadWriter(require *require.Assertions, storeBundle *fileStoreTestBundle) { 350 store := storeBundle.store 351 352 s1 := storeBundle.state1 353 fn, ok := storeBundle.files[s1] 354 if !ok { 355 log.Fatal("file not found in state1") 356 } 357 358 // Get ReadWriter and modify file concurrently. 359 var wg sync.WaitGroup 360 for i := 0; i < 100; i++ { 361 wg.Add(1) 362 go func() { 363 defer wg.Done() 364 readWriter, err := store.NewFileOp().AcceptState(s1).GetFileReadWriter(fn) 365 require.NoError(err) 366 367 _, err = readWriter.Write([]byte{'t', 'e', 's', 't', '\n'}) 368 require.NoError(err) 369 370 b := make([]byte, 3) 371 _, err = readWriter.Seek(1, 0) 372 require.NoError(err) 373 l, err := readWriter.Read(b) 374 require.NoError(err) 375 require.Equal(l, 3) 376 require.Equal(string(b[:l]), "est") 377 _, err = readWriter.Seek(0, 0) 378 require.NoError(err) 379 380 err = readWriter.Close() 381 require.NoError(err) 382 }() 383 } 384 wg.Wait() 385 386 // Verify content. 387 reader, err := store.NewFileOp().AcceptState(s1).GetFileReader(fn) 388 require.NoError(err) 389 390 b := make([]byte, 5) 391 _, err = reader.Seek(0, 0) 392 require.NoError(err) 393 l, err := reader.ReadAt(b, 0) 394 require.NoError(err) 395 require.Equal(l, 5) 396 require.Equal(string(b[:l]), "test\n") 397 398 err = reader.Close() 399 require.NoError(err) 400 } 401 402 func testGetOrSetFileMetadataConcurrently(require *require.Assertions, storeBundle *fileStoreTestBundle) { 403 store := storeBundle.store 404 405 s1 := storeBundle.state1 406 fn, ok := storeBundle.files[s1] 407 if !ok { 408 log.Fatal("file not found in state1") 409 } 410 411 original := []byte("foo") 412 413 // Get ReadWriter and modify file concurrently. 414 var wg sync.WaitGroup 415 for i := 0; i < 50; i++ { 416 wg.Add(1) 417 go func() { 418 defer wg.Done() 419 420 m := getMockMetadataOne() 421 m.content = original 422 require.NoError(store.NewFileOp().AcceptState(s1).GetOrSetFileMetadata(fn, m)) 423 require.Equal(original, m.content) 424 }() 425 } 426 wg.Wait() 427 428 // Verify content 429 m := getMockMetadataOne() 430 require.NoError(store.NewFileOp().AcceptState(s1).GetFileMetadata(fn, m)) 431 require.Equal(original, m.content) 432 } 433 434 func testSetFileMetadataAtConcurrently(require *require.Assertions, storeBundle *fileStoreTestBundle) { 435 store := storeBundle.store 436 437 s1 := storeBundle.state1 438 fn, ok := storeBundle.files[s1] 439 if !ok { 440 log.Fatal("file not found in state1") 441 } 442 443 m := getMockMetadataOne() 444 m.content = make([]byte, 50) 445 updated, err := store.NewFileOp().AcceptState(s1).SetFileMetadata(fn, m) 446 require.True(updated) 447 require.NoError(err) 448 449 // Get ReadWriter and modify file concurrently. 450 var wg sync.WaitGroup 451 for i := 0; i < 50; i++ { 452 wg.Add(1) 453 go func(offset int) { 454 defer wg.Done() 455 456 _, err := store.NewFileOp().AcceptState(s1).SetFileMetadataAt(fn, m, []byte("f"), int64(offset)) 457 // require.True(ok) 458 require.NoError(err) 459 }(i) 460 } 461 wg.Wait() 462 463 // Verify content 464 require.NoError(store.NewFileOp().AcceptState(s1).GetFileMetadata(fn, m)) 465 require.Equal(50, len(m.content)) 466 for i := 0; i < 50; i++ { 467 require.Equal(byte('f'), m.content[i]) 468 } 469 } 470 471 func testDeleteFileMetadata(require *require.Assertions, storeBundle *fileStoreTestBundle) { 472 store := storeBundle.store 473 474 s1 := storeBundle.state1 475 fn, ok := storeBundle.files[s1] 476 if !ok { 477 log.Fatal("file not found in state1") 478 } 479 480 m := getMockMetadataOne() 481 482 // DeleteFileMetadata doesn't return error if the file doesn't exist. 483 require.NoError(store.NewFileOp().AcceptState(s1).DeleteFileMetadata(fn, m)) 484 485 m.content = make([]byte, 1) 486 updated, err := store.NewFileOp().AcceptState(s1).SetFileMetadata(fn, m) 487 require.True(updated) 488 require.NoError(err) 489 490 require.NoError(store.NewFileOp().AcceptState(s1).GetFileMetadata(fn, m)) 491 require.NoError(store.NewFileOp().AcceptState(s1).DeleteFileMetadata(fn, m)) 492 require.Error(store.NewFileOp().AcceptState(s1).GetFileMetadata(fn, m)) 493 }