// CurrentSession uses the global session to find the session. If // the user isn't logged in, it returns ErrNoSession. func (h *SessionHandler) CurrentSession(_ context.Context, sessionID int) (keybase1.Session, error) { var s keybase1.Session var token string var username libkb.NormalizedUsername var uid keybase1.UID var deviceSubkey libkb.GenericKey var err error aerr := h.G().LoginState().Account(func(a *libkb.Account) { uid, username, token, deviceSubkey, err = a.UserInfo() }, "Service - SessionHandler - UserInfo") if aerr != nil { return s, aerr } if err != nil { if _, ok := err.(libkb.LoginRequiredError); ok { return s, ErrNoSession } return s, err } s.Uid = uid s.Username = username.String() s.Token = token s.DeviceSubkeyKid = deviceSubkey.GetKID() return s, nil }
func TestBasicMDUpdate(t *testing.T) { // simulate two users var userName1, userName2 libkb.NormalizedUsername = "******", "u2" config1, _, ctx := kbfsOpsConcurInit(t, userName1, userName2) defer CheckConfigAndShutdown(t, config1) config2 := ConfigAsUser(config1.(*ConfigLocal), userName2) defer CheckConfigAndShutdown(t, config2) name := userName1.String() + "," + userName2.String() kbfsOps1 := config1.KBFSOps() rootNode1, _, err := kbfsOps1.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Couldn't create folder: %v", err) } kbfsOps2 := config2.KBFSOps() rootNode2, _, err := kbfsOps2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Couldn't get root: %v", err) } _, statusChan, err := kbfsOps2.Status(ctx, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't get status") } // user 1 creates a file _, _, err = kbfsOps1.CreateFile(ctx, rootNode1, "a", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } err = kbfsOps2.SyncFromServer(ctx, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } entries, err := kbfsOps2.GetDirChildren(ctx, rootNode2) if err != nil { t.Fatalf("User 2 couldn't see the root dir: %v", err) } if len(entries) != 1 { t.Fatalf("User 2 sees wrong number of entries in root dir: %d vs 1", len(entries)) } if _, ok := entries["a"]; !ok { t.Fatalf("User 2 doesn't see file a") } // The status should have fired as well (though in this case the // writer is the same as before) <-statusChan checkStatus(t, ctx, kbfsOps1, false, userName1, nil, rootNode1.GetFolderBranch(), "Node 1") checkStatus(t, ctx, kbfsOps2, false, userName1, nil, rootNode2.GetFolderBranch(), "Node 2") }
// Same as TestCRMergedChainsSimple, but the two users make changes in // different, unrelated subdirectories, forcing the resolver to use // mostly original block pointers when constructing the merged path. func TestCRMergedChainsDifferentDirectories(t *testing.T) { var userName1, userName2 libkb.NormalizedUsername = "******", "u2" config1, uid1, ctx := kbfsOpsConcurInit(t, userName1, userName2) defer CheckConfigAndShutdown(t, config1) config2 := ConfigAsUser(config1.(*ConfigLocal), userName2) defer CheckConfigAndShutdown(t, config2) _, uid2, err := config2.KBPKI().GetCurrentUserInfo(ctx) if err != nil { t.Fatal(err) } name := userName1.String() + "," + userName2.String() configs := make(map[keybase1.UID]Config) configs[uid1] = config1 configs[uid2] = config2 nodesA := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"dirA"}) dirA1 := nodesA[uid1] nodesB := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"dirB"}) dirB1 := nodesB[uid1] dirB2 := nodesB[uid2] fb := dirA1.GetFolderBranch() // pause user 2 _, err = DisableUpdatesForTesting(config2, fb) if err != nil { t.Fatalf("Can't disable updates for user 2: %v", err) } // user1 makes a file in dir A _, _, err = config1.KBFSOps().CreateFile(ctx, dirA1, "file1", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } cr1 := testCRGetCROrBust(t, config1, fb) cr2 := testCRGetCROrBust(t, config2, fb) cr2.Shutdown() // user2 makes a file in dir B _, _, err = config2.KBFSOps().CreateFile(ctx, dirB2, "file2", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // Now step through conflict resolution manually for user 2 mergedPaths := make(map[BlockPointer]path) expectedUnmergedPath := cr2.fbo.nodeCache.PathFromNode(dirB2) mergedPath := cr1.fbo.nodeCache.PathFromNode(dirB1) mergedPaths[expectedUnmergedPath.tailPointer()] = mergedPath expectedActions := map[BlockPointer]crActionList{ mergedPath.tailPointer(): {©UnmergedEntryAction{ "file2", "file2", "", false, false, DirEntry{}, nil}}, } testCRCheckPathsAndActions(t, cr2, []path{expectedUnmergedPath}, mergedPaths, nil, expectedActions) }
// Tests that conflict resolution detects and can fix rename cycles. func TestCRMergedChainsRenameCycleSimple(t *testing.T) { var userName1, userName2 libkb.NormalizedUsername = "******", "u2" config1, uid1, ctx := kbfsOpsConcurInit(t, userName1, userName2) defer CheckConfigAndShutdown(t, config1) config2 := ConfigAsUser(config1.(*ConfigLocal), userName2) defer CheckConfigAndShutdown(t, config2) _, uid2, err := config2.KBPKI().GetCurrentUserInfo(ctx) if err != nil { t.Fatal(err) } name := userName1.String() + "," + userName2.String() configs := make(map[keybase1.UID]Config) configs[uid1] = config1 configs[uid2] = config2 nodesRoot := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"root"}) dirRoot1 := nodesRoot[uid1] dirRoot2 := nodesRoot[uid2] fb := dirRoot1.GetFolderBranch() nodesA := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"root", "dirA"}) dirA1 := nodesA[uid1] nodesB := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"root", "dirB"}) dirB1 := nodesB[uid1] dirB2 := nodesB[uid2] cr1 := testCRGetCROrBust(t, config1, fb) cr2 := testCRGetCROrBust(t, config2, fb) cr2.Shutdown() dirRootPtr := cr2.fbo.nodeCache.PathFromNode(dirRoot2).tailPointer() // pause user 2 _, err = DisableUpdatesForTesting(config2, fb) if err != nil { t.Fatalf("Can't disable updates for user 2: %v", err) } // user1 moves dirB into dirA err = config1.KBFSOps().Rename(ctx, dirRoot1, "dirB", dirA1, "dirB") if err != nil { t.Fatalf("Couldn't make dir: %v", err) } // user2 moves dirA into dirB err = config2.KBFSOps().Rename(ctx, dirRoot2, "dirA", dirB2, "dirA") if err != nil { t.Fatalf("Couldn't make dir: %v", err) } // Now step through conflict resolution manually for user 2 mergedPaths := make(map[BlockPointer]path) // root unmergedPathRoot := cr2.fbo.nodeCache.PathFromNode(dirRoot2) mergedPathRoot := cr1.fbo.nodeCache.PathFromNode(dirRoot1) mergedPaths[unmergedPathRoot.tailPointer()] = mergedPathRoot unmergedPathB := cr2.fbo.nodeCache.PathFromNode(dirB2) mergedPathB := cr1.fbo.nodeCache.PathFromNode(dirB1) mergedPaths[unmergedPathB.tailPointer()] = mergedPathB ro := newRmOp("dirA", dirRootPtr) ro.Dir.Ref = unmergedPathRoot.tailPointer() ro.dropThis = true ro.setWriterInfo(writerInfo{name: "u2"}) ro.setFinalPath(unmergedPathRoot) expectedActions := map[BlockPointer]crActionList{ mergedPathRoot.tailPointer(): {&dropUnmergedAction{ro}}, mergedPathB.tailPointer(): {©UnmergedEntryAction{ "dirA", "dirA", "./../", false, false, DirEntry{}, nil}}, } testCRCheckPathsAndActions(t, cr2, []path{unmergedPathRoot, unmergedPathB}, mergedPaths, nil, expectedActions) }
func TestRekeyQueueBasic(t *testing.T) { var u1, u2, u3, u4 libkb.NormalizedUsername = "******", "u2", "u3", "u4" config1, _, ctx := kbfsOpsConcurInit(t, u1, u2, u3, u4) defer config1.Shutdown() config2 := ConfigAsUser(config1.(*ConfigLocal), u2) defer config2.Shutdown() _, uid2, err := config2.KBPKI().GetCurrentUserInfo(context.Background()) if err != nil { t.Fatal(err) } config3 := ConfigAsUser(config1.(*ConfigLocal), u3) defer config3.Shutdown() _, _, err = config3.KBPKI().GetCurrentUserInfo(context.Background()) if err != nil { t.Fatal(err) } config4 := ConfigAsUser(config1.(*ConfigLocal), u4) defer config4.Shutdown() _, _, err = config4.KBPKI().GetCurrentUserInfo(context.Background()) if err != nil { t.Fatal(err) } kbfsOps1 := config1.KBFSOps() var names []string // Create a few shared folders for i := 0; i < 3; i++ { writers := []string{u1.String(), u2.String()} if i > 0 { writers = append(writers, u3.String()) } if i > 1 { writers = append(writers, u4.String()) } name := strings.Join(writers, ",") names = append(names, name) // user 1 creates the directory rootNode1 := GetRootNodeOrBust(t, config1, name, false) // user 1 creates a file _, _, err = kbfsOps1.CreateFile(ctx, rootNode1, "a", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } } // Create a new device for user 2 config2Dev2 := ConfigAsUser(config1.(*ConfigLocal), u2) defer config2Dev2.Shutdown() AddDeviceForLocalUserOrBust(t, config1, uid2) AddDeviceForLocalUserOrBust(t, config2, uid2) devIndex := AddDeviceForLocalUserOrBust(t, config2Dev2, uid2) SwitchDeviceForLocalUserOrBust(t, config2Dev2, devIndex) // user 2 should be unable to read the data now since its device // wasn't registered when the folder was originally created. for _, name := range names { _, err := GetRootNodeForTest(config2Dev2, name, false) if _, ok := err.(NeedSelfRekeyError); !ok { t.Fatalf("Got unexpected error when reading with new key: %v", err) } } var rekeyChannels []<-chan error // now user 1 should rekey via its rekey worker for _, name := range names { rootNode1 := GetRootNodeOrBust(t, config1, name, false) // queue it for rekey c := config1.RekeyQueue().Enqueue(rootNode1.GetFolderBranch().Tlf) rekeyChannels = append(rekeyChannels, c) } // listen for all of the rekey results for _, c := range rekeyChannels { if err := <-c; err != nil { t.Fatal(err) } } // user 2's new device should be able to read now for _, name := range names { _ = GetRootNodeOrBust(t, config2Dev2, name, false) } }
// Two devices conflict when revoking a 3rd device. // Test that after this both can still read the latest version of the folder. func TestKeyManagerRekeyAddAndRevokeDeviceWithConflict(t *testing.T) { var u1, u2 libkb.NormalizedUsername = "******", "u2" config1, _, ctx := kbfsOpsConcurInit(t, u1, u2) defer CheckConfigAndShutdown(t, config1) config2 := ConfigAsUser(config1.(*ConfigLocal), u2) defer CheckConfigAndShutdown(t, config2) uid2, err := config2.KBPKI().GetCurrentUID(context.Background()) if err != nil { t.Fatal(err) } // create a shared folder name := u1.String() + "," + u2.String() kbfsOps1 := config1.KBFSOps() rootNode1, _, err := kbfsOps1.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Couldn't create folder: %v", err) } // user 1 creates a file _, _, err = kbfsOps1.CreateFile(ctx, rootNode1, "a", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } config2Dev2 := ConfigAsUser(config1.(*ConfigLocal), u2) defer CheckConfigAndShutdown(t, config2Dev2) // give user 2 a new device AddDeviceForLocalUserOrBust(t, config1, uid2) AddDeviceForLocalUserOrBust(t, config2, uid2) devIndex := AddDeviceForLocalUserOrBust(t, config2Dev2, uid2) SwitchDeviceForLocalUserOrBust(t, config2Dev2, devIndex) // user 2 should be unable to read the data now since its device // wasn't registered when the folder was originally created. kbfsOps2Dev2 := config2Dev2.KBFSOps() root2Dev2, _, err := kbfsOps2Dev2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if _, ok := err.(ReadAccessError); !ok { t.Fatalf("Got unexpected error when reading with new key: %v", err) } // now user 1 should rekey err = kbfsOps1.Rekey(ctx, rootNode1.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't rekey: %v", err) } // this device should be able to read now root2Dev2, _, err = kbfsOps2Dev2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Got unexpected error after rekey: %v", err) } // Now revoke the original user 2 device RevokeDeviceForLocalUserOrBust(t, config1, uid2, 0) RevokeDeviceForLocalUserOrBust(t, config2Dev2, uid2, 0) // disable updates on user 1 c, err := DisableUpdatesForTesting(config1, rootNode1.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't disable updates: %v", err) } // rekey again but with user 2 device 2 err = kbfsOps2Dev2.Rekey(ctx, root2Dev2.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't rekey: %v", err) } // have user 1 also try to rekey but fail due to conflict err = kbfsOps1.Rekey(ctx, rootNode1.GetFolderBranch().Tlf) if _, isConflict := err.(MDServerErrorConflictRevision); !isConflict { t.Fatalf("Expected failure due to conflict") } // device 1 re-enables updates c <- struct{}{} err = kbfsOps1.SyncFromServer(ctx, rootNode1.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } err = kbfsOps2Dev2.SyncFromServer(ctx, root2Dev2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } // force re-encryption of the root dir _, _, err = kbfsOps2Dev2.CreateFile(ctx, root2Dev2, "b", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // device 1 should still work err = kbfsOps1.SyncFromServer(ctx, rootNode1.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } rootNode1, _, err = kbfsOps1.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Got unexpected error after rekey: %v", err) } children, err := kbfsOps1.GetDirChildren(ctx, rootNode1) if _, ok := children["b"]; !ok { t.Fatalf("Device 1 couldn't see the new dir entry") } }
// Tests that multiple users can write to the same file sequentially // without any problems. func TestMultiUserWrite(t *testing.T) { // simulate two users var userName1, userName2 libkb.NormalizedUsername = "******", "u2" config1, _, ctx := kbfsOpsConcurInit(t, userName1, userName2) defer CheckConfigAndShutdown(t, config1) config2 := ConfigAsUser(config1.(*ConfigLocal), userName2) defer CheckConfigAndShutdown(t, config2) name := userName1.String() + "," + userName2.String() // user1 creates a file in a shared dir rootNode1 := GetRootNodeOrBust(t, config1, name, false) kbfsOps1 := config1.KBFSOps() _, _, err := kbfsOps1.CreateFile(ctx, rootNode1, "a", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // then user2 write to the file rootNode2 := GetRootNodeOrBust(t, config2, name, false) kbfsOps2 := config2.KBFSOps() fileNode2, _, err := kbfsOps2.Lookup(ctx, rootNode2, "a") if err != nil { t.Fatalf("Couldn't lookup file: %v", err) } data2 := []byte{2} err = kbfsOps2.Write(ctx, fileNode2, data2, 0) if err != nil { t.Fatalf("Couldn't write file: %v", err) } // Write twice to make sure that multiple write operations within // a sync work when the writer is changing. err = kbfsOps2.Write(ctx, fileNode2, data2, 0) if err != nil { t.Fatalf("Couldn't write file: %v", err) } err = kbfsOps2.Sync(ctx, fileNode2) if err != nil { t.Fatalf("Couldn't sync file: %v", err) } readAndCompareData(t, config2, ctx, name, data2, userName2) // A second write by the same user data3 := []byte{3} err = kbfsOps2.Write(ctx, fileNode2, data3, 0) if err != nil { t.Fatalf("Couldn't write file: %v", err) } err = kbfsOps2.Sync(ctx, fileNode2) if err != nil { t.Fatalf("Couldn't sync file: %v", err) } readAndCompareData(t, config2, ctx, name, data3, userName2) err = kbfsOps1.SyncFromServerForTesting(ctx, rootNode1.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } readAndCompareData(t, config1, ctx, name, data3, userName2) }
// Test that quota reclamation works for a simple case where the user // does a few updates, then lets quota reclamation run, and we make // sure that all historical blocks have been deleted. func TestQuotaReclamationSimple(t *testing.T) { var userName libkb.NormalizedUsername = "******" config, _, ctx := kbfsOpsInitNoMocks(t, userName) defer CheckConfigAndShutdown(t, config) clock, now := newTestClockAndTimeNow() config.SetClock(clock) rootNode := GetRootNodeOrBust(t, config, userName.String(), false) kbfsOps := config.KBFSOps() _, _, err := kbfsOps.CreateDir(ctx, rootNode, "a") if err != nil { t.Fatalf("Couldn't create dir: %v", err) } err = kbfsOps.RemoveDir(ctx, rootNode, "a") if err != nil { t.Fatalf("Couldn't remove dir: %v", err) } // Wait for outstanding archives err = kbfsOps.SyncFromServerForTesting(ctx, rootNode.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } // Make sure no blocks are deleted before there's a new-enough update. bserverLocal, ok := config.BlockServer().(*BlockServerLocal) if !ok { t.Fatalf("Bad block server") } preQR1Blocks, err := bserverLocal.getAll(rootNode.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't get blocks: %v", err) } ops := kbfsOps.(*KBFSOpsStandard).getOpsByNode(ctx, rootNode) ops.fbm.forceQuotaReclamation() err = ops.fbm.waitForQuotaReclamations(ctx) if err != nil { t.Fatalf("Couldn't wait for QR: %v", err) } postQR1Blocks, err := bserverLocal.getAll(rootNode.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't get blocks: %v", err) } if !reflect.DeepEqual(preQR1Blocks, postQR1Blocks) { t.Fatalf("Blocks deleted too early (%v vs %v)!", preQR1Blocks, postQR1Blocks) } // Increase the time and make a new revision, but don't run quota // reclamation yet. clock.Set(now.Add(2 * config.QuotaReclamationMinUnrefAge())) _, _, err = kbfsOps.CreateDir(ctx, rootNode, "b") if err != nil { t.Fatalf("Couldn't create dir: %v", err) } preQR2Blocks, err := bserverLocal.getAll(rootNode.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't get blocks: %v", err) } ops.fbm.forceQuotaReclamation() err = ops.fbm.waitForQuotaReclamations(ctx) if err != nil { t.Fatalf("Couldn't wait for QR: %v", err) } postQR2Blocks, err := bserverLocal.getAll(rootNode.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't get blocks: %v", err) } if pre, post := totalBlockRefs(preQR2Blocks), totalBlockRefs(postQR2Blocks); post >= pre { t.Errorf("Blocks didn't shrink after reclamation: pre: %d, post %d", pre, post) } }
// Test that a single quota reclamation run doesn't try to reclaim too // much quota at once. func TestQuotaReclamationIncrementalReclamation(t *testing.T) { var userName libkb.NormalizedUsername = "******" config, _, ctx := kbfsOpsInitNoMocks(t, userName) defer CheckConfigAndShutdown(t, config) now := time.Now() var clock TestClock clock.Set(now) config.SetClock(&clock) rootNode := GetRootNodeOrBust(t, config, userName.String(), false) // Do a bunch of operations. kbfsOps := config.KBFSOps() for i := 0; i < numPointersPerGCThreshold; i++ { _, _, err := kbfsOps.CreateDir(ctx, rootNode, "a") if err != nil { t.Fatalf("Couldn't create dir: %v", err) } err = kbfsOps.RemoveDir(ctx, rootNode, "a") if err != nil { t.Fatalf("Couldn't remove dir: %v", err) } } // Increase the time, and make sure that there is still more than // one block in the history clock.Set(now.Add(2 * config.QuotaReclamationMinUnrefAge())) // Run it. ops := kbfsOps.(*KBFSOpsStandard).getOpsByNode(ctx, rootNode) ops.fbm.forceQuotaReclamation() err := ops.fbm.waitForQuotaReclamations(ctx) if err != nil { t.Fatalf("Couldn't wait for QR: %v", err) } bserverLocal, ok := config.BlockServer().(*BlockServerLocal) if !ok { t.Fatalf("Bad block server") } blocks, err := bserverLocal.getAll(rootNode.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't get blocks: %v", err) } b := totalBlockRefs(blocks) if b <= 1 { t.Errorf("Too many blocks left after first QR: %d", b) } // Now let it run to completion for b > 1 { ops.fbm.forceQuotaReclamation() err = ops.fbm.waitForQuotaReclamations(ctx) if err != nil { t.Fatalf("Couldn't wait for QR: %v", err) } blocks, err := bserverLocal.getAll(rootNode.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't get blocks: %v", err) } oldB := b b = totalBlockRefs(blocks) if b >= oldB { t.Fatalf("Blocks didn't shrink after reclamation: %d vs. %d", b, oldB) } } }
// Tests that two users can make independent writes while forked, and // conflict resolution will merge them correctly and the rekey bit is // preserved until rekey. func TestBasicCRFileConflictWithRekey(t *testing.T) { // simulate two users var userName1, userName2 libkb.NormalizedUsername = "******", "u2" config1, _, ctx := kbfsOpsConcurInit(t, userName1, userName2) defer CheckConfigAndShutdown(t, config1) config1.MDServer().DisableRekeyUpdatesForTesting() config2 := ConfigAsUser(config1.(*ConfigLocal), userName2) defer CheckConfigAndShutdown(t, config2) uid2, err := config2.KBPKI().GetCurrentUID(context.Background()) if err != nil { t.Fatal(err) } config2.MDServer().DisableRekeyUpdatesForTesting() now := time.Now() config2.SetClock(&TestClock{now}) name := userName1.String() + "," + userName2.String() // user1 creates a file in a shared dir kbfsOps1 := config1.KBFSOps() rootNode1, _, err := kbfsOps1.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Couldn't create folder: %v", err) } dirA1, _, err := kbfsOps1.CreateDir(ctx, rootNode1, "a") if err != nil { t.Fatalf("Couldn't create dir: %v", err) } fileB1, _, err := kbfsOps1.CreateFile(ctx, dirA1, "b", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // look it up on user2 kbfsOps2 := config2.KBFSOps() rootNode2, _, err := kbfsOps2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Couldn't create folder: %v", err) } dirA2, _, err := kbfsOps2.Lookup(ctx, rootNode2, "a") if err != nil { t.Fatalf("Couldn't lookup dir: %v", err) } fileB2, _, err := kbfsOps2.Lookup(ctx, dirA2, "b") if err != nil { t.Fatalf("Couldn't lookup file: %v", err) } config2Dev2 := ConfigAsUser(config1.(*ConfigLocal), userName2) // we don't check the config because this device can't read all of the md blocks. defer config2Dev2.Shutdown() config2Dev2.MDServer().DisableRekeyUpdatesForTesting() // Now give u2 a new device. The configs don't share a Keybase // Daemon so we have to do it in all places. AddDeviceForLocalUserOrBust(t, config1, uid2) AddDeviceForLocalUserOrBust(t, config2, uid2) devIndex := AddDeviceForLocalUserOrBust(t, config2Dev2, uid2) SwitchDeviceForLocalUserOrBust(t, config2Dev2, devIndex) // user2 device 2 should be unable to read the data now since its device // wasn't registered when the folder was originally created. kbfsOps2Dev2 := config2Dev2.KBFSOps() _, _, err = kbfsOps2Dev2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if _, ok := err.(ReadAccessError); !ok { t.Fatalf("Got unexpected error when reading with new key: %v", err) } // User 2 syncs err = kbfsOps2.SyncFromServer(ctx, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } // disable updates on user2 c, err := DisableUpdatesForTesting(config2, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't disable updates: %v", err) } // User 1 writes the file data1 := []byte{1, 2, 3, 4, 5} err = kbfsOps1.Write(ctx, fileB1, data1, 0) if err != nil { t.Fatalf("Couldn't write file: %v", err) } err = kbfsOps1.Sync(ctx, fileB1) if err != nil { t.Fatalf("Couldn't sync file: %v", err) } // User 2 dev 2 should set the rekey bit err = kbfsOps2Dev2.Rekey(ctx, rootNode2.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't set rekey bit: %v", err) } // User 1 syncs err = kbfsOps1.SyncFromServer(ctx, rootNode1.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } // User 2 makes a new different file data2 := []byte{5, 4, 3, 2, 1} err = kbfsOps2.Write(ctx, fileB2, data2, 0) if err != nil { t.Fatalf("Couldn't write file: %v", err) } err = kbfsOps2.Sync(ctx, fileB2) if err != nil { t.Fatalf("Couldn't sync file: %v", err) } // re-enable updates, and wait for CR to complete. // this should also cause a rekey of the folder. c <- struct{}{} err = kbfsOps2.SyncFromServer(ctx, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } // wait for the rekey to happen waitForRekey(t, config2, rootNode2.GetFolderBranch().Tlf) err = kbfsOps1.SyncFromServer(ctx, rootNode1.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } err = kbfsOps2Dev2.SyncFromServer(ctx, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } // look it up on user 2 dev 2 rootNode2Dev2, _, err := kbfsOps2Dev2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Couldn't create folder: %v", err) } dirA2Dev2, _, err := kbfsOps2Dev2.Lookup(ctx, rootNode2Dev2, "a") if err != nil { t.Fatalf("Couldn't lookup dir: %v", err) } // Make sure they all see the same set of children expectedChildren := []string{ "b", "b.conflict.u2." + now.Format(time.RFC3339Nano), } children1, err := kbfsOps1.GetDirChildren(ctx, dirA1) if err != nil { t.Fatalf("Couldn't get children: %v", err) } children2, err := kbfsOps2.GetDirChildren(ctx, dirA2) if err != nil { t.Fatalf("Couldn't get children: %v", err) } children2Dev2, err := kbfsOps2Dev2.GetDirChildren(ctx, dirA2Dev2) if err != nil { t.Fatalf("Couldn't get children: %v", err) } if g, e := len(children1), len(expectedChildren); g != e { t.Errorf("Wrong number of children: %d vs %d", g, e) } for _, child := range expectedChildren { if _, ok := children1[child]; !ok { t.Errorf("Couldn't find child %s", child) } } if !reflect.DeepEqual(children1, children2) { t.Fatalf("Users 1 and 2 see different children: %v vs %v", children1, children2) } if !reflect.DeepEqual(children2, children2Dev2) { t.Fatalf("User 2 device 1 and 2 see different children: %v vs %v", children2, children2Dev2) } }
// Tests that multiple users can write to the same file sequentially // without any problems. func TestMultiUserWrite(t *testing.T) { // simulate two users var userName1, userName2 libkb.NormalizedUsername = "******", "u2" config1, _, ctx := kbfsOpsConcurInit(t, userName1, userName2) defer CheckConfigAndShutdown(t, config1) config2 := ConfigAsUser(config1.(*ConfigLocal), userName2) defer CheckConfigAndShutdown(t, config2) name := userName1.String() + "," + userName2.String() // user1 creates a file in a shared dir kbfsOps1 := config1.KBFSOps() rootNode1, _, err := kbfsOps1.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Errorf("Couldn't create folder: %v", err) } _, _, err = kbfsOps1.CreateFile(ctx, rootNode1, "a", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // then user2 write to the file kbfsOps2 := config2.KBFSOps() rootNode2, _, err := kbfsOps2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Errorf("Couldn't create folder: %v", err) } fileNode2, _, err := kbfsOps2.Lookup(ctx, rootNode2, "a") if err != nil { t.Fatalf("Couldn't lookup file: %v", err) } data2 := []byte{2} err = kbfsOps2.Write(ctx, fileNode2, data2, 0) if err != nil { t.Fatalf("Couldn't write file: %v", err) } // The writer should be user 2, even before the Sync ops := kbfsOps2.(*KBFSOpsStandard).getOpsByNode(fileNode2) de, err := ops.statEntry(ctx, fileNode2) if err != nil { t.Fatalf("Couldn't lookup file: %v", err) } uid2, err := config2.KBPKI().GetCurrentUID(context.Background()) if err != nil { t.Fatal(err) } if de.GetWriter() != uid2 { t.Errorf("After user 2's first write, Writer is wrong: %v", de.GetWriter()) } err = kbfsOps2.Sync(ctx, fileNode2) if err != nil { t.Fatalf("Couldn't sync file: %v", err) } data3 := []byte{3} err = kbfsOps2.Write(ctx, fileNode2, data3, 0) if err != nil { t.Fatalf("Couldn't write file: %v", err) } err = kbfsOps2.Sync(ctx, fileNode2) if err != nil { t.Fatalf("Couldn't sync file: %v", err) } readAndCompareData(t, config2, ctx, name, data3, userName2) }
func testMultipleMDUpdates(t *testing.T, unembedChanges bool) { // simulate two users var userName1, userName2 libkb.NormalizedUsername = "******", "u2" config1, _, ctx := kbfsOpsConcurInit(t, userName1, userName2) defer CheckConfigAndShutdown(t, config1) config2 := ConfigAsUser(config1.(*ConfigLocal), userName2) defer CheckConfigAndShutdown(t, config2) if unembedChanges { bss1, ok1 := config1.BlockSplitter().(*BlockSplitterSimple) bss2, ok2 := config2.BlockSplitter().(*BlockSplitterSimple) if !ok1 || !ok2 { t.Fatalf("Couldn't convert BlockSplitters!") } bss1.blockChangeEmbedMaxSize = 3 bss2.blockChangeEmbedMaxSize = 3 } name := userName1.String() + "," + userName2.String() kbfsOps1 := config1.KBFSOps() rootNode1, _, err := kbfsOps1.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Couldn't create folder: %v", err) } // user 1 creates a file _, _, err = kbfsOps1.CreateFile(ctx, rootNode1, "a", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // user 2 looks up the directory (and sees the file) kbfsOps2 := config2.KBFSOps() rootNode2, _, err := kbfsOps2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Errorf("Couldn't get root: %v", err) } // now user 1 renames the old file, and creates a new one err = kbfsOps1.Rename(ctx, rootNode1, "a", rootNode1, "b") if err != nil { t.Fatalf("Couldn't rename file: %v", err) } _, _, err = kbfsOps1.CreateFile(ctx, rootNode1, "c", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } err = kbfsOps2.SyncFromServer(ctx, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } entries, err := kbfsOps2.GetDirChildren(ctx, rootNode2) if err != nil { t.Fatalf("User 2 couldn't see the root dir: %v", err) } if len(entries) != 2 { t.Fatalf("User 2 sees wrong number of entries in root dir: %d vs 2", len(entries)) } if _, ok := entries["b"]; !ok { t.Fatalf("User 2 doesn't see file b") } if _, ok := entries["c"]; !ok { t.Fatalf("User 2 doesn't see file c") } }
// A mix of the above TestCRMergedChains* tests, with various other // types of operations thrown in the mix (like u2 deleting unrelated // directories, etc). func TestCRMergedChainsComplex(t *testing.T) { var userName1, userName2 libkb.NormalizedUsername = "******", "u2" config1, uid1, ctx := kbfsOpsConcurInit(t, userName1, userName2) defer CheckConfigAndShutdown(t, config1) config2 := ConfigAsUser(config1.(*ConfigLocal), userName2) defer CheckConfigAndShutdown(t, config2) _, uid2, err := config2.KBPKI().GetCurrentUserInfo(ctx) if err != nil { t.Fatal(err) } // Setup: // /dirA/dirB/dirC // /dirA/dirB/dirD/file5 // /dirE/dirF // /dirG/dirH name := userName1.String() + "," + userName2.String() configs := make(map[keybase1.UID]Config) configs[uid1] = config1 configs[uid2] = config2 nodesA := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"dirA"}) dirA1 := nodesA[uid1] dirA2 := nodesA[uid2] fb := dirA1.GetFolderBranch() cr1 := testCRGetCROrBust(t, config1, fb) cr2 := testCRGetCROrBust(t, config2, fb) cr2.Shutdown() nodesB := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"dirA", "dirB"}) dirB1 := nodesB[uid1] dirB2 := nodesB[uid2] nodesC := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"dirA", "dirB", "dirC"}) dirC2 := nodesC[uid2] nodesD := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"dirA", "dirB", "dirD"}) dirD1 := nodesD[uid1] dirD2 := nodesD[uid2] nodesE := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"dirE"}) dirE1 := nodesE[uid1] nodesF := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"dirE", "dirF"}) dirF2 := nodesF[uid2] dirEPtr := cr2.fbo.nodeCache.PathFromNode(dirE1).tailPointer() dirFPtr := cr2.fbo.nodeCache.PathFromNode(dirF2).tailPointer() nodesG := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"dirG"}) dirG1 := nodesG[uid1] nodesH := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"dirG", "dirH"}) dirH1 := nodesH[uid1] dirH2 := nodesH[uid2] dirHPtr := cr1.fbo.nodeCache.PathFromNode(dirH1).tailPointer() _, _, err = config1.KBFSOps().CreateFile(ctx, dirD1, "file5", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } config2.KBFSOps().SyncFromServerForTesting(ctx, fb) // pause user 2 _, err = DisableUpdatesForTesting(config2, fb) if err != nil { t.Fatalf("Can't disable updates for user 2: %v", err) } // user 1: // touch /dirA/file1 // rm -rf /dirE/dirF // mv /dirG/dirH /dirA/dirI // // user 2: // mkdir /dirA/dirJ // touch /dirA/dirJ/file2 // touch /dirE/dirF/file3 // touch /dirA/dirB/dirC/file4 // mv /dirA/dirB/dirC/file4 /dirG/dirH/file4 // rm /dirA/dirB/dirD/file5 // rm -rf /dirA/dirB/dirD // user 1: _, _, err = config1.KBFSOps().CreateFile(ctx, dirA1, "file1", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } err = config1.KBFSOps().RemoveDir(ctx, dirE1, "dirF") if err != nil { t.Fatalf("Couldn't remove dir: %v", err) } err = config1.KBFSOps().Rename(ctx, dirG1, "dirH", dirA1, "dirI") if err != nil { t.Fatalf("Couldn't remove dir: %v", err) } // user2 dirJ2, _, err := config2.KBFSOps().CreateDir(ctx, dirA2, "dirJ") if err != nil { t.Fatalf("Couldn't create file: %v", err) } _, _, err = config2.KBFSOps().CreateFile(ctx, dirJ2, "file2", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } _, _, err = config2.KBFSOps().CreateFile(ctx, dirF2, "file3", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } _, _, err = config2.KBFSOps().CreateFile(ctx, dirC2, "file4", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } err = config2.KBFSOps().Rename(ctx, dirC2, "file4", dirH2, "file4") if err != nil { t.Fatalf("Couldn't remove dir: %v", err) } err = config2.KBFSOps().RemoveEntry(ctx, dirD2, "file5") if err != nil { t.Fatalf("Couldn't remove dir: %v", err) } err = config2.KBFSOps().RemoveDir(ctx, dirB2, "dirD") if err != nil { t.Fatalf("Couldn't remove dir: %v", err) } // Now step through conflict resolution manually for user 2 uPathA2 := cr2.fbo.nodeCache.PathFromNode(dirA2) uPathF2 := cr2.fbo.nodeCache.PathFromNode(dirF2) // no expected unmerged path for dirJ or dirC uPathH2 := cr2.fbo.nodeCache.PathFromNode(dirH2) // no expected unmerged path for dirD uPathB2 := cr2.fbo.nodeCache.PathFromNode(dirB2) mergedPaths := make(map[BlockPointer]path) // Both users updated A mergedPathA := cr1.fbo.nodeCache.PathFromNode(dirA1) mergedPaths[uPathA2.tailPointer()] = mergedPathA // user 1 deleted dirF, so reconstruct mergedPathF := cr1.fbo.nodeCache.PathFromNode(dirE1) mergedPathF.path = append(mergedPathF.path, pathNode{ BlockPointer: dirFPtr, Name: "dirF", }) mergedPaths[uPathF2.tailPointer()] = mergedPathF // dirH from user 2 is /dirA/dirI for user 1 mergedPathH := cr1.fbo.nodeCache.PathFromNode(dirA1) mergedPathH.path = append(mergedPathH.path, pathNode{ BlockPointer: dirHPtr, Name: "dirI", }) mergedPaths[uPathH2.tailPointer()] = mergedPathH // dirB wasn't touched by user 1 mergedPathB := cr1.fbo.nodeCache.PathFromNode(dirB1) mergedPaths[uPathB2.tailPointer()] = mergedPathB coF := newCreateOp("dirF", dirEPtr, Dir) mergedPathE := cr1.fbo.nodeCache.PathFromNode(dirE1) expectedActions := map[BlockPointer]crActionList{ mergedPathA.tailPointer(): {©UnmergedEntryAction{ "dirJ", "dirJ", "", false, false, DirEntry{}, nil}}, mergedPathE.tailPointer(): {©UnmergedEntryAction{ "dirF", "dirF", "", false, false, DirEntry{}, nil}}, mergedPathF.tailPointer(): {©UnmergedEntryAction{ "file3", "file3", "", false, false, DirEntry{}, nil}}, mergedPathH.tailPointer(): {©UnmergedEntryAction{ "file4", "file4", "", false, false, DirEntry{}, nil}}, mergedPathB.tailPointer(): {&rmMergedEntryAction{"dirD"}}, } // `rm file5` doesn't get an action because the parent directory // was deleted in the unmerged branch. testCRCheckPathsAndActions(t, cr2, []path{uPathA2, uPathF2, uPathH2, uPathB2}, mergedPaths, []*createOp{coF}, expectedActions) }
// Same as TestCRMergedChainsSimple, but u1 actually deletes some of // the subdirectories used by u2, forcing the resolver to generate // some recreateOps. func TestCRMergedChainsDeletedDirectories(t *testing.T) { var userName1, userName2 libkb.NormalizedUsername = "******", "u2" config1, uid1, ctx := kbfsOpsConcurInit(t, userName1, userName2) defer CheckConfigAndShutdown(t, config1) config2 := ConfigAsUser(config1.(*ConfigLocal), userName2) defer CheckConfigAndShutdown(t, config2) _, uid2, err := config2.KBPKI().GetCurrentUserInfo(ctx) if err != nil { t.Fatal(err) } name := userName1.String() + "," + userName2.String() configs := make(map[keybase1.UID]Config) configs[uid1] = config1 configs[uid2] = config2 nodesA := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"dirA"}) dirA1 := nodesA[uid1] fb := dirA1.GetFolderBranch() cr1 := testCRGetCROrBust(t, config1, fb) cr2 := testCRGetCROrBust(t, config2, fb) cr2.Shutdown() nodesB := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"dirA", "dirB"}) dirB1 := nodesB[uid1] nodesC := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"dirA", "dirB", "dirC"}) dirC2 := nodesC[uid2] dirAPtr := cr1.fbo.nodeCache.PathFromNode(dirA1).tailPointer() dirBPtr := cr1.fbo.nodeCache.PathFromNode(dirB1).tailPointer() dirCPtr := cr2.fbo.nodeCache.PathFromNode(dirC2).tailPointer() // pause user 2 _, err = DisableUpdatesForTesting(config2, fb) if err != nil { t.Fatalf("Can't disable updates for user 2: %v", err) } // user1 deletes dirB and dirC err = config1.KBFSOps().RemoveDir(ctx, dirB1, "dirC") if err != nil { t.Fatalf("Couldn't remove dir: %v", err) } err = config1.KBFSOps().RemoveDir(ctx, dirA1, "dirB") if err != nil { t.Fatalf("Couldn't remove dir: %v", err) } // user2 makes a file in dir C _, _, err = config2.KBFSOps().CreateFile(ctx, dirC2, "file2", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // Now step through conflict resolution manually for user 2 expectedUnmergedPath := cr2.fbo.nodeCache.PathFromNode(dirC2) // The merged path will consist of the latest root and dirA // components, plus the original blockpointers of the deleted // nodes. mergedPaths := make(map[BlockPointer]path) mergedPath := cr1.fbo.nodeCache.PathFromNode(dirA1) mergedPath.path = append(mergedPath.path, pathNode{ BlockPointer: dirBPtr, Name: "dirB", }) mergedPath.path = append(mergedPath.path, pathNode{ BlockPointer: dirCPtr, Name: "dirC", }) mergedPaths[expectedUnmergedPath.tailPointer()] = mergedPath coB := newCreateOp("dirB", dirAPtr, Dir) coC := newCreateOp("dirC", dirBPtr, Dir) dirAPtr1 := cr1.fbo.nodeCache.PathFromNode(dirA1).tailPointer() expectedActions := map[BlockPointer]crActionList{ dirCPtr: {©UnmergedEntryAction{"file2", "file2", "", false, false, DirEntry{}, nil}}, dirBPtr: {©UnmergedEntryAction{"dirC", "dirC", "", false, false, DirEntry{}, nil}}, dirAPtr1: {©UnmergedEntryAction{"dirB", "dirB", "", false, false, DirEntry{}, nil}}, } testCRCheckPathsAndActions(t, cr2, []path{expectedUnmergedPath}, mergedPaths, []*createOp{coB, coC}, expectedActions) }
// Test that actions get executed properly in the case of two // simultaneous writes to the same file. func TestCRDoActionsWriteConflict(t *testing.T) { var userName1, userName2 libkb.NormalizedUsername = "******", "u2" config1, uid1, ctx := kbfsOpsConcurInit(t, userName1, userName2) defer CheckConfigAndShutdown(t, config1) config2 := ConfigAsUser(config1.(*ConfigLocal), userName2) defer CheckConfigAndShutdown(t, config2) _, uid2, err := config2.KBPKI().GetCurrentUserInfo(ctx) if err != nil { t.Fatal(err) } clock, now := newTestClockAndTimeNow() config2.SetClock(clock) name := userName1.String() + "," + userName2.String() configs := make(map[keybase1.UID]Config) configs[uid1] = config1 configs[uid2] = config2 nodes := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"dir"}) dir1 := nodes[uid1] dir2 := nodes[uid2] fb := dir1.GetFolderBranch() // user1 makes a file file1, _, err := config1.KBFSOps().CreateFile(ctx, dir1, "file", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // user2 lookup err = config2.KBFSOps().SyncFromServerForTesting(ctx, fb) if err != nil { t.Fatalf("Couldn't sync user 2") } file2, _, err := config2.KBFSOps().Lookup(ctx, dir2, "file") if err != nil { t.Fatalf("Couldn't lookup file: %v", err) } // pause user 2 _, err = DisableUpdatesForTesting(config2, fb) if err != nil { t.Fatalf("Can't disable updates for user 2: %v", err) } cr1 := testCRGetCROrBust(t, config1, fb) cr2 := testCRGetCROrBust(t, config2, fb) cr2.Shutdown() // user1 writes the file err = config1.KBFSOps().Write(ctx, file1, []byte{1, 2, 3}, 0) if err != nil { t.Fatalf("Couldn't write file: %v", err) } err = config1.KBFSOps().Sync(ctx, file1) if err != nil { t.Fatalf("Couldn't sync file: %v", err) } // user2 writes the file unmergedData := []byte{4, 5, 6} err = config2.KBFSOps().Write(ctx, file2, unmergedData, 0) if err != nil { t.Fatalf("Couldn't write file: %v", err) } err = config2.KBFSOps().Sync(ctx, file2) if err != nil { t.Fatalf("Couldn't sync file: %v", err) } lState := makeFBOLockState() // Now run through conflict resolution manually for user2. unmergedChains, mergedChains, unmergedPaths, mergedPaths, recreateOps, _, _, err := cr2.buildChainsAndPaths(ctx, lState) if err != nil { t.Fatalf("Couldn't build chains and paths: %v", err) } actionMap, _, err := cr2.computeActions(ctx, unmergedChains, mergedChains, mergedPaths, recreateOps) if err != nil { t.Fatalf("Couldn't compute actions: %v", err) } lbc := make(localBcache) newFileBlocks := make(fileBlockMap) err = cr2.doActions(ctx, lState, unmergedChains, mergedChains, unmergedPaths, mergedPaths, actionMap, lbc, newFileBlocks) if err != nil { t.Fatalf("Couldn't do actions: %v", err) } // Does the merged block contain the two files? mergedRootPath := cr1.fbo.nodeCache.PathFromNode(dir1) cre := WriterDeviceDateConflictRenamer{} mergedName := cre.ConflictRenameHelper(now, "u2", "dev1", "file") if len(newFileBlocks) != 1 { t.Errorf("Unexpected new file blocks!") } if blocks, ok := newFileBlocks[mergedRootPath.tailPointer()]; !ok { t.Errorf("No blocks for dir merged ptr: %v", mergedRootPath.tailPointer()) } else if len(blocks) != 1 { t.Errorf("Unexpected number of blocks") } else if fblock, ok := blocks[mergedName]; !ok { t.Errorf("No block for name %s", mergedName) } else if fblock.IsInd { t.Errorf("Unexpected indirect block") } else if g, e := fblock.Contents, unmergedData; !reflect.DeepEqual(g, e) { t.Errorf("Unexpected block contents: %v vs %v", g, e) } // NOTE: the action doesn't actually create the entry, so this // test can only check that newFileBlocks looks correct. }
// Test that actions get executed properly in the simple case of two // files being created simultaneously in the same directory. func TestCRDoActionsSimple(t *testing.T) { var userName1, userName2 libkb.NormalizedUsername = "******", "u2" config1, uid1, ctx := kbfsOpsConcurInit(t, userName1, userName2) defer CheckConfigAndShutdown(t, config1) config2 := ConfigAsUser(config1.(*ConfigLocal), userName2) defer CheckConfigAndShutdown(t, config2) _, uid2, err := config2.KBPKI().GetCurrentUserInfo(ctx) if err != nil { t.Fatal(err) } name := userName1.String() + "," + userName2.String() configs := make(map[keybase1.UID]Config) configs[uid1] = config1 configs[uid2] = config2 nodes := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"dir"}) dir1 := nodes[uid1] dir2 := nodes[uid2] fb := dir1.GetFolderBranch() // pause user 2 _, err = DisableUpdatesForTesting(config2, fb) if err != nil { t.Fatalf("Can't disable updates for user 2: %v", err) } // user1 makes a file _, _, err = config1.KBFSOps().CreateFile(ctx, dir1, "file1", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } cr1 := testCRGetCROrBust(t, config1, fb) cr2 := testCRGetCROrBust(t, config2, fb) cr2.Shutdown() // user2 makes a file (causes a conflict, and goes unstaged) _, _, err = config2.KBFSOps().CreateFile(ctx, dir2, "file2", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } lState := makeFBOLockState() // Now run through conflict resolution manually for user2. unmergedChains, mergedChains, unmergedPaths, mergedPaths, recreateOps, _, _, err := cr2.buildChainsAndPaths(ctx, lState) if err != nil { t.Fatalf("Couldn't build chains and paths: %v", err) } actionMap, _, err := cr2.computeActions(ctx, unmergedChains, mergedChains, mergedPaths, recreateOps) if err != nil { t.Fatalf("Couldn't compute actions: %v", err) } lbc := make(localBcache) newFileBlocks := make(fileBlockMap) err = cr2.doActions(ctx, lState, unmergedChains, mergedChains, unmergedPaths, mergedPaths, actionMap, lbc, newFileBlocks) if err != nil { t.Fatalf("Couldn't do actions: %v", err) } // Does the merged block contain both entries? mergedRootPath := cr1.fbo.nodeCache.PathFromNode(dir1) block1, ok := lbc[mergedRootPath.tailPointer()] if !ok { t.Fatalf("Couldn't find merged block at path %s", mergedRootPath) } if g, e := len(block1.Children), 2; g != e { t.Errorf("Unexpected number of children: %d vs %d", g, e) } for _, file := range []string{"file1", "file2"} { if _, ok := block1.Children[file]; !ok { t.Errorf("Couldn't find entry in merged children: %s", file) } } if len(newFileBlocks) != 0 { t.Errorf("Unexpected new file blocks!") } }
// Test that two conflict resolutions work correctly. func TestCRDouble(t *testing.T) { // simulate two users var userName1, userName2 libkb.NormalizedUsername = "******", "u2" config1, _, ctx := kbfsOpsConcurInit(t, userName1, userName2) defer CheckConfigAndShutdown(t, config1) config1.MDServer().DisableRekeyUpdatesForTesting() config2 := ConfigAsUser(config1.(*ConfigLocal), userName2) defer CheckConfigAndShutdown(t, config2) _, _, err := config2.KBPKI().GetCurrentUserInfo(context.Background()) if err != nil { t.Fatal(err) } config2.MDServer().DisableRekeyUpdatesForTesting() config2.SetClock(newTestClockNow()) name := userName1.String() + "," + userName2.String() // create and write to a file rootNode := GetRootNodeOrBust(t, config1, name, false) kbfsOps1 := config1.KBFSOps() _, _, err = kbfsOps1.CreateFile(ctx, rootNode, "a", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // look it up on user2 rootNode2 := GetRootNodeOrBust(t, config2, name, false) kbfsOps2 := config2.KBFSOps() _, _, err = kbfsOps2.Lookup(ctx, rootNode2, "a") if err != nil { t.Fatalf("Couldn't lookup dir: %v", err) } // disable updates and CR on user 2 c, err := DisableUpdatesForTesting(config2, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't disable updates: %v", err) } err = DisableCRForTesting(config2, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't disable updates: %v", err) } // User 1 creates a new file to start a conflict. _, _, err = kbfsOps1.CreateFile(ctx, rootNode, "b", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // User 2 makes a couple revisions fileNodeC, _, err := kbfsOps2.CreateFile(ctx, rootNode2, "c", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } err = kbfsOps2.Write(ctx, fileNodeC, []byte{0}, 0) if err != nil { t.Fatalf("Couldn't sync file: %v", err) } // Cancel this revision after the Put happens, to force the // background block manager to try to clean up. onSyncStalledCh := make(chan struct{}, 1) syncUnstallCh := make(chan struct{}) stallKey := "requestName" syncValue := "sync" config2.SetMDOps(&stallingMDOps{ stallOpName: "PutUnmerged", stallKey: stallKey, stallMap: map[interface{}]staller{ syncValue: staller{ stalled: onSyncStalledCh, unstall: syncUnstallCh, }, }, delegate: config2.MDOps(), }) var wg sync.WaitGroup syncCtx, cancel := context.WithCancel(ctx) wg.Add(1) go func() { defer wg.Done() syncCtx = context.WithValue(syncCtx, stallKey, syncValue) err = kbfsOps2.Sync(syncCtx, fileNodeC) if err != context.Canceled { t.Fatalf("Bad sync error, expected canceled: %v", err) } }() <-onSyncStalledCh cancel() close(syncUnstallCh) wg.Wait() // Sync for real to clear out the dirty files. err = kbfsOps2.Sync(ctx, fileNodeC) if err != nil { t.Fatalf("Couldn't sync: %v", err) } // Do one CR. c <- struct{}{} err = RestartCRForTesting(config2, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't disable updates: %v", err) } err = kbfsOps2.SyncFromServerForTesting(ctx, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } // A few merged revisions _, _, err = kbfsOps2.CreateFile(ctx, rootNode2, "e", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } _, _, err = kbfsOps2.CreateFile(ctx, rootNode2, "f", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } ops := getOps(config2, rootNode.GetFolderBranch().Tlf) // Wait for the processor to try to delete the failed revision // (which pulls the unmerged MD ops back into the cache). ops.fbm.waitForArchives(ctx) // Sync user 1, then start another round of CR. err = kbfsOps1.SyncFromServerForTesting(ctx, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } // disable updates and CR on user 2 c, err = DisableUpdatesForTesting(config2, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't disable updates: %v", err) } err = DisableCRForTesting(config2, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't disable updates: %v", err) } _, _, err = kbfsOps1.CreateFile(ctx, rootNode, "g", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // User 2 makes a couple unmerged revisions _, _, err = kbfsOps2.CreateFile(ctx, rootNode2, "h", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } _, _, err = kbfsOps2.CreateFile(ctx, rootNode2, "i", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // Do a second CR. c <- struct{}{} err = RestartCRForTesting(config2, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't disable updates: %v", err) } err = kbfsOps2.SyncFromServerForTesting(ctx, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } }
// Tests that two users can make independent writes while forked, and // conflict resolution will merge them correctly. func TestBasicCRNoConflict(t *testing.T) { // simulate two users var userName1, userName2 libkb.NormalizedUsername = "******", "u2" config1, _, ctx := kbfsOpsConcurInit(t, userName1, userName2) defer CheckConfigAndShutdown(t, config1) config2 := ConfigAsUser(config1.(*ConfigLocal), userName2) defer CheckConfigAndShutdown(t, config2) name := userName1.String() + "," + userName2.String() // user1 creates a file in a shared dir rootNode1 := GetRootNodeOrBust(t, config1, name, false) kbfsOps1 := config1.KBFSOps() _, _, err := kbfsOps1.CreateFile(ctx, rootNode1, "a", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // look it up on user2 rootNode2 := GetRootNodeOrBust(t, config2, name, false) kbfsOps2 := config2.KBFSOps() _, _, err = kbfsOps2.Lookup(ctx, rootNode2, "a") if err != nil { t.Fatalf("Couldn't lookup file: %v", err) } // disable updates on user 2 c, err := DisableUpdatesForTesting(config2, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't disable updates: %v", err) } err = DisableCRForTesting(config2, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't disable updates: %v", err) } // User 1 makes a new file _, _, err = kbfsOps1.CreateFile(ctx, rootNode1, "b", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // User 2 makes a new different file _, _, err = kbfsOps2.CreateFile(ctx, rootNode2, "c", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // re-enable updates, and wait for CR to complete c <- struct{}{} err = RestartCRForTesting(config2, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't disable updates: %v", err) } err = kbfsOps2.SyncFromServerForTesting(ctx, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } err = kbfsOps1.SyncFromServerForTesting(ctx, rootNode1.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } // Make sure they both see the same set of children expectedChildren := []string{"a", "b", "c"} children1, err := kbfsOps1.GetDirChildren(ctx, rootNode1) if err != nil { t.Fatalf("Couldn't get children: %v", err) } children2, err := kbfsOps2.GetDirChildren(ctx, rootNode2) if err != nil { t.Fatalf("Couldn't get children: %v", err) } if g, e := len(children1), len(expectedChildren); g != e { t.Errorf("Wrong number of children: %d vs %d", g, e) } for _, child := range expectedChildren { if _, ok := children1[child]; !ok { t.Errorf("Couldn't find child %s", child) } } if !reflect.DeepEqual(children1, children2) { t.Fatalf("Users 1 and 2 see different children: %v vs %v", children1, children2) } }
// Tests that, in the face of a conflict, a user will commit its // changes to a private branch, which will persist after restart (and // the other user will be unaffected). func TestUnmergedAfterRestart(t *testing.T) { // simulate two users var userName1, userName2 libkb.NormalizedUsername = "******", "u2" config1, _, ctx := kbfsOpsConcurInit(t, userName1, userName2) defer CheckConfigAndShutdown(t, config1) config2 := ConfigAsUser(config1.(*ConfigLocal), userName2) defer CheckConfigAndShutdown(t, config2) name := userName1.String() + "," + userName2.String() // user1 creates a file in a shared dir kbfsOps1 := config1.KBFSOps() rootNode1, _, err := kbfsOps1.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Couldn't create folder: %v", err) } fileNode1, _, err := kbfsOps1.CreateFile(ctx, rootNode1, "a", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // then user2 write to the file kbfsOps2 := config2.KBFSOps() rootNode2, _, err := kbfsOps2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Couldn't create folder: %v", err) } fileNode2, _, err := kbfsOps2.Lookup(ctx, rootNode2, "a") if err != nil { t.Fatalf("Couldn't lookup file: %v", err) } data2 := []byte{2} err = kbfsOps2.Write(ctx, fileNode2, data2, 0) if err != nil { t.Fatalf("Couldn't write file: %v", err) } checkStatus(t, ctx, kbfsOps2, false, userName1, []string{"u1,u2/a"}, rootNode2.GetFolderBranch(), "Node 2 (after write)") err = kbfsOps2.Sync(ctx, fileNode2) if err != nil { t.Fatalf("Couldn't sync file: %v", err) } DisableCRForTesting(config1, rootNode1.GetFolderBranch()) // Now when user 1 tries to write to file 1 and sync, it will // become unmerged. Because this happens in the same goroutine as // the above Sync, we can be sure that the updater on client 1 // hasn't yet seen the MD update, and so its Sync will present a // conflict. data1 := []byte{1} err = kbfsOps1.Write(ctx, fileNode1, data1, 0) if err != nil { t.Fatalf("Couldn't write file: %v", err) } checkStatus(t, ctx, kbfsOps1, false, userName1, []string{"u1,u2/a"}, rootNode1.GetFolderBranch(), "Node 1 (after write)") err = kbfsOps1.Sync(ctx, fileNode1) if err != nil { t.Fatalf("Couldn't sync file: %v", err) } checkStatus(t, ctx, kbfsOps1, true, userName1, nil, rootNode1.GetFolderBranch(), "Node 1") checkStatus(t, ctx, kbfsOps2, false, userName2, nil, rootNode2.GetFolderBranch(), "Node 2") // now re-login the users, and make sure 1 can see the changes, // but 2 can't config1B := ConfigAsUser(config1.(*ConfigLocal), userName1) defer CheckConfigAndShutdown(t, config1B) config2B := ConfigAsUser(config1.(*ConfigLocal), userName2) defer CheckConfigAndShutdown(t, config2B) DisableCRForTesting(config1B, rootNode1.GetFolderBranch()) readAndCompareData(t, config1B, ctx, name, data1, userName1) readAndCompareData(t, config2B, ctx, name, data2, userName2) checkStatus(t, ctx, config1B.KBFSOps(), true, userName1, nil, rootNode1.GetFolderBranch(), "Node 1") checkStatus(t, ctx, config2B.KBFSOps(), false, userName2, nil, rootNode2.GetFolderBranch(), "Node 2") // register as a listener before the unstaging happens c := make(chan struct{}, 2) cro := &testCRObserver{c, nil} config1B.Notifier().RegisterForChanges( []FolderBranch{rootNode1.GetFolderBranch()}, cro) // Unstage user 1's changes, and make sure everyone is back in // sync. TODO: remove this once we have automatic conflict // resolution. err = config1B.KBFSOps().UnstageForTesting(ctx, rootNode1.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't unstage: %v", err) } // we should have had two updates, one for the unstaging and one // for the fast-forward <-c <-c // make sure we see two sync op changes, on the same node if len(cro.changes) != 2 { t.Errorf("Unexpected number of changes: %d", len(cro.changes)) } var n Node for _, change := range cro.changes { if n == nil { n = change.Node } else if n.GetID() != change.Node.GetID() { t.Errorf("Changes involve different nodes, %v vs %v\n", n.GetID(), change.Node.GetID()) } } if err := config1B.KBFSOps(). SyncFromServer(ctx, rootNode1.GetFolderBranch()); err != nil { t.Fatal("Couldn't sync user 1 from server") } if err := config2B.KBFSOps(). SyncFromServer(ctx, rootNode2.GetFolderBranch()); err != nil { t.Fatal("Couldn't sync user 2 from server") } readAndCompareData(t, config1B, ctx, name, data2, userName2) readAndCompareData(t, config2B, ctx, name, data2, userName2) checkStatus(t, ctx, config1B.KBFSOps(), false, userName1, nil, rootNode1.GetFolderBranch(), "Node 1 (after unstage)") checkStatus(t, ctx, config2B.KBFSOps(), false, userName1, nil, rootNode2.GetFolderBranch(), "Node 2 (after unstage)") }
// Tests that conflict resolution detects and renames conflicts. func TestCRMergedChainsConflictSimple(t *testing.T) { var userName1, userName2 libkb.NormalizedUsername = "******", "u2" config1, uid1, ctx := kbfsOpsConcurInit(t, userName1, userName2) defer CheckConfigAndShutdown(t, config1) config2 := ConfigAsUser(config1.(*ConfigLocal), userName2) defer CheckConfigAndShutdown(t, config2) _, uid2, err := config2.KBPKI().GetCurrentUserInfo(ctx) if err != nil { t.Fatal(err) } clock, now := newTestClockAndTimeNow() config2.SetClock(clock) name := userName1.String() + "," + userName2.String() configs := make(map[keybase1.UID]Config) configs[uid1] = config1 configs[uid2] = config2 nodesRoot := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"root"}) dirRoot1 := nodesRoot[uid1] dirRoot2 := nodesRoot[uid2] fb := dirRoot1.GetFolderBranch() cr1 := testCRGetCROrBust(t, config1, fb) cr2 := testCRGetCROrBust(t, config2, fb) cr2.Shutdown() // pause user 2 _, err = DisableUpdatesForTesting(config2, fb) if err != nil { t.Fatalf("Can't disable updates for user 2: %v", err) } // user1 creates file1 _, _, err = config1.KBFSOps().CreateFile(ctx, dirRoot1, "file1", false) if err != nil { t.Fatalf("Couldn't make file: %v", err) } // user2 also create file1, but makes it executable _, _, err = config2.KBFSOps().CreateFile(ctx, dirRoot2, "file1", true) if err != nil { t.Fatalf("Couldn't make dir: %v", err) } // Now step through conflict resolution manually for user 2 mergedPaths := make(map[BlockPointer]path) // root unmergedPathRoot := cr2.fbo.nodeCache.PathFromNode(dirRoot2) mergedPathRoot := cr1.fbo.nodeCache.PathFromNode(dirRoot1) mergedPaths[unmergedPathRoot.tailPointer()] = mergedPathRoot cre := WriterDeviceDateConflictRenamer{} expectedActions := map[BlockPointer]crActionList{ mergedPathRoot.tailPointer(): {&renameUnmergedAction{ "file1", cre.ConflictRenameHelper(now, "u2", "dev1", "file1"), "", zeroPtr, zeroPtr}}, } testCRCheckPathsAndActions(t, cr2, []path{unmergedPathRoot}, mergedPaths, nil, expectedActions) }
// Tests that two users can make independent writes while forked, and // conflict resolution will merge them correctly. func TestBasicCRFileConflict(t *testing.T) { // simulate two users var userName1, userName2 libkb.NormalizedUsername = "******", "u2" config1, _, ctx := kbfsOpsConcurInit(t, userName1, userName2) defer CheckConfigAndShutdown(t, config1) config2 := ConfigAsUser(config1.(*ConfigLocal), userName2) defer CheckConfigAndShutdown(t, config2) now := time.Now() config2.SetClock(&TestClock{now}) name := userName1.String() + "," + userName2.String() // user1 creates a file in a shared dir kbfsOps1 := config1.KBFSOps() rootNode1, _, err := kbfsOps1.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Couldn't create folder: %v", err) } dirA1, _, err := kbfsOps1.CreateDir(ctx, rootNode1, "a") if err != nil { t.Fatalf("Couldn't create dir: %v", err) } fileB1, _, err := kbfsOps1.CreateFile(ctx, dirA1, "b", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // look it up on user2 kbfsOps2 := config2.KBFSOps() rootNode2, _, err := kbfsOps2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Couldn't create folder: %v", err) } dirA2, _, err := kbfsOps2.Lookup(ctx, rootNode2, "a") if err != nil { t.Fatalf("Couldn't lookup dir: %v", err) } fileB2, _, err := kbfsOps2.Lookup(ctx, dirA2, "b") if err != nil { t.Fatalf("Couldn't lookup file: %v", err) } // disable updates on user 2 c, err := DisableUpdatesForTesting(config2, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't disable updates: %v", err) } // User 1 writes the file data1 := []byte{1, 2, 3, 4, 5} err = kbfsOps1.Write(ctx, fileB1, data1, 0) if err != nil { t.Fatalf("Couldn't write file: %v", err) } err = kbfsOps1.Sync(ctx, fileB1) if err != nil { t.Fatalf("Couldn't sync file: %v", err) } // User 2 makes a new different file data2 := []byte{5, 4, 3, 2, 1} err = kbfsOps2.Write(ctx, fileB2, data2, 0) if err != nil { t.Fatalf("Couldn't write file: %v", err) } err = kbfsOps2.Sync(ctx, fileB2) if err != nil { t.Fatalf("Couldn't sync file: %v", err) } // re-enable updates, and wait for CR to complete c <- struct{}{} err = kbfsOps2.SyncFromServer(ctx, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } err = kbfsOps1.SyncFromServer(ctx, rootNode1.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } // Make sure they both see the same set of children expectedChildren := []string{ "b", "b.conflict.u2." + now.Format(time.RFC3339Nano), } children1, err := kbfsOps1.GetDirChildren(ctx, dirA1) if err != nil { t.Fatalf("Couldn't get children: %v", err) } children2, err := kbfsOps2.GetDirChildren(ctx, dirA2) if err != nil { t.Fatalf("Couldn't get children: %v", err) } if g, e := len(children1), len(expectedChildren); g != e { t.Errorf("Wrong number of children: %d vs %d", g, e) } for _, child := range expectedChildren { if _, ok := children1[child]; !ok { t.Errorf("Couldn't find child %s", child) } } if !reflect.DeepEqual(children1, children2) { t.Fatalf("Users 1 and 2 see different children: %v vs %v", children1, children2) } }
// Tests that conflict resolution detects and renames conflicts. func TestCRMergedChainsConflictFileCollapse(t *testing.T) { var userName1, userName2 libkb.NormalizedUsername = "******", "u2" config1, uid1, ctx := kbfsOpsConcurInit(t, userName1, userName2) defer CheckConfigAndShutdown(t, config1) config2 := ConfigAsUser(config1.(*ConfigLocal), userName2) defer CheckConfigAndShutdown(t, config2) _, uid2, err := config2.KBPKI().GetCurrentUserInfo(ctx) if err != nil { t.Fatal(err) } clock, now := newTestClockAndTimeNow() config2.SetClock(clock) name := userName1.String() + "," + userName2.String() configs := make(map[keybase1.UID]Config) configs[uid1] = config1 configs[uid2] = config2 nodesRoot := testCRSharedFolderForUsers(t, name, uid1, configs, []string{"root"}) dirRoot1 := nodesRoot[uid1] dirRoot2 := nodesRoot[uid2] fb := dirRoot1.GetFolderBranch() cr1 := testCRGetCROrBust(t, config1, fb) cr2 := testCRGetCROrBust(t, config2, fb) cr2.Shutdown() // user1 creates file _, _, err = config1.KBFSOps().CreateFile(ctx, dirRoot1, "file", false) if err != nil { t.Fatalf("Couldn't make file: %v", err) } // user2 lookup err = config2.KBFSOps().SyncFromServerForTesting(ctx, fb) if err != nil { t.Fatalf("Couldn't sync user 2") } file2, _, err := config2.KBFSOps().Lookup(ctx, dirRoot2, "file") if err != nil { t.Fatalf("Couldn't lookup file: %v", err) } filePtr := cr2.fbo.nodeCache.PathFromNode(file2).tailPointer() dirRootPtr := cr2.fbo.nodeCache.PathFromNode(dirRoot2).tailPointer() // pause user 2 _, err = DisableUpdatesForTesting(config2, fb) if err != nil { t.Fatalf("Can't disable updates for user 2: %v", err) } // user1 deletes the file and creates another err = config1.KBFSOps().RemoveEntry(ctx, dirRoot1, "file") if err != nil { t.Fatalf("Couldn't remove file: %v", err) } _, _, err = config1.KBFSOps().CreateFile(ctx, dirRoot1, "file", false) if err != nil { t.Fatalf("Couldn't re-make file: %v", err) } // user2 updates the file attribute and writes to err = config2.KBFSOps().SetEx(ctx, file2, true) if err != nil { t.Fatalf("Couldn't set ex: %v", err) } err = config2.KBFSOps().Write(ctx, file2, []byte{1, 2, 3}, 0) if err != nil { t.Fatalf("Couldn't write: %v", err) } // Now step through conflict resolution manually for user 2 mergedPaths := make(map[BlockPointer]path) // file (needs to be recreated) unmergedPathFile := cr2.fbo.nodeCache.PathFromNode(file2) mergedPathFile := cr1.fbo.nodeCache.PathFromNode(dirRoot1) mergedPathFile.path = append(mergedPathFile.path, pathNode{ BlockPointer: filePtr, Name: "file", }) mergedPaths[unmergedPathFile.tailPointer()] = mergedPathFile coFile := newCreateOp("file", dirRootPtr, Exec) cre := WriterDeviceDateConflictRenamer{} mergedPathRoot := cr1.fbo.nodeCache.PathFromNode(dirRoot1) // Both unmerged actions should collapse into just one rename operation expectedActions := map[BlockPointer]crActionList{ mergedPathRoot.tailPointer(): {&renameUnmergedAction{ "file", cre.ConflictRenameHelper(now, "u2", "dev1", "file"), "", zeroPtr, zeroPtr}}, } testCRCheckPathsAndActions(t, cr2, []path{unmergedPathFile}, mergedPaths, []*createOp{coFile}, expectedActions) }
func TestKeyManagerRekeyAddAndRevokeDevice(t *testing.T) { var u1, u2 libkb.NormalizedUsername = "******", "u2" config1, _, ctx := kbfsOpsConcurInit(t, u1, u2) defer CheckConfigAndShutdown(t, config1) config2 := ConfigAsUser(config1.(*ConfigLocal), u2) defer CheckConfigAndShutdown(t, config2) uid2, err := config2.KBPKI().GetCurrentUID(context.Background()) if err != nil { t.Fatal(err) } // Create a shared folder name := u1.String() + "," + u2.String() kbfsOps1 := config1.KBFSOps() rootNode1, _, err := kbfsOps1.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Couldn't create folder: %v", err) } // user 1 creates a file _, _, err = kbfsOps1.CreateFile(ctx, rootNode1, "a", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } config2Dev2 := ConfigAsUser(config1.(*ConfigLocal), u2) defer CheckConfigAndShutdown(t, config2Dev2) // Now give u2 a new device. The configs don't share a Keybase // Daemon so we have to do it in all places. AddDeviceForLocalUserOrBust(t, config1, uid2) AddDeviceForLocalUserOrBust(t, config2, uid2) devIndex := AddDeviceForLocalUserOrBust(t, config2Dev2, uid2) SwitchDeviceForLocalUserOrBust(t, config2Dev2, devIndex) // user 2 should be unable to read the data now since its device // wasn't registered when the folder was originally created. kbfsOps2Dev2 := config2Dev2.KBFSOps() _, _, err = kbfsOps2Dev2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if _, ok := err.(ReadAccessError); !ok { t.Fatalf("Got unexpected error when reading with new key: %v", err) } // now user 1 should rekey err = kbfsOps1.Rekey(ctx, rootNode1.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't rekey: %v", err) } // this device should be able to read now root2Dev2, _, err := kbfsOps2Dev2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Got unexpected error after rekey: %v", err) } // add a third device for user 2 config2Dev3 := ConfigAsUser(config1.(*ConfigLocal), u2) defer CheckConfigAndShutdown(t, config2Dev3) AddDeviceForLocalUserOrBust(t, config1, uid2) AddDeviceForLocalUserOrBust(t, config2, uid2) AddDeviceForLocalUserOrBust(t, config2Dev2, uid2) devIndex = AddDeviceForLocalUserOrBust(t, config2Dev3, uid2) SwitchDeviceForLocalUserOrBust(t, config2Dev3, devIndex) // Now revoke the original user 2 device RevokeDeviceForLocalUserOrBust(t, config1, uid2, 0) RevokeDeviceForLocalUserOrBust(t, config2Dev2, uid2, 0) RevokeDeviceForLocalUserOrBust(t, config2Dev3, uid2, 0) // rekey again err = kbfsOps1.Rekey(ctx, rootNode1.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't rekey: %v", err) } // force re-encryption of the root dir _, _, err = kbfsOps1.CreateFile(ctx, rootNode1, "b", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } err = kbfsOps2Dev2.SyncFromServer(ctx, root2Dev2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } // device 2 should still work rootNode2, _, err := kbfsOps2Dev2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Got unexpected error after rekey: %v", err) } children, err := kbfsOps2Dev2.GetDirChildren(ctx, rootNode2) if _, ok := children["b"]; !ok { t.Fatalf("Device 2 couldn't see the new dir entry") } // but device 1 should now fail kbfsOps2 := config2.KBFSOps() _, _, err = kbfsOps2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if _, ok := err.(ReadAccessError); !ok { t.Fatalf("Got unexpected error when reading with revoked key: %v", err) } // meanwhile, device 3 should be able to read both the new and the // old files kbfsOps2Dev3 := config2Dev3.KBFSOps() rootNode2Dev3, _, err := kbfsOps2Dev3.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Device 3 couldn't read root: %v", err) } aNode, _, err := kbfsOps2Dev3.Lookup(ctx, rootNode2Dev3, "a") if err != nil { t.Fatalf("Device 3 couldn't lookup a: %v", err) } buf := []byte{0} _, err = kbfsOps2Dev3.Read(ctx, aNode, buf, 0) if err != nil { t.Fatalf("Device 3 couldn't read a: %v", err) } bNode, _, err := kbfsOps2Dev3.Lookup(ctx, rootNode2Dev3, "b") if err != nil { t.Fatalf("Device 3 couldn't lookup b: %v", err) } _, err = kbfsOps2Dev3.Read(ctx, bNode, buf, 0) if err != nil { t.Fatalf("Device 3 couldn't read b: %v", err) } // Make sure the server-side keys for the revoked device are gone // for all keygens. rmd, err := config1.MDOps().GetForTLF(ctx, rootNode1.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't get latest md: %v", err) } currKeyGen := rmd.LatestKeyGeneration() // clear the key cache config2.SetKeyCache(NewKeyCacheStandard(5000)) km2, ok := config2.KeyManager().(*KeyManagerStandard) if !ok { t.Fatal("Wrong kind of key manager for config2") } for keyGen := KeyGen(FirstValidKeyGen); keyGen <= currKeyGen; keyGen++ { _, err = km2.getTLFCryptKeyUsingCurrentDevice(ctx, rmd, keyGen, true) if err == nil { t.Errorf("User 2 could still fetch a key for keygen %d", keyGen) } } }
func TestKeyManagerSelfRekeyAcrossDevices(t *testing.T) { var u1, u2 libkb.NormalizedUsername = "******", "u2" config1, _, ctx := kbfsOpsConcurInit(t, u1, u2) defer CheckConfigAndShutdown(t, config1) config2 := ConfigAsUser(config1.(*ConfigLocal), u2) defer CheckConfigAndShutdown(t, config2) uid2, err := config2.KBPKI().GetCurrentUID(context.Background()) if err != nil { t.Fatal(err) } t.Log("Create a shared folder") name := u1.String() + "," + u2.String() kbfsOps1 := config1.KBFSOps() rootNode1, _, err := kbfsOps1.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Couldn't create folder: %v", err) } t.Log("User 1 creates a file") _, _, err = kbfsOps1.CreateFile(ctx, rootNode1, "a", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } t.Log("User 2 adds a device") // The configs don't share a Keybase Daemon so we have to do it in all // places. AddDeviceForLocalUserOrBust(t, config1, uid2) devIndex := AddDeviceForLocalUserOrBust(t, config2, uid2) config2Dev2 := ConfigAsUser(config2, u2) defer CheckConfigAndShutdown(t, config2Dev2) SwitchDeviceForLocalUserOrBust(t, config2Dev2, devIndex) t.Log("Check that user 2 device 2 is unable to read the file") // user 2 device 2 should be unable to read the data now since its device // wasn't registered when the folder was originally created. kbfsOps2Dev2 := config2Dev2.KBFSOps() _, _, err = kbfsOps2Dev2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if _, ok := err.(ReadAccessError); !ok { t.Fatalf("Got unexpected error when reading with new key: %v", err) } t.Log("User 2 rekeys from device 1") kbfsOps2 := config2.KBFSOps() root2dev1, _, err := kbfsOps2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Couldn't obtain folder: %#v", err) } err = kbfsOps2.Rekey(ctx, root2dev1.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't rekey: %v", err) } t.Log("User 2 device 2 should be able to read now") root2dev2, _, err := kbfsOps2Dev2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Got unexpected error after rekey: %v", err) } t.Log("User 2 device 2 reads user 1's file") children2, err := kbfsOps2Dev2.GetDirChildren(ctx, root2dev2) if _, ok := children2["a"]; !ok { t.Fatalf("Device 2 couldn't see user 1's dir entry") } t.Log("User 2 device 2 creates a file") _, _, err = kbfsOps2Dev2.CreateFile(ctx, root2dev2, "b", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } t.Log("User 1 syncs from the server") err = kbfsOps1.SyncFromServer(ctx, rootNode1.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } t.Log("User 1 should be able to read the file that user 2 device 2 created") children1, err := kbfsOps1.GetDirChildren(ctx, rootNode1) if _, ok := children1["b"]; !ok { t.Fatalf("Device 1 couldn't see the new dir entry") } }
// Test that deleted blocks are correctly flushed from the user cache. func TestQuotaReclamationDeletedBlocks(t *testing.T) { var u1, u2 libkb.NormalizedUsername = "******", "u2" config1, _, ctx := kbfsOpsInitNoMocks(t, u1, u2) defer CheckConfigAndShutdown(t, config1) clock, now := newTestClockAndTimeNow() config1.SetClock(clock) // Initialize the MD using a different config config2 := ConfigAsUser(config1.(*ConfigLocal), u2) defer CheckConfigAndShutdown(t, config2) config2.SetClock(clock) name := u1.String() + "," + u2.String() rootNode1 := GetRootNodeOrBust(t, config1, name, false) data := []byte{1, 2, 3, 4, 5} kbfsOps1 := config1.KBFSOps() aNode1, _, err := kbfsOps1.CreateFile(ctx, rootNode1, "a", false) if err != nil { t.Fatalf("Couldn't create dir: %v", err) } err = kbfsOps1.Write(ctx, aNode1, data, 0) if err != nil { t.Fatalf("Couldn't write file: %v", err) } err = kbfsOps1.Sync(ctx, aNode1) if err != nil { t.Fatalf("Couldn't sync file: %v", err) } // Make two more files that share a block, only one of which will // be deleted. otherData := []byte{5, 4, 3, 2, 1} for _, name := range []string{"b", "c"} { node, _, err := kbfsOps1.CreateFile(ctx, rootNode1, name, false) if err != nil { t.Fatalf("Couldn't create dir: %v", err) } err = kbfsOps1.Write(ctx, node, otherData, 0) if err != nil { t.Fatalf("Couldn't write file: %v", err) } err = kbfsOps1.Sync(ctx, node) if err != nil { t.Fatalf("Couldn't sync file: %v", err) } } // u2 reads the file rootNode2 := GetRootNodeOrBust(t, config2, name, false) kbfsOps2 := config2.KBFSOps() aNode2, _, err := kbfsOps2.Lookup(ctx, rootNode2, "a") if err != nil { t.Fatalf("Couldn't create dir: %v", err) } data2 := make([]byte, len(data)) _, err = kbfsOps2.Read(ctx, aNode2, data2, 0) if err != nil { t.Fatalf("Couldn't read file: %v", err) } if !bytes.Equal(data, data2) { t.Fatalf("Read bad data: %v", data2) } bNode2, _, err := kbfsOps2.Lookup(ctx, rootNode2, "b") if err != nil { t.Fatalf("Couldn't create dir: %v", err) } data2 = make([]byte, len(data)) _, err = kbfsOps2.Read(ctx, bNode2, data2, 0) if err != nil { t.Fatalf("Couldn't read file: %v", err) } if !bytes.Equal(otherData, data2) { t.Fatalf("Read bad data: %v", data2) } // Remove two of the files err = kbfsOps1.RemoveEntry(ctx, rootNode1, "a") if err != nil { t.Fatalf("Couldn't remove file: %v", err) } err = kbfsOps1.RemoveEntry(ctx, rootNode1, "b") if err != nil { t.Fatalf("Couldn't remove file: %v", err) } // Wait for outstanding archives err = kbfsOps1.SyncFromServerForTesting(ctx, rootNode1.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } // Get the current set of blocks bserverLocal, ok := config1.BlockServer().(*BlockServerLocal) if !ok { t.Fatalf("Bad block server") } preQRBlocks, err := bserverLocal.getAll(rootNode1.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't get blocks: %v", err) } clock.Set(now.Add(2 * config1.QuotaReclamationMinUnrefAge())) ops1 := kbfsOps1.(*KBFSOpsStandard).getOpsByNode(ctx, rootNode1) ops1.fbm.forceQuotaReclamation() err = ops1.fbm.waitForQuotaReclamations(ctx) if err != nil { t.Fatalf("Couldn't wait for QR: %v", err) } postQRBlocks, err := bserverLocal.getAll(rootNode1.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't get blocks: %v", err) } if pre, post := totalBlockRefs(preQRBlocks), totalBlockRefs(postQRBlocks); post >= pre { t.Errorf("Blocks didn't shrink after reclamation: pre: %d, post %d", pre, post) } // Sync u2 err = kbfsOps2.SyncFromServerForTesting(ctx, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } // Make a file with the other data on node 2, which uses a block // for which one reference has been deleted, but the other should // still be live. This will cause one dedup reference, and 3 new // blocks (2 from the create, and 1 from the sync). dNode, _, err := kbfsOps2.CreateFile(ctx, rootNode2, "d", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } err = kbfsOps2.Write(ctx, dNode, otherData, 0) if err != nil { t.Fatalf("Couldn't write file: %v", err) } err = kbfsOps2.Sync(ctx, dNode) if err != nil { t.Fatalf("Couldn't write file: %v", err) } // Wait for outstanding archives err = kbfsOps2.SyncFromServerForTesting(ctx, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } // Make the same file on node 2, making sure this doesn't try to // reuse the same block (i.e., there are only 2 put calls). eNode, _, err := kbfsOps2.CreateFile(ctx, rootNode2, "e", false) if err != nil { t.Fatalf("Couldn't create dir: %v", err) } err = kbfsOps2.Write(ctx, eNode, data, 0) if err != nil { t.Fatalf("Couldn't write file: %v", err) } // Stall the puts that comes as part of the sync call. onWriteStalledCh := make(chan struct{}, 2) writeUnstallCh := make(chan struct{}) stallKey := "requestName" writeValue := "write" config2.SetBlockOps(&stallingBlockOps{ stallOpName: "Put", stallKey: stallKey, stallMap: map[interface{}]staller{ writeValue: staller{ stalled: onWriteStalledCh, unstall: writeUnstallCh, }, }, delegate: config2.BlockOps(), }) // Start the sync and wait for it to stall twice only. errChan := make(chan error) go func() { syncCtx := context.WithValue(ctx, stallKey, writeValue) errChan <- kbfsOps2.Sync(syncCtx, eNode) }() <-onWriteStalledCh <-onWriteStalledCh writeUnstallCh <- struct{}{} writeUnstallCh <- struct{}{} // Don't close the channel, we want to make sure other Puts get // stalled. err = <-errChan if err != nil { t.Fatalf("Couldn't sync file: %v", err) } // Wait for outstanding archives err = kbfsOps2.SyncFromServerForTesting(ctx, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } // Delete any blocks that happened to be put during a failed (due // to recoverable block errors) update. clock.Set(now.Add(2 * config1.QuotaReclamationMinUnrefAge())) ops1.fbm.forceQuotaReclamation() err = ops1.fbm.waitForQuotaReclamations(ctx) if err != nil { t.Fatalf("Couldn't wait for QR: %v", err) } endBlocks, err := bserverLocal.getAll(rootNode2.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't get blocks: %v", err) } // There should be exactly 8 extra blocks refs (2 for the create, // and 2 for the write/sync, for both files above) as a result of // the operations, and exactly one should have more than one // reference. if pre, post := totalBlockRefs(postQRBlocks), totalBlockRefs(endBlocks); post != pre+8 { t.Errorf("Different number of blocks than expected: pre: %d, post %d", pre, post) } oneDedupFound := false for id, refs := range endBlocks { if len(refs) > 1 && (len(refs) > 2 || oneDedupFound) { t.Errorf("Block %v unexpectedly had %d references", id, len(refs)) } else if len(refs) == 2 { oneDedupFound = true } } if !oneDedupFound { t.Error("No dedup reference found") } }
// This tests 2 variations of the situation where clients w/o the folder key set the rekey bit. // In one case the client is a writer and in the other a reader. They both blindly copy the existing // metadata and simply set the rekey bit. Then another participant rekeys the folder and they try to read. func TestKeyManagerRekeyBit(t *testing.T) { var u1, u2, u3 libkb.NormalizedUsername = "******", "u2", "u3" config1, _, ctx := kbfsOpsConcurInit(t, u1, u2, u3) doShutdown1 := true defer func() { if doShutdown1 { CheckConfigAndShutdown(t, config1) } }() config1.MDServer().DisableRekeyUpdatesForTesting() config2 := ConfigAsUser(config1.(*ConfigLocal), u2) defer CheckConfigAndShutdown(t, config2) uid2, err := config2.KBPKI().GetCurrentUID(context.Background()) if err != nil { t.Fatal(err) } config2.MDServer().DisableRekeyUpdatesForTesting() config3 := ConfigAsUser(config1.(*ConfigLocal), u3) defer CheckConfigAndShutdown(t, config3) uid3, err := config3.KBPKI().GetCurrentUID(context.Background()) if err != nil { t.Fatal(err) } config3.MDServer().DisableRekeyUpdatesForTesting() // 2 writers 1 reader name := u1.String() + "," + u2.String() + "#" + u3.String() kbfsOps1 := config1.KBFSOps() rootNode1, _, err := kbfsOps1.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Couldn't create folder: %v", err) } // user 1 creates a file _, _, err = kbfsOps1.CreateFile(ctx, rootNode1, "a", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } config2Dev2 := ConfigAsUser(config1.(*ConfigLocal), u2) // we don't check the config because this device can't read all of the md blocks. defer config2Dev2.Shutdown() config2Dev2.MDServer().DisableRekeyUpdatesForTesting() // Now give u2 a new device. The configs don't share a Keybase // Daemon so we have to do it in all places. AddDeviceForLocalUserOrBust(t, config1, uid2) AddDeviceForLocalUserOrBust(t, config2, uid2) AddDeviceForLocalUserOrBust(t, config3, uid2) devIndex := AddDeviceForLocalUserOrBust(t, config2Dev2, uid2) SwitchDeviceForLocalUserOrBust(t, config2Dev2, devIndex) // user 2 should be unable to read the data now since its device // wasn't registered when the folder was originally created. kbfsOps2Dev2 := config2Dev2.KBFSOps() _, _, err = kbfsOps2Dev2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if _, ok := err.(ReadAccessError); !ok { t.Fatalf("Got unexpected error when reading with new key: %v", err) } // now user 2 should set the rekey bit err = kbfsOps2Dev2.Rekey(ctx, rootNode1.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't rekey: %v", err) } // user 1 syncs from server err = kbfsOps1.SyncFromServer(ctx, rootNode1.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } // user 1 should try to rekey err = kbfsOps1.Rekey(ctx, rootNode1.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't rekey: %v", err) } // user 2 syncs from server err = kbfsOps2Dev2.SyncFromServer(ctx, rootNode1.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } // this device should be able to read now rootNode2Dev2, _, err := kbfsOps2Dev2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Got unexpected error after rekey: %v", err) } // look for the file aNode, _, err := kbfsOps2Dev2.Lookup(ctx, rootNode2Dev2, "a") if err != nil { t.Fatalf("Device 2 couldn't lookup a: %v", err) } // read it buf := []byte{0} _, err = kbfsOps2Dev2.Read(ctx, aNode, buf, 0) if err != nil { t.Fatalf("Device 2 couldn't read a: %v", err) } config3Dev2 := ConfigAsUser(config1.(*ConfigLocal), u3) // we don't check the config because this device can't read all of the md blocks. defer config3Dev2.Shutdown() config3Dev2.MDServer().DisableRekeyUpdatesForTesting() // Now give u3 a new device. AddDeviceForLocalUserOrBust(t, config1, uid3) AddDeviceForLocalUserOrBust(t, config2, uid3) AddDeviceForLocalUserOrBust(t, config2Dev2, uid3) AddDeviceForLocalUserOrBust(t, config3, uid3) devIndex = AddDeviceForLocalUserOrBust(t, config3Dev2, uid3) SwitchDeviceForLocalUserOrBust(t, config3Dev2, devIndex) // user 3 dev 2 should be unable to read the data now since its device // wasn't registered when the folder was originally created. kbfsOps3Dev2 := config3Dev2.KBFSOps() _, _, err = kbfsOps3Dev2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if _, ok := err.(ReadAccessError); !ok { t.Fatalf("Got unexpected error when reading with new key: %v", err) } // now user 3 dev 2 should set the rekey bit err = kbfsOps3Dev2.Rekey(ctx, rootNode1.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't rekey: %v", err) } // user 2 dev 2 syncs from server err = kbfsOps2Dev2.SyncFromServer(ctx, rootNode1.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } // user 2 dev 2 should try to rekey err = kbfsOps2Dev2.Rekey(ctx, rootNode1.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't rekey: %v", err) } // user 3 dev 2 syncs from server err = kbfsOps3Dev2.SyncFromServer(ctx, rootNode1.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } // this device should be able to read now rootNode3Dev2, _, err := kbfsOps3Dev2.GetOrCreateRootNode(ctx, name, false, MasterBranch) if err != nil { t.Fatalf("Got unexpected error after rekey: %v", err) } // look for the file a2Node, _, err := kbfsOps3Dev2.Lookup(ctx, rootNode3Dev2, "a") if err != nil { t.Fatalf("Device 3 couldn't lookup a: %v", err) } // read it buf = []byte{0} _, err = kbfsOps3Dev2.Read(ctx, a2Node, buf, 0) if err != nil { t.Fatalf("Device 3 couldn't read a: %v", err) } // Explicitly run the checks with config1 before the deferred shutdowns begin. // This way the shared mdserver hasn't been shutdown. CheckConfigAndShutdown(t, config1) doShutdown1 = false }
// Test that quota reclamation doesn't happen while waiting for a // requested rekey. func TestQuotaReclamationFailAfterRekeyRequest(t *testing.T) { var u1, u2 libkb.NormalizedUsername = "******", "u2" config1, _, ctx := kbfsOpsConcurInit(t, u1, u2) defer CheckConfigAndShutdown(t, config1) clock := newTestClockNow() config1.SetClock(clock) config2 := ConfigAsUser(config1.(*ConfigLocal), u2) defer CheckConfigAndShutdown(t, config2) _, uid2, err := config2.KBPKI().GetCurrentUserInfo(context.Background()) if err != nil { t.Fatal(err) } // Create a shared folder. name := u1.String() + "," + u2.String() rootNode1 := GetRootNodeOrBust(t, config1, name, false) config2Dev2 := ConfigAsUser(config1.(*ConfigLocal), u2) defer CheckConfigAndShutdown(t, config2Dev2) // Now give u2 a new device. The configs don't share a Keybase // Daemon so we have to do it in all places. AddDeviceForLocalUserOrBust(t, config1, uid2) AddDeviceForLocalUserOrBust(t, config2, uid2) devIndex := AddDeviceForLocalUserOrBust(t, config2Dev2, uid2) SwitchDeviceForLocalUserOrBust(t, config2Dev2, devIndex) // user 2 should be unable to read the data now since its device // wasn't registered when the folder was originally created. _, err = GetRootNodeForTest(config2Dev2, name, false) if _, ok := err.(NeedSelfRekeyError); !ok { t.Fatalf("Got unexpected error when reading with new key: %v", err) } // Request a rekey from the new device, which will only be // able to set the rekey bit (copying the root MD). kbfsOps2Dev2 := config2Dev2.KBFSOps() err = kbfsOps2Dev2.Rekey(ctx, rootNode1.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't rekey: %v", err) } // Make sure QR returns an error. ops := config2Dev2.KBFSOps().(*KBFSOpsStandard).getOpsByNode(ctx, rootNode1) timer := time.NewTimer(config2Dev2.QuotaReclamationPeriod()) ops.fbm.reclamationGroup.Add(1) err = ops.fbm.doReclamation(timer) if _, ok := err.(NeedSelfRekeyError); !ok { t.Fatalf("Unexpected rekey error: %v", err) } // Rekey from another device. kbfsOps1 := config1.KBFSOps() err = kbfsOps1.SyncFromServerForTesting(ctx, rootNode1.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } err = kbfsOps1.Rekey(ctx, rootNode1.GetFolderBranch().Tlf) if err != nil { t.Fatalf("Couldn't rekey: %v", err) } // Retry the QR; should work now. err = kbfsOps2Dev2.SyncFromServerForTesting(ctx, rootNode1.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } ops.fbm.reclamationGroup.Add(1) err = ops.fbm.doReclamation(timer) if err != nil { t.Fatalf("Unexpected rekey error: %v", err) } }
// Test that, when writing multiple blocks in parallel under conflict // resolution, one error will cancel the remaining puts and the block // server will be consistent. func TestCRSyncParallelBlocksErrorCleanup(t *testing.T) { // simulate two users var userName1, userName2 libkb.NormalizedUsername = "******", "u2" config1, _, ctx := kbfsOpsConcurInit(t, userName1, userName2) defer CheckConfigAndShutdown(t, config1) config1.MDServer().DisableRekeyUpdatesForTesting() config2 := ConfigAsUser(config1.(*ConfigLocal), userName2) defer CheckConfigAndShutdown(t, config2) _, _, err := config2.KBPKI().GetCurrentUserInfo(context.Background()) if err != nil { t.Fatal(err) } config2.MDServer().DisableRekeyUpdatesForTesting() config2.SetClock(newTestClockNow()) name := userName1.String() + "," + userName2.String() // make blocks small blockSize := int64(5) config1.BlockSplitter().(*BlockSplitterSimple).maxSize = blockSize // create and write to a file rootNode := GetRootNodeOrBust(t, config1, name, false) kbfsOps1 := config1.KBFSOps() _, _, err = kbfsOps1.CreateFile(ctx, rootNode, "a", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // look it up on user2 rootNode2 := GetRootNodeOrBust(t, config2, name, false) kbfsOps2 := config2.KBFSOps() _, _, err = kbfsOps2.Lookup(ctx, rootNode2, "a") if err != nil { t.Fatalf("Couldn't lookup dir: %v", err) } // disable updates and CR on user 2 c, err := DisableUpdatesForTesting(config2, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't disable updates: %v", err) } err = DisableCRForTesting(config2, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't disable updates: %v", err) } // User 1 creates a new file to start a conflict. _, _, err = kbfsOps1.CreateFile(ctx, rootNode, "b", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // User 2 does one successful operation to create the first unmerged MD. fileNodeB, _, err := kbfsOps2.CreateFile(ctx, rootNode2, "b", false) if err != nil { t.Fatalf("Couldn't create file: %v", err) } // Now user 2 makes a big write where most of the blocks get canceled. // We only need to know the first time we stall. onSyncStalledCh := make(chan struct{}, maxParallelBlockPuts) syncUnstallCh := make(chan struct{}) stallKey := "requestName" syncValue := "sync" config2.SetBlockOps(&stallingBlockOps{ stallOpName: "Put", stallKey: stallKey, stallMap: map[interface{}]staller{ syncValue: staller{ stalled: onSyncStalledCh, unstall: syncUnstallCh, }, }, delegate: config2.BlockOps(), }) // User 2 writes some data fileBlocks := int64(15) var data []byte for i := int64(0); i < blockSize*fileBlocks; i++ { data = append(data, byte(i)) } err = kbfsOps2.Write(ctx, fileNodeB, data, 0) if err != nil { t.Fatalf("Couldn't write: %v", err) } // Start the sync and wait for it to stall. var wg sync.WaitGroup wg.Add(1) syncCtx, cancel := context.WithCancel(context.Background()) var syncErr error go func() { defer wg.Done() syncCtx = context.WithValue(syncCtx, stallKey, syncValue) syncErr = kbfsOps2.Sync(syncCtx, fileNodeB) }() // Wait for 2 of the blocks and let them go <-onSyncStalledCh <-onSyncStalledCh syncUnstallCh <- struct{}{} syncUnstallCh <- struct{}{} // Wait for the rest of the puts (this indicates that the first // two succeeded correctly and two more were sent to replace them) for i := 0; i < maxParallelBlockPuts; i++ { <-onSyncStalledCh } // Cancel so all other block puts fail cancel() close(syncUnstallCh) wg.Wait() // Get the mdWriterLock to be sure the sync has exited (since the // cleanup logic happens in a background goroutine) ops := getOps(config2, rootNode2.GetFolderBranch().Tlf) lState := makeFBOLockState() ops.mdWriterLock.Lock(lState) ops.mdWriterLock.Unlock(lState) // The state checker will make sure those blocks from // the failed sync get cleaned up. // Now succeed with different data so CR can happen. config2.SetBlockOps(config2.BlockOps().(*stallingBlockOps).delegate) for i := int64(0); i < blockSize*fileBlocks; i++ { data[i] = byte(i + 10) } err = kbfsOps2.Write(ctx, fileNodeB, data, 0) if err != nil { t.Fatalf("Couldn't write: %v", err) } err = kbfsOps2.Sync(ctx, fileNodeB) if err != nil { t.Fatalf("Couldn't sync: %v", err) } c <- struct{}{} err = RestartCRForTesting(config2, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't disable updates: %v", err) } err = kbfsOps2.SyncFromServerForTesting(ctx, rootNode2.GetFolderBranch()) if err != nil { t.Fatalf("Couldn't sync from server: %v", err) } }