github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/journal_md_ops_test.go (about)

     1  // Copyright 2016 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package libkbfs
     6  
     7  import (
     8  	"os"
     9  	"testing"
    10  
    11  	"github.com/keybase/client/go/kbfs/ioutil"
    12  	"github.com/keybase/client/go/kbfs/kbfsmd"
    13  	"github.com/keybase/client/go/kbfs/tlf"
    14  	"github.com/keybase/client/go/kbfs/tlfhandle"
    15  	"github.com/keybase/client/go/protocol/keybase1"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  	"golang.org/x/net/context"
    19  )
    20  
    21  func setupJournalMDOpsTest(t *testing.T) (
    22  	tempdir string, ctx context.Context, cancel context.CancelFunc,
    23  	config *ConfigLocal, oldMDOps MDOps, jManager *JournalManager) {
    24  	tempdir, err := ioutil.TempDir(os.TempDir(), "journal_md_ops")
    25  	require.NoError(t, err)
    26  
    27  	// Clean up the tempdir if the rest of the setup fails.
    28  	setupSucceeded := false
    29  	defer func() {
    30  		if !setupSucceeded {
    31  			err := ioutil.RemoveAll(tempdir)
    32  			assert.NoError(t, err)
    33  		}
    34  	}()
    35  
    36  	ctx, cancel = context.WithTimeout(
    37  		context.Background(), individualTestTimeout)
    38  
    39  	// Clean up the context if the rest of the setup fails.
    40  	defer func() {
    41  		if !setupSucceeded {
    42  			cancel()
    43  		}
    44  	}()
    45  
    46  	config = MakeTestConfigOrBust(t, "test_user")
    47  
    48  	// Clean up the config if the rest of the setup fails.
    49  	defer func() {
    50  		if !setupSucceeded {
    51  			CheckConfigAndShutdown(ctx, t, config)
    52  		}
    53  	}()
    54  
    55  	oldMDOps = config.MDOps()
    56  	err = config.EnableDiskLimiter(tempdir)
    57  	require.NoError(t, err)
    58  	err = config.EnableJournaling(
    59  		ctx, tempdir, TLFJournalBackgroundWorkEnabled)
    60  	require.NoError(t, err)
    61  	jManager, err = GetJournalManager(config)
    62  	// Turn off listeners to avoid background MD pushes for CR.
    63  	jManager.onBranchChange = nil
    64  	jManager.onMDFlush = nil
    65  	require.NoError(t, err)
    66  
    67  	// Tests need to explicitly enable journaling, to avoid races
    68  	// where journals are enabled before they can be paused.
    69  	err = jManager.DisableAuto(ctx)
    70  	require.NoError(t, err)
    71  
    72  	setupSucceeded = true
    73  	return tempdir, ctx, cancel, config, oldMDOps, jManager
    74  }
    75  
    76  func teardownJournalMDOpsTest(
    77  	ctx context.Context, t *testing.T, tempdir string,
    78  	cancel context.CancelFunc, config Config) {
    79  	CheckConfigAndShutdown(ctx, t, config)
    80  	cancel()
    81  	err := ioutil.RemoveAll(tempdir)
    82  	assert.NoError(t, err)
    83  }
    84  
    85  func makeMDForJournalMDOpsTest(
    86  	t *testing.T, config Config, tlfID tlf.ID, h *tlfhandle.Handle,
    87  	revision kbfsmd.Revision) *RootMetadata {
    88  	rmd, err := makeInitialRootMetadata(config.MetadataVersion(), tlfID, h)
    89  	require.NoError(t, err)
    90  	rmd.SetRevision(revision)
    91  	ctx := context.Background()
    92  	rekeyDone, _, err := config.KeyManager().Rekey(ctx, rmd, false)
    93  	require.NoError(t, err)
    94  	require.True(t, rekeyDone)
    95  	return rmd
    96  }
    97  
    98  // TODO: Clean up the test below.
    99  
   100  func TestJournalMDOpsBasics(t *testing.T) {
   101  	tempdir, ctx, cancel, config, oldMDOps, jManager := setupJournalMDOpsTest(t)
   102  	defer teardownJournalMDOpsTest(ctx, t, tempdir, cancel, config)
   103  
   104  	session, err := config.KBPKI().GetCurrentSession(ctx)
   105  	require.NoError(t, err)
   106  
   107  	// (1) get metadata -- allocates an ID
   108  	bh, err := tlf.MakeHandle(
   109  		[]keybase1.UserOrTeamID{session.UID.AsUserOrTeam()}, nil, nil, nil, nil)
   110  	require.NoError(t, err)
   111  
   112  	h, err := tlfhandle.MakeHandle(
   113  		ctx, bh, bh.Type(), config.KBPKI(), config.KBPKI(), nil,
   114  		keybase1.OfflineAvailability_NONE)
   115  	require.NoError(t, err)
   116  
   117  	mdOps := jManager.mdOps()
   118  
   119  	id, err := mdOps.GetIDForHandle(ctx, h)
   120  	require.NoError(t, err)
   121  	require.NotEqual(t, tlf.NullID, id)
   122  	irmd, err := mdOps.GetForTLF(ctx, id, nil)
   123  	require.NoError(t, err)
   124  	require.Equal(t, ImmutableRootMetadata{}, irmd)
   125  	h.SetTlfID(id)
   126  
   127  	err = jManager.Enable(ctx, id, nil, TLFJournalBackgroundWorkPaused)
   128  	require.NoError(t, err)
   129  
   130  	rmd := makeMDForJournalMDOpsTest(t, config, id, h, kbfsmd.Revision(1))
   131  
   132  	irmd, err = mdOps.Put(
   133  		ctx, rmd, session.VerifyingKey, nil, keybase1.MDPriorityNormal, nil)
   134  	require.NoError(t, err)
   135  	prevRoot := irmd.mdID
   136  
   137  	// (2) push some new metadata blocks
   138  	for i := kbfsmd.Revision(2); i < 8; i++ {
   139  		rmd.SetRevision(i)
   140  		rmd.SetPrevRoot(prevRoot)
   141  		irmd, err := mdOps.Put(
   142  			ctx, rmd, session.VerifyingKey, nil, keybase1.MDPriorityNormal, nil)
   143  		require.NoError(t, err, "i=%d", i)
   144  		prevRoot = irmd.mdID
   145  	}
   146  
   147  	head, err := mdOps.GetForTLF(ctx, id, nil)
   148  	require.NoError(t, err)
   149  	require.NotEqual(t, ImmutableRootMetadata{}, head)
   150  	require.Equal(t, kbfsmd.Revision(7), head.Revision())
   151  
   152  	head, err = oldMDOps.GetForTLF(ctx, id, nil)
   153  	require.NoError(t, err)
   154  	require.Equal(t, ImmutableRootMetadata{}, head)
   155  
   156  	err = jManager.Flush(ctx, id)
   157  	require.NoError(t, err)
   158  
   159  	head, err = mdOps.GetForTLF(ctx, id, nil)
   160  	require.NoError(t, err)
   161  	require.NotEqual(t, ImmutableRootMetadata{}, head)
   162  	require.Equal(t, kbfsmd.Revision(7), head.Revision())
   163  
   164  	head, err = oldMDOps.GetForTLF(ctx, id, nil)
   165  	require.NoError(t, err)
   166  	require.NotEqual(t, ImmutableRootMetadata{}, head)
   167  	require.Equal(t, kbfsmd.Revision(7), head.Revision())
   168  
   169  	// (3) trigger a conflict
   170  	rmd.SetRevision(kbfsmd.Revision(8))
   171  	rmd.SetPrevRoot(prevRoot)
   172  	resolveMD, err := rmd.deepCopy(config.Codec())
   173  	require.NoError(t, err)
   174  	_, err = oldMDOps.Put(
   175  		ctx, rmd, session.VerifyingKey, nil, keybase1.MDPriorityNormal, nil)
   176  	require.NoError(t, err)
   177  
   178  	for i := kbfsmd.Revision(8); i <= 10; i++ {
   179  		rmd.SetRevision(i)
   180  		rmd.SetPrevRoot(prevRoot)
   181  		irmd, err := mdOps.Put(
   182  			ctx, rmd, session.VerifyingKey, nil, keybase1.MDPriorityNormal, nil)
   183  		require.NoError(t, err, "i=%d", i)
   184  		prevRoot = irmd.mdID
   185  	}
   186  
   187  	err = jManager.Flush(ctx, id)
   188  	require.NoError(t, err)
   189  
   190  	head, err = mdOps.GetForTLF(ctx, id, nil)
   191  	require.NoError(t, err)
   192  	require.NotEqual(t, ImmutableRootMetadata{}, head)
   193  	require.Equal(t, kbfsmd.Revision(8), head.Revision())
   194  
   195  	head, err = oldMDOps.GetForTLF(ctx, id, nil)
   196  	require.NoError(t, err)
   197  	require.NotEqual(t, ImmutableRootMetadata{}, head)
   198  	require.Equal(t, kbfsmd.Revision(8), head.Revision())
   199  
   200  	// Find the branch ID.
   201  	tlfJournal, ok := jManager.getTLFJournal(id, nil)
   202  	require.True(t, ok)
   203  	bid := tlfJournal.mdJournal.branchID
   204  
   205  	head, err = mdOps.GetUnmergedForTLF(ctx, id, bid)
   206  	require.NoError(t, err)
   207  	require.NotEqual(t, ImmutableRootMetadata{}, head)
   208  	require.Equal(t, kbfsmd.Revision(10), head.Revision())
   209  	require.Equal(t, bid, head.BID())
   210  
   211  	// (4) push some new unmerged metadata blocks linking to the
   212  	//     middle merged block.
   213  	for i := kbfsmd.Revision(11); i < 41; i++ {
   214  		rmd.SetRevision(i)
   215  		rmd.SetPrevRoot(prevRoot)
   216  		irmd, err := mdOps.PutUnmerged(ctx, rmd, session.VerifyingKey, nil)
   217  		require.NoError(t, err, "i=%d", i)
   218  		prevRoot = irmd.mdID
   219  		require.Equal(t, bid, rmd.BID())
   220  		bid = rmd.BID()
   221  		require.NoError(t, err)
   222  	}
   223  
   224  	// (5) check for proper unmerged head
   225  	head, err = mdOps.GetUnmergedForTLF(ctx, id, bid)
   226  	require.NoError(t, err)
   227  	require.NotEqual(t, ImmutableRootMetadata{}, head)
   228  	require.Equal(t, kbfsmd.Revision(40), head.Revision())
   229  
   230  	// (6a) try to get unmerged range
   231  	rmdses, err := mdOps.GetUnmergedRange(ctx, id, bid, 1, 100)
   232  	require.NoError(t, err)
   233  	require.Equal(t, 33, len(rmdses))
   234  	for i := kbfsmd.Revision(8); i < 41; i++ {
   235  		require.Equal(t, i, rmdses[i-8].Revision())
   236  	}
   237  
   238  	// (6b) try to get unmerged range subset.
   239  	rmdses, err = mdOps.GetUnmergedRange(ctx, id, bid, 7, 14)
   240  	require.NoError(t, err)
   241  	require.Equal(t, 7, len(rmdses))
   242  	for i := kbfsmd.Revision(8); i <= 14; i++ {
   243  		require.Equal(t, i, rmdses[i-8].Revision())
   244  	}
   245  
   246  	// (7) resolve the branch
   247  	_, err = mdOps.ResolveBranch(
   248  		ctx, id, bid, nil, resolveMD, session.VerifyingKey, nil)
   249  	require.NoError(t, err)
   250  
   251  	// (8) verify head is pruned
   252  	head, err = mdOps.GetUnmergedForTLF(ctx, id, kbfsmd.NullBranchID)
   253  	require.NoError(t, err)
   254  	require.Equal(t, ImmutableRootMetadata{}, head)
   255  
   256  	// (9) verify revision history is pruned
   257  	rmdses, err = mdOps.GetUnmergedRange(ctx, id, kbfsmd.NullBranchID, 1, 100)
   258  	require.NoError(t, err)
   259  	require.Equal(t, 0, len(rmdses))
   260  
   261  	// (10) check for proper merged head
   262  	head, err = mdOps.GetForTLF(ctx, id, nil)
   263  	require.NoError(t, err)
   264  	require.NotEqual(t, ImmutableRootMetadata{}, head)
   265  	require.Equal(t, kbfsmd.Revision(8), head.Revision())
   266  
   267  	// (11) try to get merged range
   268  	rmdses, err = mdOps.GetRange(ctx, id, 1, 100, nil)
   269  	require.NoError(t, err)
   270  	require.Equal(t, 8, len(rmdses))
   271  	for i := kbfsmd.Revision(1); i <= 8; i++ {
   272  		require.Equal(t, i, rmdses[i-1].Revision())
   273  	}
   274  }
   275  
   276  // TODO: Add a test for GetRange where the server has an overlapping
   277  // range with the journal.
   278  
   279  func TestJournalMDOpsPutUnmerged(t *testing.T) {
   280  	tempdir, ctx, cancel, config, _, jManager := setupJournalMDOpsTest(t)
   281  	defer teardownJournalMDOpsTest(ctx, t, tempdir, cancel, config)
   282  
   283  	session, err := config.KBPKI().GetCurrentSession(ctx)
   284  	require.NoError(t, err)
   285  
   286  	bh, err := tlf.MakeHandle(
   287  		[]keybase1.UserOrTeamID{session.UID.AsUserOrTeam()}, nil, nil, nil, nil)
   288  	require.NoError(t, err)
   289  
   290  	h, err := tlfhandle.MakeHandle(
   291  		ctx, bh, bh.Type(), config.KBPKI(), config.KBPKI(), nil,
   292  		keybase1.OfflineAvailability_NONE)
   293  	require.NoError(t, err)
   294  
   295  	mdOps := jManager.mdOps()
   296  
   297  	id, err := mdOps.GetIDForHandle(ctx, h)
   298  	require.NoError(t, err)
   299  	require.NotEqual(t, tlf.NullID, id)
   300  	irmd, err := mdOps.GetForTLF(ctx, id, nil)
   301  	require.NoError(t, err)
   302  	require.Equal(t, ImmutableRootMetadata{}, irmd)
   303  
   304  	err = jManager.Enable(ctx, id, nil, TLFJournalBackgroundWorkPaused)
   305  	require.NoError(t, err)
   306  
   307  	rmd := makeMDForJournalMDOpsTest(t, config, id, h, kbfsmd.Revision(2))
   308  	rmd.SetPrevRoot(kbfsmd.FakeID(1))
   309  	rmd.SetBranchID(kbfsmd.FakeBranchID(1))
   310  
   311  	_, err = mdOps.PutUnmerged(ctx, rmd, session.VerifyingKey, nil)
   312  	require.NoError(t, err)
   313  }
   314  
   315  func TestJournalMDOpsPutUnmergedError(t *testing.T) {
   316  	tempdir, ctx, cancel, config, _, jManager := setupJournalMDOpsTest(t)
   317  	defer teardownJournalMDOpsTest(ctx, t, tempdir, cancel, config)
   318  
   319  	session, err := config.KBPKI().GetCurrentSession(ctx)
   320  	require.NoError(t, err)
   321  
   322  	bh, err := tlf.MakeHandle(
   323  		[]keybase1.UserOrTeamID{session.UID.AsUserOrTeam()}, nil, nil, nil, nil)
   324  	require.NoError(t, err)
   325  
   326  	h, err := tlfhandle.MakeHandle(
   327  		ctx, bh, bh.Type(), config.KBPKI(), config.KBPKI(), nil,
   328  		keybase1.OfflineAvailability_NONE)
   329  	require.NoError(t, err)
   330  
   331  	mdOps := jManager.mdOps()
   332  
   333  	id, err := mdOps.GetIDForHandle(ctx, h)
   334  	require.NoError(t, err)
   335  	require.NotEqual(t, tlf.NullID, id)
   336  	irmd, err := mdOps.GetForTLF(ctx, id, nil)
   337  	require.NoError(t, err)
   338  	require.Equal(t, ImmutableRootMetadata{}, irmd)
   339  
   340  	err = jManager.Enable(ctx, id, nil, TLFJournalBackgroundWorkPaused)
   341  	require.NoError(t, err)
   342  
   343  	rmd := makeMDForJournalMDOpsTest(t, config, id, h, kbfsmd.Revision(1))
   344  
   345  	_, err = mdOps.PutUnmerged(ctx, rmd, session.VerifyingKey, nil)
   346  	require.Error(t, err, "Unmerged put with rmd.BID() == j.branchID == kbfsmd.NullBranchID")
   347  }
   348  
   349  func TestJournalMDOpsLocalSquashBranch(t *testing.T) {
   350  	tempdir, ctx, cancel, config, _, jManager := setupJournalMDOpsTest(t)
   351  	defer teardownJournalMDOpsTest(ctx, t, tempdir, cancel, config)
   352  
   353  	session, err := config.KBPKI().GetCurrentSession(ctx)
   354  	require.NoError(t, err)
   355  
   356  	bh, err := tlf.MakeHandle(
   357  		[]keybase1.UserOrTeamID{session.UID.AsUserOrTeam()}, nil, nil, nil, nil)
   358  	require.NoError(t, err)
   359  
   360  	h, err := tlfhandle.MakeHandle(
   361  		ctx, bh, bh.Type(), config.KBPKI(), config.KBPKI(), nil,
   362  		keybase1.OfflineAvailability_NONE)
   363  	require.NoError(t, err)
   364  
   365  	mdOps := jManager.mdOps()
   366  	id, err := mdOps.GetIDForHandle(ctx, h)
   367  	require.NoError(t, err)
   368  	irmd, err := mdOps.GetForTLF(ctx, id, nil)
   369  	require.NoError(t, err)
   370  	require.Equal(t, ImmutableRootMetadata{}, irmd)
   371  	err = jManager.Enable(ctx, id, nil, TLFJournalBackgroundWorkPaused)
   372  	require.NoError(t, err)
   373  
   374  	tlfJournal, ok := jManager.getTLFJournal(id, nil)
   375  	require.True(t, ok)
   376  
   377  	// Prepare the md journal to have a leading local squash revision.
   378  	firstRevision := kbfsmd.Revision(1)
   379  	initialRmd := makeMDForJournalMDOpsTest(t, config, id, h, firstRevision)
   380  	j := tlfJournal.mdJournal
   381  	initialMdID, _, err := j.put(ctx, config.Crypto(), config.KeyManager(),
   382  		config.BlockSplitter(), initialRmd, true)
   383  	require.NoError(t, err)
   384  
   385  	mdCount := 10
   386  	rmd := initialRmd
   387  	mdID := initialMdID
   388  	// Put several MDs after a local squash
   389  	for i := 0; i < mdCount; i++ {
   390  		rmd, err = rmd.MakeSuccessor(ctx, config.MetadataVersion(),
   391  			config.Codec(), config.KeyManager(),
   392  			config.KBPKI(), config.KBPKI(), config, mdID, true)
   393  		require.NoError(t, err)
   394  		mdID, _, err = j.put(ctx, config.Crypto(), config.KeyManager(),
   395  			config.BlockSplitter(), rmd, false)
   396  		require.NoError(t, err)
   397  	}
   398  
   399  	mdcache := NewMDCacheStandard(10)
   400  	err = j.convertToBranch(
   401  		ctx, kbfsmd.PendingLocalSquashBranchID, config.Crypto(), config.Codec(),
   402  		id, mdcache)
   403  	require.NoError(t, err)
   404  
   405  	// The merged head should still be the initial rmd, because we
   406  	// marked it as a squash and it shouldn't have gotten converted.
   407  	irmd, err = mdOps.GetForTLF(ctx, id, nil)
   408  	require.NoError(t, err)
   409  	require.Equal(t, initialMdID, irmd.mdID)
   410  	require.Equal(t, firstRevision, irmd.Revision())
   411  
   412  	// The unmerged head should be the last MD we put, converted to a
   413  	// branch.
   414  	irmd, err = mdOps.GetUnmergedForTLF(ctx, id, kbfsmd.PendingLocalSquashBranchID)
   415  	require.NoError(t, err)
   416  	require.Equal(t, rmd.Revision(), irmd.Revision())
   417  	require.Equal(t, kbfsmd.PendingLocalSquashBranchID, irmd.BID())
   418  
   419  	// The merged range should just be the initial MD.
   420  	stopRevision := firstRevision + kbfsmd.Revision(mdCount*2)
   421  	irmds, err := mdOps.GetRange(ctx, id, firstRevision, stopRevision, nil)
   422  	require.NoError(t, err)
   423  	require.Len(t, irmds, 1)
   424  	require.Equal(t, initialMdID, irmds[0].mdID)
   425  	require.Equal(t, firstRevision, irmds[0].Revision())
   426  
   427  	irmds, err = mdOps.GetUnmergedRange(ctx, id, kbfsmd.PendingLocalSquashBranchID,
   428  		firstRevision, stopRevision)
   429  	require.NoError(t, err)
   430  	require.Len(t, irmds, mdCount)
   431  	require.Equal(t, firstRevision+kbfsmd.Revision(1), irmds[0].Revision())
   432  }