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  }