func makeTestGossip(t *testing.T) (*gossip.Gossip, func()) { n := simulation.NewNetwork(1, "tcp", gossip.TestInterval) g := n.Nodes[0].Gossip permConfig := &config.PermConfig{ Read: []string{""}, Write: []string{""}, } configMap, err := config.NewPrefixConfigMap([]*config.PrefixConfig{ {proto.KeyMin, nil, permConfig}, }) if err != nil { t.Fatalf("failed to make prefix config map, err: %s", err.Error()) } if err := g.AddInfo(gossip.KeySentinel, "cluster1", time.Hour); err != nil { t.Fatal(err) } if err := g.AddInfo(gossip.KeyConfigPermission, configMap, time.Hour); err != nil { t.Fatal(err) } if err := g.AddInfo(gossip.KeyFirstRangeDescriptor, testRangeDescriptor, time.Hour); err != nil { t.Fatal(err) } nodeIDKey := gossip.MakeNodeIDKey(1) if err := g.AddInfo(nodeIDKey, &proto.NodeDescriptor{ NodeID: 1, Address: util.MakeUnresolvedAddr(testAddress.Network(), testAddress.String()), Attrs: proto.Attributes{Attrs: []string{"attr1", "attr2"}}, }, time.Hour); err != nil { t.Fatal(err) } return g, n.Stop }
func TestGetFirstRangeDescriptor(t *testing.T) { n := simulation.NewNetwork(3, "unix", gossip.TestInterval, gossip.TestBootstrap) ds := NewDistSender(n.Nodes[0].Gossip) if _, err := ds.getFirstRangeDescriptor(); err == nil { t.Errorf("expected not to find first range descriptor") } expectedDesc := &proto.RangeDescriptor{} expectedDesc.StartKey = proto.Key("a") expectedDesc.EndKey = proto.Key("c") // Add first RangeDescriptor to a node different from the node for // this dist sender and ensure that this dist sender has the // information within a given time. n.Nodes[1].Gossip.AddInfo( gossip.KeyFirstRangeDescriptor, *expectedDesc, time.Hour) maxCycles := 10 n.SimulateNetwork(func(cycle int, network *simulation.Network) bool { desc, err := ds.getFirstRangeDescriptor() if err != nil { if cycle >= maxCycles { t.Errorf("could not get range descriptor after %d cycles", cycle) return false } return true } if !bytes.Equal(desc.StartKey, expectedDesc.StartKey) || !bytes.Equal(desc.EndKey, expectedDesc.EndKey) { t.Errorf("expected first range descriptor %v, instead was %v", expectedDesc, desc) } return false }) n.Stop() }
// verifyConvergence verifies that info from each node is visible from // every node in the network within numCycles cycles of the gossip protocol. func verifyConvergence(numNodes, maxCycles int, _ *testing.T) { network := simulation.NewNetwork(numNodes) if connectedCycle := network.RunUntilFullyConnected(); connectedCycle > maxCycles { log.Warningf("expected a fully-connected network within %d cycles; took %d", maxCycles, connectedCycle) } network.Stop() }
// verifyConvergence verifies that info from each node is visible from // every node in the network within numCycles cycles of the gossip protocol. // NOTE: This test is non-deterministic because it involves multiple goroutines // that may not all be able to keep up if the given interval is too small. // As a rule of thumb, increase the interval until the same number of cycles are used // for both race and non-race tests; this indicates that no goroutines are being // left behind by CPU limits. // TODO(spencer): figure out a more deterministic setup, advancing the clock // manually and counting cycles accurately instead of relying on real-time sleeps. func verifyConvergence(numNodes, maxCycles int, interval time.Duration, t *testing.T) { network := simulation.NewNetwork(numNodes, "tcp", interval) if connectedCycle := network.RunUntilFullyConnected(); connectedCycle > maxCycles { t.Errorf("expected a fully-connected network within %d cycles; took %d", maxCycles, connectedCycle) } network.Stop() }
func main() { // Seed the random number generator for non-determinism across // multiple runs. randutil.SeedForTests() if f := flag.Lookup("alsologtostderr"); f != nil { fmt.Println("Starting simulation. Add -alsologtostderr to see progress.") } flag.Parse() dirName, err := ioutil.TempDir("", "gossip-simulation-") if err != nil { log.Fatalf(context.TODO(), "could not create temporary directory for gossip simulation output: %s", err) } // Simulation callbacks to run the simulation for cycleCount // cycles. At each cycle % outputEvery, a dot file showing the // state of the network graph is output. nodeCount := 3 switch *size { case "tiny": // Use default parameters. case "small": nodeCount = 10 case "medium": nodeCount = 25 case "large": nodeCount = 50 case "huge": nodeCount = 100 case "ginormous": nodeCount = 250 default: log.Fatalf(context.TODO(), "unknown simulation size: %s", *size) } edgeSet := make(map[string]edge) stopper := stop.NewStopper() defer stopper.Stop() n := simulation.NewNetwork(stopper, nodeCount, true) n.SimulateNetwork( func(cycle int, network *simulation.Network) bool { // Output dot graph. dotFN := fmt.Sprintf("%s/sim-cycle-%03d.dot", dirName, cycle) _, quiescent := outputDotFile(dotFN, cycle, network, edgeSet) // Run until network has quiesced. return !quiescent }, ) // Output instructions for viewing graphs. fmt.Printf("To view simulation graph output run (you must install graphviz):\n\nfor f in %s/*.dot ; do circo $f -Tpng -o $f.png ; echo $f.png ; done\n", dirName) }
// TestGossipStorageCleanup verifies that bad resolvers are purged // from the bootstrap info after gossip has successfully connected. func TestGossipStorageCleanup(t *testing.T) { defer leaktest.AfterTest(t)() stopper := stop.NewStopper() defer stopper.Stop() const numNodes = 3 network := simulation.NewNetwork(stopper, numNodes, false) const notReachableAddr = "localhost:0" const invalidAddr = "10.0.0.1000:3333333" // Set storage for each of the nodes. addresses := make(unresolvedAddrSlice, len(network.Nodes)) stores := make([]testStorage, len(network.Nodes)) for i, n := range network.Nodes { addresses[i] = util.MakeUnresolvedAddr(n.Addr().Network(), n.Addr().String()) // Pre-add an invalid address to each gossip storage. if err := stores[i].WriteBootstrapInfo(&gossip.BootstrapInfo{ Addresses: []util.UnresolvedAddr{ util.MakeUnresolvedAddr("tcp", network.Nodes[(i+1)%numNodes].Addr().String()), // node i+1 address util.MakeUnresolvedAddr("tcp", notReachableAddr), // unreachable address util.MakeUnresolvedAddr("tcp", invalidAddr), // invalid address }, }); err != nil { t.Fatal(err) } if err := n.Gossip.SetStorage(&stores[i]); err != nil { t.Fatal(err) } n.Gossip.SetStallInterval(1 * time.Millisecond) n.Gossip.SetBootstrapInterval(1 * time.Millisecond) } // Wait for the gossip network to connect. network.RunUntilFullyConnected() // Wait long enough for storage to get the expected number of // addresses and no pending cleanups. util.SucceedsSoon(t, func() error { for i := range stores { p := &stores[i] if expected, actual := len(network.Nodes)-1 /* -1 is ourself */, p.Len(); expected != actual { return errors.Errorf("expected %v, got %v (info: %#v)", expected, actual, p.Info().Addresses) } for _, addr := range p.Info().Addresses { if addr.String() == invalidAddr { return errors.Errorf("node %d still needs bootstrap cleanup", i) } } } return nil }) }
// TestConvergence verifies a 10 node gossip network converges within // a fixed number of simulation cycles. It's really difficult to // determine the right number for cycles because different things can // happen during a single cycle, depending on how much CPU time is // available. Eliminating this variability by getting more // synchronization primitives in place for the simulation is possible, // though two attempts so far have introduced more complexity into the // actual production gossip code than seems worthwhile for a unittest. func TestConvergence(t *testing.T) { defer leaktest.AfterTest(t)() stopper := stop.NewStopper() defer stopper.Stop() network := simulation.NewNetwork(stopper, 10, true) const maxCycles = 100 if connectedCycle := network.RunUntilFullyConnected(); connectedCycle > maxCycles { log.Warningf(context.TODO(), "expected a fully-connected network within %d cycles; took %d", maxCycles, connectedCycle) } }
func makeTestGossip(t *testing.T) (*gossip.Gossip, func()) { n := simulation.NewNetwork(1) g := n.Nodes[0].Gossip if err := g.AddInfo(gossip.KeySentinel, nil, time.Hour); err != nil { t.Fatal(err) } if err := g.AddInfoProto(gossip.KeyFirstRangeDescriptor, &testRangeDescriptor, time.Hour); err != nil { t.Fatal(err) } nodeIDKey := gossip.MakeNodeIDKey(1) if err := g.AddInfoProto(nodeIDKey, &roachpb.NodeDescriptor{ NodeID: 1, Address: util.MakeUnresolvedAddr(testAddress.Network(), testAddress.String()), Attrs: roachpb.Attributes{Attrs: []string{"attr1", "attr2"}}, }, time.Hour); err != nil { t.Fatal(err) } return g, n.Stop }
func TestGetFirstRangeDescriptor(t *testing.T) { defer leaktest.AfterTest(t)() n := simulation.NewNetwork(3) ds := NewDistSender(nil, n.Nodes[0].Gossip) if _, err := ds.FirstRange(); err == nil { t.Errorf("expected not to find first range descriptor") } expectedDesc := &roachpb.RangeDescriptor{} expectedDesc.StartKey = roachpb.RKey("a") expectedDesc.EndKey = roachpb.RKey("c") // Add first RangeDescriptor to a node different from the node for // this dist sender and ensure that this dist sender has the // information within a given time. if err := n.Nodes[1].Gossip.AddInfoProto(gossip.KeyFirstRangeDescriptor, expectedDesc, time.Hour); err != nil { t.Fatal(err) } maxCycles := 10 n.SimulateNetwork(func(cycle int, network *simulation.Network) bool { desc, err := ds.FirstRange() if err != nil { if cycle >= maxCycles { t.Errorf("could not get range descriptor after %d cycles", cycle) return false } return true } if !bytes.Equal(desc.StartKey, expectedDesc.StartKey) || !bytes.Equal(desc.EndKey, expectedDesc.EndKey) { t.Errorf("expected first range descriptor %v, instead was %v", expectedDesc, desc) } return false }) n.Stop() }
func main() { // Seed the random number generator for non-determinism across // multiple runs. rand.Seed(time.Now().UnixNano()) if f := flag.Lookup("alsologtostderr"); f != nil { fmt.Println("Starting simulation. Add -alsologtostderr to see progress.") } flag.Parse() dirName, err := ioutil.TempDir("", "gossip-simulation-") if err != nil { log.Fatalf("could not create temporary directory for gossip simulation output: %s", err) } // Simulation callbacks to run the simulation for cycleCount // cycles. At each cycle % outputEvery, a dot file showing the // state of the network graph is output. nodeCount := 3 gossipInterval := simGossipInterval numCycles := 10 outputEvery := 1 switch *size { case "tiny": // Use default parameters. case "small": nodeCount = 10 case "medium": nodeCount = 25 case "large": nodeCount = 50 gossipInterval = time.Millisecond * 250 case "huge": nodeCount = 100 gossipInterval = time.Second numCycles = 20 outputEvery = 2 case "ginormous": nodeCount = 250 gossipInterval = time.Second * 3 numCycles = 20 outputEvery = 2 default: log.Fatalf("unknown simulation size: %s", *size) } edgeSet := make(map[string]edge) n := simulation.NewNetwork(nodeCount, *networkType, gossipInterval) n.SimulateNetwork( func(cycle int, network *simulation.Network) bool { if cycle == numCycles { return false } // Update infos. nodes := network.Nodes for i := 0; i < len(nodes); i++ { node := nodes[i].Gossip if err := node.AddInfo(nodes[i].Addr.String(), int64(cycle), time.Hour); err != nil { log.Infof("error updating infos addr: %s cycle: %v: %s", nodes[i].Addr.String(), cycle, err) } } // Output dot graph periodically. if (cycle+1)%outputEvery == 0 { dotFN := fmt.Sprintf("%s/sim-cycle-%d.dot", dirName, cycle) outputDotFile(dotFN, cycle, network, edgeSet) } return true }) n.Stop() // Output instructions for viewing graphs. fmt.Printf("To view simulation graph output run (you must install graphviz):\n\nfor f in %s/*.dot ; do circo $f -Tpng -o $f.png ; echo $f.png ; done\n", dirName) }
// TestGossipStorage verifies that a gossip node can join the cluster // using the bootstrap hosts in a gossip.Storage object. func TestGossipStorage(t *testing.T) { defer leaktest.AfterTest(t)() network := simulation.NewNetwork(3) defer network.Stop() // Set storage for each of the nodes. addresses := make(unresolvedAddrSlice, len(network.Nodes)) stores := make([]*testStorage, len(network.Nodes)) for i, n := range network.Nodes { addresses[i] = util.MakeUnresolvedAddr(n.Addr.Network(), n.Addr.String()) stores[i] = new(testStorage) if err := n.Gossip.SetStorage(stores[i]); err != nil { t.Fatal(err) } } // Wait for the gossip network to connect. network.RunUntilFullyConnected() // Wait long enough for storage to get the expected number of addresses. util.SucceedsSoon(t, func() error { for _, p := range stores { if p.Len() != 2 { return util.Errorf("incorrect number of addresses: expected 2; got %d", p.Len()) } } return nil }) for i, p := range stores { if !p.isRead() { t.Errorf("%d: expected read from storage", i) } if !p.isWrite() { t.Errorf("%d: expected write from storage", i) } p.Lock() gotAddresses := unresolvedAddrSlice(p.info.Addresses) sort.Sort(gotAddresses) var expectedAddresses unresolvedAddrSlice for j, addr := range addresses { if i != j { // skip node's own address expectedAddresses = append(expectedAddresses, addr) } } sort.Sort(expectedAddresses) // Verify all gossip addresses are written to each persistent store. if !reflect.DeepEqual(gotAddresses, expectedAddresses) { t.Errorf("%d: expected addresses: %s, got: %s", i, expectedAddresses, gotAddresses) } p.Unlock() } // Create an unaffiliated gossip node with only itself as a resolver, // leaving it no way to reach the gossip network. node, err := network.CreateNode() if err != nil { t.Fatal(err) } node.Gossip.SetBootstrapInterval(1 * time.Millisecond) r, err := resolver.NewResolverFromAddress(node.Addr) if err != nil { t.Fatal(err) } node.Gossip.SetResolvers([]resolver.Resolver{r}) if err := network.StartNode(node); err != nil { t.Fatal(err) } // Wait for a bit to ensure no connection. select { case <-time.After(10 * time.Millisecond): // expected outcome... case <-node.Gossip.Connected: t.Fatal("unexpectedly connected to gossip") } // Give the new node storage with info established from a node // in the established network. var ts2 testStorage if err := stores[0].ReadBootstrapInfo(&ts2.info); err != nil { t.Fatal(err) } if err := node.Gossip.SetStorage(&ts2); err != nil { t.Fatal(err) } network.SimulateNetwork(func(cycle int, network *simulation.Network) bool { if cycle > 1000 { t.Fatal("failed to connect to gossip") } select { case <-node.Gossip.Connected: return false default: return true } }) }
// TestVerifyPermissions verifies permissions are checked for single // zones and across multiple zones. It also verifies that permissions // are checked hierarchically. func TestVerifyPermissions(t *testing.T) { defer leaktest.AfterTest(t) n := simulation.NewNetwork(1, "tcp", gossip.TestInterval) ds := NewDistSender(nil, n.Nodes[0].Gossip) config1 := &proto.PermConfig{ Read: []string{"read1", "readAll", "rw1", "rwAll"}, Write: []string{"write1", "writeAll", "rw1", "rwAll"}} config2 := &proto.PermConfig{ Read: []string{"read2", "readAll", "rw2", "rwAll"}, Write: []string{"write2", "writeAll", "rw2", "rwAll"}} configs := []*storage.PrefixConfig{ {proto.KeyMin, nil, config1}, {proto.Key("a"), nil, config2}, } configMap, err := storage.NewPrefixConfigMap(configs) if err != nil { t.Fatalf("failed to make prefix config map, err: %s", err.Error()) } if err := ds.gossip.AddInfo(gossip.KeyConfigPermission, configMap, time.Hour); err != nil { t.Fatal(err) } allRequestTypes := []proto.Request{ &proto.GetRequest{}, &proto.PutRequest{}, &proto.ConditionalPutRequest{}, &proto.IncrementRequest{}, &proto.DeleteRequest{}, &proto.DeleteRangeRequest{}, &proto.ScanRequest{}, &proto.EndTransactionRequest{}, &proto.BatchRequest{}, &proto.AdminSplitRequest{}, &proto.AdminMergeRequest{}, &proto.InternalHeartbeatTxnRequest{}, &proto.InternalGCRequest{}, &proto.InternalPushTxnRequest{}, &proto.InternalRangeLookupRequest{}, &proto.InternalResolveIntentRequest{}, &proto.InternalResolveIntentRangeRequest{}, &proto.InternalMergeRequest{}, &proto.InternalTruncateLogRequest{}, &proto.InternalLeaderLeaseRequest{}, &proto.InternalBatchRequest{}, } var readOnlyRequests []proto.Request var writeOnlyRequests []proto.Request var readWriteRequests []proto.Request for _, r := range allRequestTypes { if proto.IsRead(r) && !proto.IsWrite(r) { readOnlyRequests = append(readOnlyRequests, r) } if proto.IsWrite(r) && !proto.IsRead(r) { writeOnlyRequests = append(writeOnlyRequests, r) } if proto.IsRead(r) && proto.IsWrite(r) { readWriteRequests = append(readWriteRequests, r) } } testData := []struct { // Permission-based db methods from the storage package. requests []proto.Request user string startKey, endKey proto.Key hasPermission bool }{ // Test permissions within a single range {readOnlyRequests, "read1", proto.KeyMin, proto.KeyMin, true}, {readOnlyRequests, "rw1", proto.KeyMin, proto.KeyMin, true}, {readOnlyRequests, "write1", proto.KeyMin, proto.KeyMin, false}, {readOnlyRequests, "random", proto.KeyMin, proto.KeyMin, false}, {readWriteRequests, "rw1", proto.KeyMin, proto.KeyMin, true}, {readWriteRequests, "read1", proto.KeyMin, proto.KeyMin, false}, {readWriteRequests, "write1", proto.KeyMin, proto.KeyMin, false}, {writeOnlyRequests, "write1", proto.KeyMin, proto.KeyMin, true}, {writeOnlyRequests, "rw1", proto.KeyMin, proto.KeyMin, true}, {writeOnlyRequests, "read1", proto.KeyMin, proto.KeyMin, false}, {writeOnlyRequests, "random", proto.KeyMin, proto.KeyMin, false}, // Test permissions hierarchically. {readOnlyRequests, "read1", proto.Key("a"), proto.Key("a1"), true}, {readWriteRequests, "rw1", proto.Key("a"), proto.Key("a1"), true}, {writeOnlyRequests, "write1", proto.Key("a"), proto.Key("a1"), true}, // Test permissions across both ranges. {readOnlyRequests, "readAll", proto.KeyMin, proto.Key("b"), true}, {readOnlyRequests, "read1", proto.KeyMin, proto.Key("b"), true}, {readOnlyRequests, "read2", proto.KeyMin, proto.Key("b"), false}, {readOnlyRequests, "random", proto.KeyMin, proto.Key("b"), false}, {readWriteRequests, "rwAll", proto.KeyMin, proto.Key("b"), true}, {readWriteRequests, "rw1", proto.KeyMin, proto.Key("b"), true}, {readWriteRequests, "random", proto.KeyMin, proto.Key("b"), false}, {writeOnlyRequests, "writeAll", proto.KeyMin, proto.Key("b"), true}, {writeOnlyRequests, "write1", proto.KeyMin, proto.Key("b"), true}, {writeOnlyRequests, "write2", proto.KeyMin, proto.Key("b"), false}, {writeOnlyRequests, "random", proto.KeyMin, proto.Key("b"), false}, // Test permissions within and around the boundaries of a range, // representatively using rw methods. {readWriteRequests, "rw2", proto.Key("a"), proto.Key("b"), true}, {readWriteRequests, "rwAll", proto.Key("a"), proto.Key("b"), true}, {readWriteRequests, "rw2", proto.Key("a"), proto.Key("a"), true}, {readWriteRequests, "rw2", proto.Key("a"), proto.Key("a1"), true}, {readWriteRequests, "rw2", proto.Key("a"), proto.Key("b1"), false}, {readWriteRequests, "rw2", proto.Key("a3"), proto.Key("a4"), true}, {readWriteRequests, "rw2", proto.Key("a3"), proto.Key("b1"), false}, } for i, test := range testData { for _, r := range test.requests { *r.Header() = proto.RequestHeader{ User: test.user, Key: test.startKey, EndKey: test.endKey, } err := ds.verifyPermissions(r) if err != nil && test.hasPermission { t.Errorf("test %d: user %s should have had permission to %s, err: %s", i, test.user, r.Method(), err.Error()) break } else if err == nil && !test.hasPermission { t.Errorf("test %d: user %s should not have had permission to %s", i, test.user, r.Method()) break } } } n.Stop() }
// TestGossipStorage verifies that a gossip node can join the cluster // using the bootstrap hosts in a gossip.Storage object. func TestGossipStorage(t *testing.T) { defer leaktest.AfterTest(t) const numNodes = 3 network := simulation.NewNetwork(3) defer network.Stop() // Set storage for each of the nodes. stores := []*testStorage{} for _, n := range network.Nodes { tp := &testStorage{} stores = append(stores, tp) if err := n.Gossip.SetStorage(tp); err != nil { t.Fatal(err) } } // Wait for the gossip network to connect. network.RunUntilFullyConnected() for i, p := range stores { if !p.read { t.Errorf("%d: expected read from storage", i) } if !p.write { t.Errorf("%d: expected write from storage", i) } // Verify all gossip addresses are written to each persistent store. if len(p.info.Addresses) != 3 { t.Errorf("%d: expected 3 addresses, have: %s", i, p.info.Addresses) } } // Create an unaffiliated gossip node with only itself as a resolver, // leaving it no way to reach the gossip network. node, err := network.CreateNode() if err != nil { t.Fatal(err) } node.Gossip.SetBootstrapInterval(1 * time.Millisecond) r, err := resolver.NewResolverFromAddress(node.Addr) if err != nil { t.Fatal(err) } node.Gossip.SetResolvers([]resolver.Resolver{r}) if err := network.StartNode(node); err != nil { t.Fatal(err) } // Wait for a bit to ensure no connection. select { case <-time.After(10 * time.Millisecond): // expected outcome... case <-node.Gossip.Connected: t.Fatal("unexpectedly connected to gossip") } // Give the new node storage with info established from a node // in the established network. tp := &testStorage{ info: stores[0].info, } if err := node.Gossip.SetStorage(tp); err != nil { t.Fatal(err) } network.SimulateNetwork(func(cycle int, network *simulation.Network) bool { if cycle > 100 { t.Fatal("failed to connect to gossip") } select { case <-node.Gossip.Connected: return false default: return true } }) }
// TestVerifyPermissions verifies permissions are checked for single // zones and across multiple zones. It also verifies that permissions // are checked hierarchically. func TestVerifyPermissions(t *testing.T) { n := simulation.NewNetwork(1, "unix", gossip.TestInterval, gossip.TestBootstrap) ds := NewDistSender(n.Nodes[0].Gossip) config1 := &proto.PermConfig{ Read: []string{"read1", "readAll", "rw1", "rwAll"}, Write: []string{"write1", "writeAll", "rw1", "rwAll"}} config2 := &proto.PermConfig{ Read: []string{"read2", "readAll", "rw2", "rwAll"}, Write: []string{"write2", "writeAll", "rw2", "rwAll"}} configs := []*storage.PrefixConfig{ {engine.KeyMin, nil, config1}, {proto.Key("a"), nil, config2}, } configMap, err := storage.NewPrefixConfigMap(configs) if err != nil { t.Fatalf("failed to make prefix config map, err: %s", err.Error()) } ds.gossip.AddInfo(gossip.KeyConfigPermission, configMap, time.Hour) readOnlyMethods := make([]string, 0, len(proto.ReadMethods)) writeOnlyMethods := make([]string, 0, len(proto.WriteMethods)) readWriteMethods := make([]string, 0, len(proto.ReadMethods)+len(proto.WriteMethods)) for readM := range proto.ReadMethods { if proto.IsReadOnly(readM) { readOnlyMethods = append(readOnlyMethods, readM) } else { readWriteMethods = append(readWriteMethods, readM) } } for writeM := range proto.WriteMethods { if !proto.NeedReadPerm(writeM) { writeOnlyMethods = append(writeOnlyMethods, writeM) } } testData := []struct { // Permission-based db methods from the storage package. methods []string user string startKey, endKey proto.Key hasPermission bool }{ // Test permissions within a single range {readOnlyMethods, "read1", engine.KeyMin, engine.KeyMin, true}, {readOnlyMethods, "rw1", engine.KeyMin, engine.KeyMin, true}, {readOnlyMethods, "write1", engine.KeyMin, engine.KeyMin, false}, {readOnlyMethods, "random", engine.KeyMin, engine.KeyMin, false}, {readWriteMethods, "rw1", engine.KeyMin, engine.KeyMin, true}, {readWriteMethods, "read1", engine.KeyMin, engine.KeyMin, false}, {readWriteMethods, "write1", engine.KeyMin, engine.KeyMin, false}, {writeOnlyMethods, "write1", engine.KeyMin, engine.KeyMin, true}, {writeOnlyMethods, "rw1", engine.KeyMin, engine.KeyMin, true}, {writeOnlyMethods, "read1", engine.KeyMin, engine.KeyMin, false}, {writeOnlyMethods, "random", engine.KeyMin, engine.KeyMin, false}, // Test permissions hierarchically. {readOnlyMethods, "read1", proto.Key("a"), proto.Key("a1"), true}, {readWriteMethods, "rw1", proto.Key("a"), proto.Key("a1"), true}, {writeOnlyMethods, "write1", proto.Key("a"), proto.Key("a1"), true}, // Test permissions across both ranges. {readOnlyMethods, "readAll", engine.KeyMin, proto.Key("b"), true}, {readOnlyMethods, "read1", engine.KeyMin, proto.Key("b"), true}, {readOnlyMethods, "read2", engine.KeyMin, proto.Key("b"), false}, {readOnlyMethods, "random", engine.KeyMin, proto.Key("b"), false}, {readWriteMethods, "rwAll", engine.KeyMin, proto.Key("b"), true}, {readWriteMethods, "rw1", engine.KeyMin, proto.Key("b"), true}, {readWriteMethods, "random", engine.KeyMin, proto.Key("b"), false}, {writeOnlyMethods, "writeAll", engine.KeyMin, proto.Key("b"), true}, {writeOnlyMethods, "write1", engine.KeyMin, proto.Key("b"), true}, {writeOnlyMethods, "write2", engine.KeyMin, proto.Key("b"), false}, {writeOnlyMethods, "random", engine.KeyMin, proto.Key("b"), false}, // Test permissions within and around the boundaries of a range, // representatively using rw methods. {readWriteMethods, "rw2", proto.Key("a"), proto.Key("b"), true}, {readWriteMethods, "rwAll", proto.Key("a"), proto.Key("b"), true}, {readWriteMethods, "rw2", proto.Key("a"), proto.Key("a"), true}, {readWriteMethods, "rw2", proto.Key("a"), proto.Key("a1"), true}, {readWriteMethods, "rw2", proto.Key("a"), proto.Key("b1"), false}, {readWriteMethods, "rw2", proto.Key("a3"), proto.Key("a4"), true}, {readWriteMethods, "rw2", proto.Key("a3"), proto.Key("b1"), false}, } for i, test := range testData { for _, method := range test.methods { err := ds.verifyPermissions( method, &proto.RequestHeader{ User: test.user, Key: test.startKey, EndKey: test.endKey}) if err != nil && test.hasPermission { t.Errorf("test %d: user %s should have had permission to %s, err: %s", i, test.user, method, err.Error()) break } else if err == nil && !test.hasPermission { t.Errorf("test %d: user %s should not have had permission to %s", i, test.user, method) break } } } n.Stop() }