github.com/m3db/m3@v1.5.0/src/dbnode/storage/cleanup_test.go (about) 1 // Copyright (c) 2016 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package storage 22 23 import ( 24 "errors" 25 "fmt" 26 "strings" 27 "testing" 28 "time" 29 30 "github.com/m3db/m3/src/dbnode/namespace" 31 "github.com/m3db/m3/src/dbnode/persist" 32 "github.com/m3db/m3/src/dbnode/persist/fs" 33 "github.com/m3db/m3/src/dbnode/persist/fs/commitlog" 34 "github.com/m3db/m3/src/dbnode/retention" 35 xerrors "github.com/m3db/m3/src/x/errors" 36 "github.com/m3db/m3/src/x/ident" 37 xtest "github.com/m3db/m3/src/x/test" 38 xtime "github.com/m3db/m3/src/x/time" 39 40 "github.com/golang/mock/gomock" 41 "github.com/pborman/uuid" 42 "github.com/stretchr/testify/require" 43 "github.com/uber-go/tally" 44 ) 45 46 var ( 47 retentionOptions = retention.NewOptions() 48 namespaceOptions = namespace.NewOptions() 49 ) 50 51 func TestCleanupManagerCleanupCommitlogsAndSnapshots(t *testing.T) { 52 ctrl := xtest.NewController(t) 53 defer ctrl.Finish() 54 55 testBlockStart := xtime.Now().Truncate(2 * time.Hour) 56 testSnapshotUUID0 := uuid.Parse("a6367b49-9c83-4706-bd5c-400a4a9ec77c") 57 require.NotNil(t, testSnapshotUUID0) 58 59 testSnapshotUUID1 := uuid.Parse("bed2156f-182a-47ea-83ff-0a55d34c8a82") 60 require.NotNil(t, testSnapshotUUID1) 61 62 testCommitlogFileIdentifier := persist.CommitLogFile{ 63 FilePath: "commitlog-filepath-1", 64 Index: 1, 65 } 66 testSnapshotMetadataIdentifier1 := fs.SnapshotMetadataIdentifier{ 67 Index: 0, 68 UUID: testSnapshotUUID0, 69 } 70 testSnapshotMetadataIdentifier2 := fs.SnapshotMetadataIdentifier{ 71 Index: 1, 72 UUID: testSnapshotUUID1, 73 } 74 testSnapshotMetadata0 := fs.SnapshotMetadata{ 75 ID: testSnapshotMetadataIdentifier1, 76 CommitlogIdentifier: testCommitlogFileIdentifier, 77 MetadataFilePath: "metadata-filepath-0", 78 CheckpointFilePath: "checkpoint-filepath-0", 79 } 80 testSnapshotMetadata1 := fs.SnapshotMetadata{ 81 ID: testSnapshotMetadataIdentifier2, 82 CommitlogIdentifier: testCommitlogFileIdentifier, 83 MetadataFilePath: "metadata-filepath-1", 84 CheckpointFilePath: "checkpoint-filepath-1", 85 } 86 87 testCases := []struct { 88 title string 89 snapshotMetadata snapshotMetadataFilesFn 90 commitlogs commitLogFilesFn 91 snapshots snapshotFilesFn 92 expectedDeletedFiles []string 93 expectErr bool 94 }{ 95 { 96 title: "Does nothing if no snapshot metadata files", 97 snapshotMetadata: func(fs.Options) ([]fs.SnapshotMetadata, []fs.SnapshotMetadataErrorWithPaths, error) { 98 return nil, nil, nil 99 }, 100 }, 101 { 102 title: "Does not delete snapshots associated with the most recent snapshot metadata file", 103 snapshotMetadata: func(fs.Options) ([]fs.SnapshotMetadata, []fs.SnapshotMetadataErrorWithPaths, error) { 104 return []fs.SnapshotMetadata{testSnapshotMetadata0}, nil, nil 105 }, 106 snapshots: func(filePathPrefix string, namespace ident.ID, shard uint32) (fs.FileSetFilesSlice, error) { 107 return fs.FileSetFilesSlice{ 108 { 109 ID: fs.FileSetFileIdentifier{ 110 Namespace: namespace, 111 BlockStart: testBlockStart, 112 Shard: shard, 113 VolumeIndex: 0, 114 }, 115 AbsoluteFilePaths: []string{fmt.Sprintf("/snapshots/%s/snapshot-filepath-%d", namespace, shard)}, 116 CachedSnapshotTime: testBlockStart, 117 CachedSnapshotID: testSnapshotUUID0, 118 }, 119 }, nil 120 }, 121 commitlogs: func(commitlog.Options) (persist.CommitLogFiles, []commitlog.ErrorWithPath, error) { 122 return nil, nil, nil 123 }, 124 }, 125 { 126 title: "Deletes snapshots and metadata not associated with the most recent snapshot metadata file", 127 snapshotMetadata: func(fs.Options) ([]fs.SnapshotMetadata, []fs.SnapshotMetadataErrorWithPaths, error) { 128 return []fs.SnapshotMetadata{testSnapshotMetadata0, testSnapshotMetadata1}, nil, nil 129 }, 130 snapshots: func(filePathPrefix string, namespace ident.ID, shard uint32) (fs.FileSetFilesSlice, error) { 131 return fs.FileSetFilesSlice{ 132 { 133 ID: fs.FileSetFileIdentifier{ 134 Namespace: namespace, 135 BlockStart: testBlockStart, 136 Shard: shard, 137 VolumeIndex: 0, 138 }, 139 AbsoluteFilePaths: []string{fmt.Sprintf("/snapshots/%s/snapshot-filepath-%d", namespace, shard)}, 140 CachedSnapshotTime: testBlockStart, 141 CachedSnapshotID: testSnapshotUUID0, 142 }, 143 }, nil 144 }, 145 commitlogs: func(commitlog.Options) (persist.CommitLogFiles, []commitlog.ErrorWithPath, error) { 146 return nil, nil, nil 147 }, 148 expectedDeletedFiles: []string{ 149 "/snapshots/ns0/snapshot-filepath-0", 150 "/snapshots/ns0/snapshot-filepath-1", 151 "/snapshots/ns0/snapshot-filepath-2", 152 "/snapshots/ns1/snapshot-filepath-0", 153 "/snapshots/ns1/snapshot-filepath-1", 154 "/snapshots/ns1/snapshot-filepath-2", 155 "/snapshots/ns2/snapshot-filepath-0", 156 "/snapshots/ns2/snapshot-filepath-1", 157 "/snapshots/ns2/snapshot-filepath-2", 158 "metadata-filepath-0", 159 "checkpoint-filepath-0", 160 }, 161 }, 162 { 163 title: "Deletes corrupt snapshot metadata", 164 snapshotMetadata: func(fs.Options) ([]fs.SnapshotMetadata, []fs.SnapshotMetadataErrorWithPaths, error) { 165 return []fs.SnapshotMetadata{testSnapshotMetadata1}, []fs.SnapshotMetadataErrorWithPaths{ 166 { 167 Error: errors.New("some-error"), 168 MetadataFilePath: "metadata-filepath-0", 169 CheckpointFilePath: "checkpoint-filepath-0", 170 }, 171 }, nil 172 }, 173 snapshots: func(filePathPrefix string, namespace ident.ID, shard uint32) (fs.FileSetFilesSlice, error) { 174 return nil, nil 175 }, 176 commitlogs: func(commitlog.Options) (persist.CommitLogFiles, []commitlog.ErrorWithPath, error) { 177 return nil, nil, nil 178 }, 179 expectedDeletedFiles: []string{ 180 "metadata-filepath-0", 181 "checkpoint-filepath-0", 182 }, 183 }, 184 { 185 title: "Deletes corrupt snapshot files", 186 snapshotMetadata: func(fs.Options) ([]fs.SnapshotMetadata, []fs.SnapshotMetadataErrorWithPaths, error) { 187 return []fs.SnapshotMetadata{testSnapshotMetadata0}, nil, nil 188 }, 189 snapshots: func(filePathPrefix string, namespace ident.ID, shard uint32) (fs.FileSetFilesSlice, error) { 190 return fs.FileSetFilesSlice{ 191 { 192 ID: fs.FileSetFileIdentifier{ 193 Namespace: namespace, 194 BlockStart: testBlockStart, 195 Shard: shard, 196 VolumeIndex: 0, 197 }, 198 AbsoluteFilePaths: []string{fmt.Sprintf("/snapshots/%s/snapshot-filepath-%d", namespace, shard)}, 199 // Zero these out so it will try to look them up and return an error, indicating the files 200 // are corrupt. 201 CachedSnapshotTime: 0, 202 CachedSnapshotID: nil, 203 }, 204 }, nil 205 }, 206 commitlogs: func(commitlog.Options) (persist.CommitLogFiles, []commitlog.ErrorWithPath, error) { 207 return nil, nil, nil 208 }, 209 expectedDeletedFiles: []string{ 210 "/snapshots/ns0/snapshot-filepath-0", 211 "/snapshots/ns0/snapshot-filepath-1", 212 "/snapshots/ns0/snapshot-filepath-2", 213 "/snapshots/ns1/snapshot-filepath-0", 214 "/snapshots/ns1/snapshot-filepath-1", 215 "/snapshots/ns1/snapshot-filepath-2", 216 "/snapshots/ns2/snapshot-filepath-0", 217 "/snapshots/ns2/snapshot-filepath-1", 218 "/snapshots/ns2/snapshot-filepath-2", 219 }, 220 }, 221 { 222 title: "Does not delete the commitlog identified in the most recent snapshot metadata file, or any with a higher index", 223 snapshotMetadata: func(fs.Options) ([]fs.SnapshotMetadata, []fs.SnapshotMetadataErrorWithPaths, error) { 224 return []fs.SnapshotMetadata{testSnapshotMetadata0}, nil, nil 225 }, 226 snapshots: func(filePathPrefix string, namespace ident.ID, shard uint32) (fs.FileSetFilesSlice, error) { 227 return nil, nil 228 }, 229 commitlogs: func(commitlog.Options) (persist.CommitLogFiles, []commitlog.ErrorWithPath, error) { 230 return persist.CommitLogFiles{ 231 {FilePath: "commitlog-file-0", Index: 0}, 232 // Index 1, the one pointed to bby testSnapshotMetdata1 233 testCommitlogFileIdentifier, 234 {FilePath: "commitlog-file-2", Index: 2}, 235 }, nil, nil 236 }, 237 // Should only delete anything with an index lower than 1. 238 expectedDeletedFiles: []string{"commitlog-file-0"}, 239 }, 240 { 241 title: "Deletes all corrupt commitlog files", 242 snapshotMetadata: func(fs.Options) ([]fs.SnapshotMetadata, []fs.SnapshotMetadataErrorWithPaths, error) { 243 return []fs.SnapshotMetadata{testSnapshotMetadata0}, nil, nil 244 }, 245 snapshots: func(filePathPrefix string, namespace ident.ID, shard uint32) (fs.FileSetFilesSlice, error) { 246 return nil, nil 247 }, 248 commitlogs: func(commitlog.Options) (persist.CommitLogFiles, []commitlog.ErrorWithPath, error) { 249 return nil, []commitlog.ErrorWithPath{ 250 commitlog.NewErrorWithPath(errors.New("some-error-0"), "corrupt-commitlog-file-0"), 251 commitlog.NewErrorWithPath(errors.New("some-error-1"), "corrupt-commitlog-file-1"), 252 }, nil 253 }, 254 // Should only delete anything with an index lower than 1. 255 expectedDeletedFiles: []string{"corrupt-commitlog-file-0", "corrupt-commitlog-file-1"}, 256 }, 257 { 258 title: "Handles errors listing snapshot files", 259 snapshotMetadata: func(fs.Options) ([]fs.SnapshotMetadata, []fs.SnapshotMetadataErrorWithPaths, error) { 260 return []fs.SnapshotMetadata{testSnapshotMetadata0}, nil, nil 261 }, 262 snapshots: func(filePathPrefix string, namespace ident.ID, shard uint32) (fs.FileSetFilesSlice, error) { 263 return nil, errors.New("some-error") 264 }, 265 commitlogs: func(commitlog.Options) (persist.CommitLogFiles, []commitlog.ErrorWithPath, error) { 266 return nil, []commitlog.ErrorWithPath{ 267 commitlog.NewErrorWithPath(errors.New("some-error-0"), "corrupt-commitlog-file-0"), 268 commitlog.NewErrorWithPath(errors.New("some-error-1"), "corrupt-commitlog-file-1"), 269 }, nil 270 }, 271 // We still expect it to delete the commitlog files even though its going to return an error. 272 expectedDeletedFiles: []string{"corrupt-commitlog-file-0", "corrupt-commitlog-file-1"}, 273 expectErr: true, 274 }, 275 } 276 277 for _, tc := range testCases { 278 t.Run(tc.title, func(t *testing.T) { 279 ts := timeFor() 280 rOpts := retention.NewOptions(). 281 SetRetentionPeriod(21600 * time.Second). 282 SetBlockSize(7200 * time.Second) 283 nsOpts := namespace.NewOptions().SetRetentionOptions(rOpts) 284 285 namespaces := make([]databaseNamespace, 0, 3) 286 shards := make([]databaseShard, 0, 3) 287 for i := 0; i < 3; i++ { 288 shard := NewMockdatabaseShard(ctrl) 289 shard.EXPECT().ID().Return(uint32(i)).AnyTimes() 290 shard.EXPECT().IsBootstrapped().Return(true).AnyTimes() 291 shard.EXPECT().CleanupExpiredFileSets(gomock.Any()).Return(nil).AnyTimes() 292 shard.EXPECT().CleanupCompactedFileSets().Return(nil).AnyTimes() 293 294 shards = append(shards, shard) 295 } 296 297 for i := 0; i < 3; i++ { 298 ns := NewMockdatabaseNamespace(ctrl) 299 ns.EXPECT().ID().Return(ident.StringID(fmt.Sprintf("ns%d", i))).AnyTimes() 300 ns.EXPECT().Options().Return(nsOpts).AnyTimes() 301 ns.EXPECT().NeedsFlush(gomock.Any(), gomock.Any()).Return(false, nil).AnyTimes() 302 ns.EXPECT().OwnedShards().Return(shards).AnyTimes() 303 namespaces = append(namespaces, ns) 304 } 305 306 db := newMockdatabase(ctrl, namespaces...) 307 db.EXPECT().OwnedNamespaces().Return(namespaces, nil).AnyTimes() 308 mgr := newCleanupManager(db, newNoopFakeActiveLogs(), tally.NoopScope).(*cleanupManager) 309 mgr.opts = mgr.opts.SetCommitLogOptions( 310 mgr.opts.CommitLogOptions(). 311 SetBlockSize(rOpts.BlockSize())) 312 313 mgr.snapshotMetadataFilesFn = tc.snapshotMetadata 314 mgr.commitLogFilesFn = tc.commitlogs 315 mgr.snapshotFilesFn = tc.snapshots 316 317 var deletedFiles []string 318 mgr.deleteFilesFn = func(files []string) error { 319 deletedFiles = append(deletedFiles, files...) 320 return nil 321 } 322 323 err := cleanup(mgr, ts) 324 if tc.expectErr { 325 require.Error(t, err) 326 } else { 327 require.NoError(t, err) 328 } 329 330 require.Equal(t, tc.expectedDeletedFiles, deletedFiles) 331 }) 332 } 333 } 334 335 func TestCleanupManagerNamespaceCleanupBootstrapped(t *testing.T) { 336 ctrl := xtest.NewController(t) 337 defer ctrl.Finish() 338 339 ts := timeFor() 340 rOpts := retentionOptions. 341 SetRetentionPeriod(21600 * time.Second). 342 SetBlockSize(3600 * time.Second) 343 nsOpts := namespaceOptions. 344 SetRetentionOptions(rOpts). 345 SetCleanupEnabled(true). 346 SetIndexOptions(namespace.NewIndexOptions(). 347 SetEnabled(true). 348 SetBlockSize(7200 * time.Second)) 349 350 shard := NewMockdatabaseShard(ctrl) 351 shard.EXPECT().ID().Return(uint32(42)).AnyTimes() 352 shard.EXPECT().IsBootstrapped().Return(true).AnyTimes() 353 shard.EXPECT().CleanupExpiredFileSets(gomock.Eq(ts.Add(-rOpts.RetentionPeriod()))).Return(nil) 354 shard.EXPECT().CleanupCompactedFileSets().Return(nil) 355 356 ns := NewMockdatabaseNamespace(ctrl) 357 ns.EXPECT().ID().Return(ident.StringID("ns")).AnyTimes() 358 ns.EXPECT().Options().Return(nsOpts).AnyTimes() 359 ns.EXPECT().NeedsFlush(gomock.Any(), gomock.Any()).Return(false, nil).AnyTimes() 360 ns.EXPECT().OwnedShards().Return([]databaseShard{shard}).AnyTimes() 361 362 idx := NewMockNamespaceIndex(ctrl) 363 ns.EXPECT().Index().Times(3).Return(idx, nil) 364 365 nses := []databaseNamespace{ns} 366 db := newMockdatabase(ctrl, ns) 367 db.EXPECT().OwnedNamespaces().Return(nses, nil).AnyTimes() 368 369 mgr := newCleanupManager(db, newNoopFakeActiveLogs(), tally.NoopScope).(*cleanupManager) 370 idx.EXPECT().CleanupExpiredFileSets(ts).Return(nil) 371 idx.EXPECT().CleanupCorruptedFileSets().Return(nil) 372 idx.EXPECT().CleanupDuplicateFileSets([]uint32{42}).Return(nil) 373 require.NoError(t, cleanup(mgr, ts)) 374 } 375 376 func TestCleanupManagerNamespaceCleanupNotBootstrapped(t *testing.T) { 377 ctrl := xtest.NewController(t) 378 defer ctrl.Finish() 379 380 ts := timeFor() 381 rOpts := retentionOptions. 382 SetRetentionPeriod(21600 * time.Second). 383 SetBlockSize(3600 * time.Second) 384 nsOpts := namespaceOptions. 385 SetRetentionOptions(rOpts). 386 SetCleanupEnabled(true). 387 SetIndexOptions(namespace.NewIndexOptions(). 388 SetEnabled(true). 389 SetBlockSize(7200 * time.Second)) 390 391 idx := NewMockNamespaceIndex(ctrl) 392 idx.EXPECT().CleanupExpiredFileSets(gomock.Any()).Return(nil) 393 idx.EXPECT().CleanupCorruptedFileSets().Return(nil) 394 idx.EXPECT().CleanupDuplicateFileSets(gomock.Any()).Return(nil) 395 396 ns := NewMockdatabaseNamespace(ctrl) 397 ns.EXPECT().ID().Return(ident.StringID("ns")).AnyTimes() 398 ns.EXPECT().Options().Return(nsOpts).AnyTimes() 399 ns.EXPECT().NeedsFlush(gomock.Any(), gomock.Any()).Return(false, nil).AnyTimes() 400 ns.EXPECT().OwnedShards().Return(nil).AnyTimes() 401 ns.EXPECT().Index().Return(idx, nil).AnyTimes() 402 403 nses := []databaseNamespace{ns} 404 db := newMockdatabase(ctrl, ns) 405 db.EXPECT().OwnedNamespaces().Return(nses, nil).AnyTimes() 406 407 mgr := newCleanupManager(db, newNoopFakeActiveLogs(), tally.NoopScope).(*cleanupManager) 408 require.NoError(t, cleanup(mgr, ts)) 409 } 410 411 // Test NS doesn't cleanup when flag is present 412 func TestCleanupManagerDoesntNeedCleanup(t *testing.T) { 413 ctrl := xtest.NewController(t) 414 defer ctrl.Finish() 415 ts := timeFor() 416 rOpts := retentionOptions. 417 SetRetentionPeriod(21600 * time.Second). 418 SetBlockSize(7200 * time.Second) 419 nsOpts := namespaceOptions.SetRetentionOptions(rOpts).SetCleanupEnabled(false) 420 421 namespaces := make([]databaseNamespace, 0, 3) 422 for range namespaces { 423 ns := NewMockdatabaseNamespace(ctrl) 424 ns.EXPECT().Options().Return(nsOpts).AnyTimes() 425 ns.EXPECT().NeedsFlush(gomock.Any(), gomock.Any()).Return(false, nil).AnyTimes() 426 namespaces = append(namespaces, ns) 427 } 428 db := newMockdatabase(ctrl, namespaces...) 429 db.EXPECT().OwnedNamespaces().Return(namespaces, nil).AnyTimes() 430 mgr := newCleanupManager(db, newNoopFakeActiveLogs(), tally.NoopScope).(*cleanupManager) 431 mgr.opts = mgr.opts.SetCommitLogOptions( 432 mgr.opts.CommitLogOptions(). 433 SetBlockSize(rOpts.BlockSize())) 434 435 var deletedFiles []string 436 mgr.deleteFilesFn = func(files []string) error { 437 deletedFiles = append(deletedFiles, files...) 438 return nil 439 } 440 441 require.NoError(t, cleanup(mgr, ts)) 442 } 443 444 func TestCleanupDataAndSnapshotFileSetFiles(t *testing.T) { 445 ctrl := xtest.NewController(t) 446 defer ctrl.Finish() 447 ts := timeFor() 448 449 nsOpts := namespaceOptions 450 ns := NewMockdatabaseNamespace(ctrl) 451 ns.EXPECT().Options().Return(nsOpts).AnyTimes() 452 453 shard := NewMockdatabaseShard(ctrl) 454 shardNotBootstrapped := NewMockdatabaseShard(ctrl) 455 shardNotBootstrapped.EXPECT().IsBootstrapped().Return(false).AnyTimes() 456 shardNotBootstrapped.EXPECT().ID().Return(uint32(1)).AnyTimes() 457 expectedEarliestToRetain := retention.FlushTimeStart(ns.Options().RetentionOptions(), ts) 458 shard.EXPECT().IsBootstrapped().Return(true).AnyTimes() 459 shard.EXPECT().CleanupExpiredFileSets(expectedEarliestToRetain).Return(nil) 460 shard.EXPECT().CleanupCompactedFileSets().Return(nil) 461 shard.EXPECT().ID().Return(uint32(0)).AnyTimes() 462 ns.EXPECT().OwnedShards().Return([]databaseShard{shard, shardNotBootstrapped}).AnyTimes() 463 ns.EXPECT().ID().Return(ident.StringID("nsID")).AnyTimes() 464 ns.EXPECT().NeedsFlush(gomock.Any(), gomock.Any()).Return(false, nil).AnyTimes() 465 namespaces := []databaseNamespace{ns} 466 467 db := newMockdatabase(ctrl, namespaces...) 468 db.EXPECT().OwnedNamespaces().Return(namespaces, nil).AnyTimes() 469 mgr := newCleanupManager(db, newNoopFakeActiveLogs(), tally.NoopScope).(*cleanupManager) 470 471 require.NoError(t, cleanup(mgr, ts)) 472 } 473 474 type deleteInactiveDirectoriesCall struct { 475 parentDirPath string 476 activeDirNames []string 477 } 478 479 func TestDeleteInactiveDataAndSnapshotFileSetFiles(t *testing.T) { 480 ctrl := xtest.NewController(t) 481 defer ctrl.Finish() 482 ts := timeFor() 483 484 nsOpts := namespaceOptions. 485 SetCleanupEnabled(false) 486 ns := NewMockdatabaseNamespace(ctrl) 487 ns.EXPECT().Options().Return(nsOpts).AnyTimes() 488 489 shard := NewMockdatabaseShard(ctrl) 490 shard.EXPECT().ID().Return(uint32(0)).AnyTimes() 491 shard.EXPECT().IsBootstrapped().Return(true).AnyTimes() 492 ns.EXPECT().OwnedShards().Return([]databaseShard{shard}).AnyTimes() 493 ns.EXPECT().ID().Return(ident.StringID("nsID")).AnyTimes() 494 ns.EXPECT().NeedsFlush(gomock.Any(), gomock.Any()).Return(false, nil).AnyTimes() 495 namespaces := []databaseNamespace{ns} 496 497 db := newMockdatabase(ctrl, namespaces...) 498 db.EXPECT().OwnedNamespaces().Return(namespaces, nil).AnyTimes() 499 mgr := newCleanupManager(db, newNoopFakeActiveLogs(), tally.NoopScope).(*cleanupManager) 500 501 deleteInactiveDirectoriesCalls := []deleteInactiveDirectoriesCall{} 502 deleteInactiveDirectoriesFn := func(parentDirPath string, activeDirNames []string) error { 503 deleteInactiveDirectoriesCalls = append(deleteInactiveDirectoriesCalls, deleteInactiveDirectoriesCall{ 504 parentDirPath: parentDirPath, 505 activeDirNames: activeDirNames, 506 }) 507 return nil 508 } 509 mgr.deleteInactiveDirectoriesFn = deleteInactiveDirectoriesFn 510 511 require.NoError(t, cleanup(mgr, ts)) 512 513 expectedCalls := []deleteInactiveDirectoriesCall{ 514 { 515 parentDirPath: "data/nsID", 516 activeDirNames: []string{"0"}, 517 }, 518 { 519 parentDirPath: "snapshots/nsID", 520 activeDirNames: []string{"0"}, 521 }, 522 { 523 parentDirPath: "data", 524 activeDirNames: []string{"nsID"}, 525 }, 526 } 527 528 for _, expectedCall := range expectedCalls { 529 found := false 530 for _, call := range deleteInactiveDirectoriesCalls { 531 if strings.Contains(call.parentDirPath, expectedCall.parentDirPath) && 532 expectedCall.activeDirNames[0] == call.activeDirNames[0] { 533 found = true 534 } 535 } 536 require.Equal(t, true, found) 537 } 538 } 539 540 func TestCleanupManagerPropagatesOwnedNamespacesError(t *testing.T) { 541 ctrl := xtest.NewController(t) 542 defer ctrl.Finish() 543 544 ts := timeFor() 545 546 db := NewMockdatabase(ctrl) 547 db.EXPECT().Options().Return(DefaultTestOptions()).AnyTimes() 548 db.EXPECT().Open().Return(nil) 549 db.EXPECT().Terminate().Return(nil) 550 db.EXPECT().OwnedNamespaces().Return(nil, errDatabaseIsClosed).AnyTimes() 551 552 mgr := newCleanupManager(db, newNoopFakeActiveLogs(), tally.NoopScope).(*cleanupManager) 553 require.NoError(t, db.Open()) 554 require.NoError(t, db.Terminate()) 555 556 require.Error(t, cleanup(mgr, ts)) 557 } 558 559 func timeFor() xtime.UnixNano { 560 return xtime.FromSeconds(36000) 561 } 562 563 type fakeActiveLogs struct { 564 activeLogs persist.CommitLogFiles 565 } 566 567 func (f fakeActiveLogs) ActiveLogs() (persist.CommitLogFiles, error) { 568 return f.activeLogs, nil 569 } 570 571 func newNoopFakeActiveLogs() fakeActiveLogs { 572 return newFakeActiveLogs(nil) 573 } 574 575 func newFakeActiveLogs(activeLogs persist.CommitLogFiles) fakeActiveLogs { 576 return fakeActiveLogs{ 577 activeLogs: activeLogs, 578 } 579 } 580 581 func cleanup( 582 mgr databaseCleanupManager, 583 t xtime.UnixNano, 584 ) error { 585 multiErr := xerrors.NewMultiError() 586 multiErr = multiErr.Add(mgr.WarmFlushCleanup(t)) 587 multiErr = multiErr.Add(mgr.ColdFlushCleanup(t)) 588 return multiErr.FinalError() 589 }