blob: 6c87ee78d82342ea4a3ec9d6e5150e42dc786736 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package vsync
// Tests for SyncGroup management and storage in Syncbase.
import (
"reflect"
"testing"
"time"
"v.io/syncbase/v23/services/syncbase/nosql"
"v.io/syncbase/x/ref/services/syncbase/server/interfaces"
"v.io/syncbase/x/ref/services/syncbase/store"
_ "v.io/x/ref/runtime/factories/generic"
)
// checkSGStats verifies SyncGroup stats.
func checkSGStats(t *testing.T, svc *mockService, which string, numSG, numMembers int) {
memberViewTTL = 0 // Always recompute the SyncGroup membership view.
svc.sync.refreshMembersIfExpired(nil)
view := svc.sync.allMembers
if num := len(view.members); num != numMembers {
t.Errorf("num-members (%s): got %v instead of %v", which, num, numMembers)
}
sgids := make(map[interfaces.GroupId]bool)
for _, info := range view.members {
for _, sgmi := range info.db2sg {
for gid := range sgmi {
sgids[gid] = true
}
}
}
if num := len(sgids); num != numSG {
t.Errorf("num-syncgroups (%s): got %v instead of %v", which, num, numSG)
}
}
// TestAddSyncGroup tests adding SyncGroups.
func TestAddSyncGroup(t *testing.T) {
// Set a large value to prevent the initiator from running. Since this
// test adds a fake SyncGroup, if the initiator runs, it will attempt
// to initiate using this fake and partial SyncGroup data.
peerSyncInterval = 1 * time.Hour
svc := createService(t)
defer destroyService(t, svc)
st := svc.St()
checkSGStats(t, svc, "add-1", 0, 0)
// Add a SyncGroup.
sgName := "foobar"
sgId := interfaces.GroupId(1234)
sg := &interfaces.SyncGroup{
Name: sgName,
Id: sgId,
AppName: "mockApp",
DbName: "mockDB",
Creator: "mockCreator",
SpecVersion: "etag-0",
Spec: nosql.SyncGroupSpec{
Prefixes: []string{"foo", "bar"},
},
Joiners: map[string]nosql.SyncGroupMemberInfo{
"phone": nosql.SyncGroupMemberInfo{SyncPriority: 10},
"tablet": nosql.SyncGroupMemberInfo{SyncPriority: 25},
"cloud": nosql.SyncGroupMemberInfo{SyncPriority: 1},
},
}
tx := st.NewTransaction()
if err := addSyncGroup(nil, tx, sg); err != nil {
t.Errorf("cannot add SyncGroup ID %d: %v", sg.Id, err)
}
if err := tx.Commit(); err != nil {
t.Errorf("cannot commit adding SyncGroup ID %d: %v", sg.Id, err)
}
// Verify SyncGroup ID, name, and data.
if id, err := getSyncGroupId(nil, st, sgName); err != nil || id != sgId {
t.Errorf("cannot get ID of SyncGroup %s: got %d instead of %d; err: %v", sgName, id, sgId, err)
}
if name, err := getSyncGroupName(nil, st, sgId); err != nil || name != sgName {
t.Errorf("cannot get name of SyncGroup %d: got %s instead of %s; err: %v",
sgId, name, sgName, err)
}
sgOut, err := getSyncGroupById(nil, st, sgId)
if err != nil {
t.Errorf("cannot get SyncGroup by ID %d: %v", sgId, err)
}
if !reflect.DeepEqual(sgOut, sg) {
t.Errorf("invalid SyncGroup data for group ID %d: got %v instead of %v", sgId, sgOut, sg)
}
sgOut, err = getSyncGroupByName(nil, st, sgName)
if err != nil {
t.Errorf("cannot get SyncGroup by Name %s: %v", sgName, err)
}
if !reflect.DeepEqual(sgOut, sg) {
t.Errorf("invalid SyncGroup data for group name %s: got %v instead of %v", sgName, sgOut, sg)
}
// Verify membership data.
expMembers := map[string]uint32{"phone": 1, "tablet": 1, "cloud": 1}
members := svc.sync.getMembers(nil)
if !reflect.DeepEqual(members, expMembers) {
t.Errorf("invalid SyncGroup members: got %v instead of %v", members, expMembers)
}
view := svc.sync.allMembers
for mm := range members {
mi := view.members[mm]
if mi == nil {
t.Errorf("cannot get info for SyncGroup member %s", mm)
}
if len(mi.db2sg) != 1 {
t.Errorf("invalid info for SyncGroup member %s: %v", mm, mi)
}
var sgmi sgMemberInfo
for _, v := range mi.db2sg {
sgmi = v
break
}
if len(sgmi) != 1 {
t.Errorf("invalid member info for SyncGroup member %s: %v", mm, sgmi)
}
expJoinerInfo := sg.Joiners[mm]
joinerInfo := sgmi[sgId]
if !reflect.DeepEqual(joinerInfo, expJoinerInfo) {
t.Errorf("invalid Info for SyncGroup member %s in group ID %d: got %v instead of %v",
mm, sgId, joinerInfo, expJoinerInfo)
}
}
checkSGStats(t, svc, "add-2", 1, 3)
// Adding a SyncGroup for a pre-existing group ID or name should fail.
sg.Name = "another-name"
tx = st.NewTransaction()
if err = addSyncGroup(nil, tx, sg); err == nil {
t.Errorf("re-adding SyncGroup %d did not fail", sgId)
}
tx.Abort()
sg.Name = sgName
sg.Id = interfaces.GroupId(5555)
tx = st.NewTransaction()
if err = addSyncGroup(nil, tx, sg); err == nil {
t.Errorf("adding SyncGroup %s with a different ID did not fail", sgName)
}
tx.Abort()
checkSGStats(t, svc, "add-3", 1, 3)
// Fetch a non-existing SyncGroup by ID or name should fail.
badName := "not-available"
badId := interfaces.GroupId(999)
if id, err := getSyncGroupId(nil, st, badName); err == nil {
t.Errorf("found non-existing SyncGroup %s: got ID %d", badName, id)
}
if name, err := getSyncGroupName(nil, st, badId); err == nil {
t.Errorf("found non-existing SyncGroup %d: got name %s", badId, name)
}
if sg, err := getSyncGroupByName(nil, st, badName); err == nil {
t.Errorf("found non-existing SyncGroup %s: got %v", badName, sg)
}
if sg, err := getSyncGroupById(nil, st, badId); err == nil {
t.Errorf("found non-existing SyncGroup %d: got %v", badId, sg)
}
}
// TestInvalidAddSyncGroup tests adding SyncGroups.
func TestInvalidAddSyncGroup(t *testing.T) {
// Set a large value to prevent the threads from firing.
peerSyncInterval = 1 * time.Hour
svc := createService(t)
defer destroyService(t, svc)
st := svc.St()
checkBadAddSyncGroup := func(t *testing.T, st store.Store, sg *interfaces.SyncGroup, msg string) {
tx := st.NewTransaction()
if err := addSyncGroup(nil, tx, sg); err == nil {
t.Errorf("checkBadAddSyncGroup: adding bad SyncGroup (%s) did not fail", msg)
}
tx.Abort()
}
checkBadAddSyncGroup(t, st, nil, "nil SG")
sg := &interfaces.SyncGroup{Id: 1234}
checkBadAddSyncGroup(t, st, sg, "SG w/o name")
sg = &interfaces.SyncGroup{Name: "foobar"}
checkBadAddSyncGroup(t, st, sg, "SG w/o Id")
sg.Id = 1234
checkBadAddSyncGroup(t, st, sg, "SG w/o Version")
sg.SpecVersion = "v1"
checkBadAddSyncGroup(t, st, sg, "SG w/o Joiners")
sg.Joiners = map[string]nosql.SyncGroupMemberInfo{
"phone": nosql.SyncGroupMemberInfo{SyncPriority: 10},
}
checkBadAddSyncGroup(t, st, sg, "SG w/o Prefixes")
}
// TestDeleteSyncGroup tests deleting a SyncGroup.
func TestDeleteSyncGroup(t *testing.T) {
// Set a large value to prevent the threads from firing.
peerSyncInterval = 1 * time.Hour
svc := createService(t)
defer destroyService(t, svc)
st := svc.St()
sgName := "foobar"
sgId := interfaces.GroupId(1234)
// Delete non-existing SyncGroups.
tx := st.NewTransaction()
if err := delSyncGroupById(nil, tx, sgId); err == nil {
t.Errorf("deleting a non-existing SyncGroup ID did not fail")
}
if err := delSyncGroupByName(nil, tx, sgName); err == nil {
t.Errorf("deleting a non-existing SyncGroup name did not fail")
}
tx.Abort()
checkSGStats(t, svc, "del-1", 0, 0)
// Create the SyncGroup to delete later.
sg := &interfaces.SyncGroup{
Name: sgName,
Id: sgId,
AppName: "mockApp",
DbName: "mockDB",
Creator: "mockCreator",
SpecVersion: "etag-0",
Spec: nosql.SyncGroupSpec{
Prefixes: []string{"foo", "bar"},
},
Joiners: map[string]nosql.SyncGroupMemberInfo{
"phone": nosql.SyncGroupMemberInfo{SyncPriority: 10},
"tablet": nosql.SyncGroupMemberInfo{SyncPriority: 25},
"cloud": nosql.SyncGroupMemberInfo{SyncPriority: 1},
},
}
tx = st.NewTransaction()
if err := addSyncGroup(nil, tx, sg); err != nil {
t.Errorf("creating SyncGroup ID %d failed: %v", sgId, err)
}
if err := tx.Commit(); err != nil {
t.Errorf("cannot commit adding SyncGroup ID %d: %v", sgId, err)
}
checkSGStats(t, svc, "del-2", 1, 3)
// Delete it by ID.
tx = st.NewTransaction()
if err := delSyncGroupById(nil, tx, sgId); err != nil {
t.Errorf("deleting SyncGroup ID %d failed: %v", sgId, err)
}
if err := tx.Commit(); err != nil {
t.Errorf("cannot commit deleting SyncGroup ID %d: %v", sgId, err)
}
checkSGStats(t, svc, "del-3", 0, 0)
// Create it again then delete it by name.
tx = st.NewTransaction()
if err := addSyncGroup(nil, tx, sg); err != nil {
t.Errorf("creating SyncGroup ID %d after delete failed: %v", sgId, err)
}
if err := tx.Commit(); err != nil {
t.Errorf("cannot commit adding SyncGroup ID %d after delete: %v", sgId, err)
}
checkSGStats(t, svc, "del-4", 1, 3)
tx = st.NewTransaction()
if err := delSyncGroupByName(nil, tx, sgName); err != nil {
t.Errorf("deleting SyncGroup name %s failed: %v", sgName, err)
}
if err := tx.Commit(); err != nil {
t.Errorf("cannot commit deleting SyncGroup name %s: %v", sgName, err)
}
checkSGStats(t, svc, "del-5", 0, 0)
}
// TestMultiSyncGroups tests creating multiple SyncGroups.
func TestMultiSyncGroups(t *testing.T) {
// Set a large value to prevent the threads from firing.
peerSyncInterval = 1 * time.Hour
svc := createService(t)
defer destroyService(t, svc)
st := svc.St()
sgName1, sgName2 := "foo", "bar"
sgId1, sgId2 := interfaces.GroupId(1234), interfaces.GroupId(8888)
// Add two SyncGroups.
sg1 := &interfaces.SyncGroup{
Name: sgName1,
Id: sgId1,
AppName: "mockApp",
DbName: "mockDB",
Creator: "mockCreator",
SpecVersion: "etag-1",
Spec: nosql.SyncGroupSpec{
Prefixes: []string{"foo"},
},
Joiners: map[string]nosql.SyncGroupMemberInfo{
"phone": nosql.SyncGroupMemberInfo{SyncPriority: 10},
"tablet": nosql.SyncGroupMemberInfo{SyncPriority: 25},
"cloud": nosql.SyncGroupMemberInfo{SyncPriority: 1},
},
}
sg2 := &interfaces.SyncGroup{
Name: sgName2,
Id: sgId2,
AppName: "mockApp",
DbName: "mockDB",
Creator: "mockCreator",
SpecVersion: "etag-2",
Spec: nosql.SyncGroupSpec{
Prefixes: []string{"bar"},
},
Joiners: map[string]nosql.SyncGroupMemberInfo{
"tablet": nosql.SyncGroupMemberInfo{SyncPriority: 111},
"door": nosql.SyncGroupMemberInfo{SyncPriority: 33},
"lamp": nosql.SyncGroupMemberInfo{SyncPriority: 9},
},
}
tx := st.NewTransaction()
if err := addSyncGroup(nil, tx, sg1); err != nil {
t.Errorf("creating SyncGroup ID %d failed: %v", sgId1, err)
}
if err := tx.Commit(); err != nil {
t.Errorf("cannot commit adding SyncGroup ID %d: %v", sgId1, err)
}
checkSGStats(t, svc, "multi-1", 1, 3)
tx = st.NewTransaction()
if err := addSyncGroup(nil, tx, sg2); err != nil {
t.Errorf("creating SyncGroup ID %d failed: %v", sgId2, err)
}
if err := tx.Commit(); err != nil {
t.Errorf("cannot commit adding SyncGroup ID %d: %v", sgId2, err)
}
checkSGStats(t, svc, "multi-2", 2, 5)
// Verify membership data.
expMembers := map[string]uint32{"phone": 1, "tablet": 2, "cloud": 1, "door": 1, "lamp": 1}
members := svc.sync.getMembers(nil)
if !reflect.DeepEqual(members, expMembers) {
t.Errorf("invalid SyncGroup members: got %v instead of %v", members, expMembers)
}
expMemberInfo := map[string]*memberInfo{
"phone": &memberInfo{
db2sg: map[string]sgMemberInfo{
"mockapp:mockdb": sgMemberInfo{
sgId1: sg1.Joiners["phone"],
},
},
},
"tablet": &memberInfo{
db2sg: map[string]sgMemberInfo{
"mockapp:mockdb": sgMemberInfo{
sgId1: sg1.Joiners["tablet"],
sgId2: sg2.Joiners["tablet"],
},
},
},
"cloud": &memberInfo{
db2sg: map[string]sgMemberInfo{
"mockapp:mockdb": sgMemberInfo{
sgId1: sg1.Joiners["cloud"],
},
},
},
"door": &memberInfo{
db2sg: map[string]sgMemberInfo{
"mockapp:mockdb": sgMemberInfo{
sgId2: sg2.Joiners["door"],
},
},
},
"lamp": &memberInfo{
db2sg: map[string]sgMemberInfo{
"mockapp:mockdb": sgMemberInfo{
sgId2: sg2.Joiners["lamp"],
},
},
},
}
view := svc.sync.allMembers
for mm := range members {
mi := view.members[mm]
if mi == nil {
t.Errorf("cannot get info for SyncGroup member %s", mm)
}
expInfo := expMemberInfo[mm]
if !reflect.DeepEqual(mi, expInfo) {
t.Errorf("invalid Info for SyncGroup member %s: got %v instead of %v", mm, mi, expInfo)
}
}
// Delete the 1st SyncGroup.
tx = st.NewTransaction()
if err := delSyncGroupById(nil, tx, sgId1); err != nil {
t.Errorf("deleting SyncGroup ID %d failed: %v", sgId1, err)
}
if err := tx.Commit(); err != nil {
t.Errorf("cannot commit deleting SyncGroup ID %d: %v", sgId1, err)
}
checkSGStats(t, svc, "multi-3", 1, 3)
// Verify SyncGroup membership data.
expMembers = map[string]uint32{"tablet": 1, "door": 1, "lamp": 1}
members = svc.sync.getMembers(nil)
if !reflect.DeepEqual(members, expMembers) {
t.Errorf("invalid SyncGroup members: got %v instead of %v", members, expMembers)
}
expMemberInfo = map[string]*memberInfo{
"tablet": &memberInfo{
db2sg: map[string]sgMemberInfo{
"mockapp:mockdb": sgMemberInfo{
sgId2: sg2.Joiners["tablet"],
},
},
},
"door": &memberInfo{
db2sg: map[string]sgMemberInfo{
"mockapp:mockdb": sgMemberInfo{
sgId2: sg2.Joiners["door"],
},
},
},
"lamp": &memberInfo{
db2sg: map[string]sgMemberInfo{
"mockapp:mockdb": sgMemberInfo{
sgId2: sg2.Joiners["lamp"],
},
},
},
}
view = svc.sync.allMembers
for mm := range members {
mi := view.members[mm]
if mi == nil {
t.Errorf("cannot get info for SyncGroup member %s", mm)
}
expInfo := expMemberInfo[mm]
if !reflect.DeepEqual(mi, expInfo) {
t.Errorf("invalid Info for SyncGroup member %s: got %v instead of %v", mm, mi, expInfo)
}
}
}