// TestRetryOnDescriptorLookupError verifies that the DistSender retries a descriptor // lookup on retryable errors. func TestRetryOnDescriptorLookupError(t *testing.T) { defer leaktest.AfterTest(t) g, s := makeTestGossip(t) defer s() var testFn rpcSendFn = func(_ rpc.Options, _ string, _ []net.Addr, _ func(addr net.Addr) gogoproto.Message, getReply func() gogoproto.Message, _ *rpc.Context) ([]gogoproto.Message, error) { return []gogoproto.Message{getReply()}, nil } errors := []error{ &proto.Error{ Message: "fatal boom", Retryable: false, }, &proto.Error{ Message: "temporary boom", Retryable: true, }, nil, } ctx := &DistSenderContext{ RPCSend: testFn, RangeDescriptorDB: mockRangeDescriptorDB(func(k proto.Key, _ lookupOptions) ([]proto.RangeDescriptor, error) { // Return next error and truncate the prefix of the errors array. var err error if k != nil { err = errors[0] errors = errors[1:] } return []proto.RangeDescriptor{testRangeDescriptor}, err }), } ds := NewDistSender(ctx, g) call := proto.PutCall(proto.Key("a"), proto.Value{Bytes: []byte("value")}) reply := call.Reply.(*proto.PutResponse) // Fatal error on descriptor lookup, propagated to reply. client.SendCallConverted(ds, context.Background(), call) if err := reply.Header().Error; err.GetMessage() != "fatal boom" { t.Errorf("unexpected error: %s", err) } // Retryable error on descriptor lookup, second attempt successful. client.SendCallConverted(ds, context.Background(), call) if err := reply.GoError(); err != nil { t.Errorf("unexpected error: %s", err) } if len(errors) != 0 { t.Fatalf("expected more descriptor lookups, leftover errors: %+v", errors) } }
// TestRangeLookupOptionOnReverseScan verifies that a lookup triggered by a // ReverseScan request has the `useReverseScan` option specified. func TestRangeLookupOptionOnReverseScan(t *testing.T) { defer leaktest.AfterTest(t) g, s := makeTestGossip(t) defer s() var testFn rpcSendFn = func(_ rpc.Options, method string, addrs []net.Addr, getArgs func(addr net.Addr) gogoproto.Message, getReply func() gogoproto.Message, _ *rpc.Context) ([]gogoproto.Message, error) { return []gogoproto.Message{getReply()}, nil } ctx := &DistSenderContext{ RPCSend: testFn, RangeDescriptorDB: mockRangeDescriptorDB(func(k proto.Key, opts lookupOptions) ([]proto.RangeDescriptor, error) { if len(k) > 0 && !opts.useReverseScan { t.Fatalf("expected useReverseScan to be set") } return []proto.RangeDescriptor{testRangeDescriptor}, nil }), } ds := NewDistSender(ctx, g) call := proto.Call{ Args: &proto.ReverseScanRequest{ RequestHeader: proto.RequestHeader{EndKey: proto.Key("a")}, }, Reply: &proto.ReverseScanResponse{}, } client.SendCallConverted(ds, context.Background(), call) }
// TestSendRPCRetry verifies that sendRPC failed on first address but succeed on // second address, the second reply should be successfully returned back. func TestSendRPCRetry(t *testing.T) { defer leaktest.AfterTest(t) g, s := makeTestGossip(t) defer s() if err := g.SetNodeDescriptor(&proto.NodeDescriptor{NodeID: 1}); err != nil { t.Fatal(err) } // Fill RangeDescriptor with 2 replicas var descriptor = proto.RangeDescriptor{ RangeID: 1, StartKey: proto.Key("a"), EndKey: proto.Key("z"), } for i := 1; i <= 2; i++ { addr := util.MakeUnresolvedAddr("tcp", fmt.Sprintf("node%d", i)) nd := &proto.NodeDescriptor{ NodeID: proto.NodeID(i), Address: util.MakeUnresolvedAddr(addr.Network(), addr.String()), } if err := g.AddInfoProto(gossip.MakeNodeIDKey(proto.NodeID(i)), nd, time.Hour); err != nil { t.Fatal(err) } descriptor.Replicas = append(descriptor.Replicas, proto.Replica{ NodeID: proto.NodeID(i), StoreID: proto.StoreID(i), }) } // Define our rpcSend stub which returns success on the second address. var testFn rpcSendFn = func(_ rpc.Options, method string, addrs []net.Addr, getArgs func(addr net.Addr) gogoproto.Message, getReply func() gogoproto.Message, _ *rpc.Context) ([]gogoproto.Message, error) { if method == "Node.Batch" { // reply from first address failed _ = getReply() // reply from second address succeed batchReply := getReply().(*proto.BatchResponse) reply := &proto.ScanResponse{} batchReply.Add(reply) reply.Rows = append([]proto.KeyValue{}, proto.KeyValue{Key: proto.Key("b"), Value: proto.Value{}}) return []gogoproto.Message{batchReply}, nil } return nil, util.Errorf("unexpected method %v", method) } ctx := &DistSenderContext{ RPCSend: testFn, RangeDescriptorDB: mockRangeDescriptorDB(func(_ proto.Key, _ lookupOptions) ([]proto.RangeDescriptor, error) { return []proto.RangeDescriptor{descriptor}, nil }), } ds := NewDistSender(ctx, g) call := proto.ScanCall(proto.Key("a"), proto.Key("d"), 1) sr := call.Reply.(*proto.ScanResponse) client.SendCallConverted(ds, context.Background(), call) if err := sr.GoError(); err != nil { t.Fatal(err) } if l := len(sr.Rows); l != 1 { t.Fatalf("expected 1 row; got %d", l) } }
func TestEvictCacheOnError(t *testing.T) { defer leaktest.AfterTest(t) // if rpcError is true, the first attempt gets an RPC error, otherwise // the RPC call succeeds but there is an error in the RequestHeader. // Currently leader and cached range descriptor are treated equally. testCases := []struct{ rpcError, retryable, shouldClearLeader, shouldClearReplica bool }{ {false, false, false, false}, // non-retryable replica error {false, true, false, false}, // retryable replica error {true, false, true, true}, // RPC error aka all nodes dead {true, true, false, false}, // retryable RPC error } for i, tc := range testCases { g, s := makeTestGossip(t) defer s() leader := proto.Replica{ NodeID: 99, StoreID: 999, } first := true var testFn rpcSendFn = func(_ rpc.Options, _ string, _ []net.Addr, _ func(addr net.Addr) gogoproto.Message, getReply func() gogoproto.Message, _ *rpc.Context) ([]gogoproto.Message, error) { if !first { return []gogoproto.Message{getReply()}, nil } first = false err := rpc.NewSendError("boom", tc.retryable) if tc.rpcError { return nil, err } reply := getReply() reply.(proto.Response).Header().SetGoError(err) return []gogoproto.Message{reply}, nil } ctx := &DistSenderContext{ RPCSend: testFn, RangeDescriptorDB: mockRangeDescriptorDB(func(_ proto.Key, _ lookupOptions) ([]proto.RangeDescriptor, error) { return []proto.RangeDescriptor{testRangeDescriptor}, nil }), } ds := NewDistSender(ctx, g) ds.updateLeaderCache(1, leader) call := proto.PutCall(proto.Key("a"), proto.Value{Bytes: []byte("value")}) reply := call.Reply.(*proto.PutResponse) client.SendCallConverted(ds, context.Background(), call) if err := reply.GoError(); err != nil && !testutils.IsError(err, "boom") { t.Errorf("put encountered unexpected error: %s", err) } if cur := ds.leaderCache.Lookup(1); reflect.DeepEqual(cur, &proto.Replica{}) && !tc.shouldClearLeader { t.Errorf("%d: leader cache eviction: shouldClearLeader=%t, but value is %v", i, tc.shouldClearLeader, cur) } _, cachedDesc := ds.rangeCache.getCachedRangeDescriptor(call.Args.Header().Key, false /* !inclusive */) if cachedDesc == nil != tc.shouldClearReplica { t.Errorf("%d: unexpected second replica lookup behaviour: wanted=%t", i, tc.shouldClearReplica) } } }
// TestRetryOnWrongReplicaError sets up a DistSender on a minimal gossip // network and a mock of rpc.Send, and verifies that the DistSender correctly // retries upon encountering a stale entry in its range descriptor cache. func TestRetryOnWrongReplicaError(t *testing.T) { defer leaktest.AfterTest(t) g, s := makeTestGossip(t) defer s() // Updated below, after it has first been returned. badStartKey := proto.Key("m") newRangeDescriptor := testRangeDescriptor goodStartKey := newRangeDescriptor.StartKey newRangeDescriptor.StartKey = badStartKey descStale := true var testFn rpcSendFn = func(_ rpc.Options, method string, addrs []net.Addr, getArgs func(addr net.Addr) gogoproto.Message, getReply func() gogoproto.Message, _ *rpc.Context) ([]gogoproto.Message, error) { ba := getArgs(testAddress).(*proto.BatchRequest) if _, ok := ba.GetArg(proto.RangeLookup); ok { if !descStale && bytes.HasPrefix(ba.Key, keys.Meta2Prefix) { t.Errorf("unexpected extra lookup for non-stale replica descriptor at %s", ba.Key) } br := getReply().(*proto.BatchResponse) r := &proto.RangeLookupResponse{} r.Ranges = append(r.Ranges, newRangeDescriptor) br.Add(r) // If we just returned the stale descriptor, set up returning the // good one next time. if bytes.HasPrefix(ba.Key, keys.Meta2Prefix) { if newRangeDescriptor.StartKey.Equal(badStartKey) { newRangeDescriptor.StartKey = goodStartKey } else { descStale = false } } return []gogoproto.Message{br}, nil } // When the Scan first turns up, update the descriptor for future // range descriptor lookups. if !newRangeDescriptor.StartKey.Equal(goodStartKey) { return nil, &proto.RangeKeyMismatchError{RequestStartKey: ba.Key, RequestEndKey: ba.EndKey} } return []gogoproto.Message{getReply()}, nil } ctx := &DistSenderContext{ RPCSend: testFn, } ds := NewDistSender(ctx, g) call := proto.ScanCall(proto.Key("a"), proto.Key("d"), 0) sr := call.Reply.(*proto.ScanResponse) client.SendCallConverted(ds, context.Background(), call) if err := sr.GoError(); err != nil { t.Errorf("scan encountered error: %s", err) } }
// TestRetryOnNotLeaderError verifies that the DistSender correctly updates the // leader cache and retries when receiving a NotLeaderError. func TestRetryOnNotLeaderError(t *testing.T) { defer leaktest.AfterTest(t) g, s := makeTestGossip(t) defer s() leader := proto.Replica{ NodeID: 99, StoreID: 999, } first := true var testFn rpcSendFn = func(_ rpc.Options, method string, addrs []net.Addr, getArgs func(addr net.Addr) gogoproto.Message, getReply func() gogoproto.Message, _ *rpc.Context) ([]gogoproto.Message, error) { if first { reply := getReply() reply.(proto.Response).Header().SetGoError( &proto.NotLeaderError{Leader: &leader, Replica: &proto.Replica{}}) first = false return []gogoproto.Message{reply}, nil } return []gogoproto.Message{getReply()}, nil } ctx := &DistSenderContext{ RPCSend: testFn, RangeDescriptorDB: mockRangeDescriptorDB(func(_ proto.Key, _ lookupOptions) ([]proto.RangeDescriptor, error) { return []proto.RangeDescriptor{testRangeDescriptor}, nil }), } ds := NewDistSender(ctx, g) call := proto.PutCall(proto.Key("a"), proto.Value{Bytes: []byte("value")}) reply := call.Reply.(*proto.PutResponse) client.SendCallConverted(ds, context.Background(), call) if err := reply.GoError(); err != nil { t.Errorf("put encountered error: %s", err) } if first { t.Errorf("The command did not retry") } if cur := ds.leaderCache.Lookup(1); cur.StoreID != leader.StoreID { t.Errorf("leader cache was not updated: expected %v, got %v", &leader, cur) } }
// TestMultiRangeScanReverseScanInconsistent verifies that a Scan/ReverseScan // across ranges that doesn't require read consistency will set a timestamp // using the clock local to the distributed sender. func TestMultiRangeScanReverseScanInconsistent(t *testing.T) { defer leaktest.AfterTest(t) s, db := setupMultipleRanges(t, "b") defer s.Stop() // Write keys "a" and "b", the latter of which is the first key in the // second range. keys := []string{"a", "b"} ts := []time.Time{} b := &client.Batch{} for _, key := range keys { b.Put(key, "value") } if err := db.Run(b); err != nil { t.Fatal(err) } for i := range keys { ts = append(ts, b.Results[i].Rows[0].Timestamp()) log.Infof("%d: %s", i, b.Results[i].Rows[0].Timestamp()) } // Do an inconsistent Scan/ReverseScan from a new DistSender and verify // it does the read at its local clock and doesn't receive an // OpRequiresTxnError. We set the local clock to the timestamp of // the first key to verify it's used to read only key "a". manual := hlc.NewManualClock(ts[1].UnixNano() - 1) clock := hlc.NewClock(manual.UnixNano) ds := kv.NewDistSender(&kv.DistSenderContext{Clock: clock}, s.Gossip()) // Scan. call := proto.ScanCall(proto.Key("a"), proto.Key("c"), 0) sr := call.Reply.(*proto.ScanResponse) sa := call.Args.(*proto.ScanRequest) sa.ReadConsistency = proto.INCONSISTENT client.SendCallConverted(ds, context.Background(), call) if err := sr.GoError(); err != nil { t.Fatal(err) } if l := len(sr.Rows); l != 1 { t.Fatalf("expected 1 row; got %d", l) } if key := string(sr.Rows[0].Key); keys[0] != key { t.Errorf("expected key %q; got %q", keys[0], key) } // ReverseScan. call = proto.ReverseScanCall(proto.Key("a"), proto.Key("c"), 0) rsr := call.Reply.(*proto.ReverseScanResponse) rsa := call.Args.(*proto.ReverseScanRequest) rsa.ReadConsistency = proto.INCONSISTENT client.SendCallConverted(ds, context.Background(), call) if err := rsr.GoError(); err != nil { t.Fatal(err) } if l := len(rsr.Rows); l != 1 { t.Fatalf("expected 1 row; got %d", l) } if key := string(rsr.Rows[0].Key); keys[0] != key { t.Errorf("expected key %q; got %q", keys[0], key) } }
// Send implements the client.Sender interface. If the call is part // of a transaction, the coordinator will initialize the transaction // if it's not nil but has an empty ID. // TODO(tschottdorf): remove in favor of SendBatch. func (tc *TxnCoordSender) Send(ctx context.Context, call proto.Call) { client.SendCallConverted(tc, ctx, call) }
// TestMultiRangeMergeStaleDescriptor simulates the situation in which the // DistSender executes a multi-range scan which encounters the stale descriptor // of a range which has since incorporated its right neighbor by means of a // merge. It is verified that the DistSender scans the correct keyrange exactly // once. func TestMultiRangeMergeStaleDescriptor(t *testing.T) { defer leaktest.AfterTest(t) g, s := makeTestGossip(t) defer s() // Assume we have two ranges, [a-b) and [b-KeyMax). merged := false // The stale first range descriptor which is unaware of the merge. var firstRange = proto.RangeDescriptor{ RangeID: 1, StartKey: proto.Key("a"), EndKey: proto.Key("b"), Replicas: []proto.Replica{ { NodeID: 1, StoreID: 1, }, }, } // The merged descriptor, which will be looked up after having processed // the stale range [a,b). var mergedRange = proto.RangeDescriptor{ RangeID: 1, StartKey: proto.Key("a"), EndKey: proto.KeyMax, Replicas: []proto.Replica{ { NodeID: 1, StoreID: 1, }, }, } // Assume we have two key-value pairs, a=1 and c=2. existingKVs := []proto.KeyValue{ {Key: proto.Key("a"), Value: proto.Value{Bytes: []byte("1")}}, {Key: proto.Key("c"), Value: proto.Value{Bytes: []byte("2")}}, } var testFn rpcSendFn = func(_ rpc.Options, method string, addrs []net.Addr, getArgs func(addr net.Addr) gogoproto.Message, getReply func() gogoproto.Message, _ *rpc.Context) ([]gogoproto.Message, error) { if method != "Node.Batch" { t.Fatalf("unexpected method:%s", method) } header := getArgs(testAddress).(proto.Request).Header() batchReply := getReply().(*proto.BatchResponse) reply := &proto.ScanResponse{} batchReply.Add(reply) results := []proto.KeyValue{} for _, curKV := range existingKVs { if header.Key.Less(curKV.Key.Next()) && curKV.Key.Less(header.EndKey) { results = append(results, curKV) } } reply.Rows = results return []gogoproto.Message{batchReply}, nil } ctx := &DistSenderContext{ RPCSend: testFn, RangeDescriptorDB: mockRangeDescriptorDB(func(key proto.Key, _ lookupOptions) ([]proto.RangeDescriptor, error) { if !merged { // Assume a range merge operation happened. merged = true return []proto.RangeDescriptor{firstRange}, nil } return []proto.RangeDescriptor{mergedRange}, nil }), } ds := NewDistSender(ctx, g) call := proto.ScanCall(proto.Key("a"), proto.Key("d"), 10) // Set the Txn info to avoid an OpRequiresTxnError. call.Args.Header().Txn = &proto.Transaction{} reply := call.Reply.(*proto.ScanResponse) client.SendCallConverted(ds, context.Background(), call) if err := reply.GoError(); err != nil { t.Fatalf("scan encountered error: %s", err) } if !reflect.DeepEqual(existingKVs, reply.Rows) { t.Fatalf("expect get %v, actual get %v", existingKVs, reply.Rows) } }
// TestSendRPCOrder verifies that sendRPC correctly takes into account the // leader, attributes and required consistency to determine where to send // remote requests. func TestSendRPCOrder(t *testing.T) { defer leaktest.AfterTest(t) g, s := makeTestGossip(t) defer s() rangeID := proto.RangeID(99) nodeAttrs := map[int32][]string{ 1: {}, // The local node, set in each test case. 2: {"us", "west", "gpu"}, 3: {"eu", "dublin", "pdu2", "gpu"}, 4: {"us", "east", "gpu"}, 5: {"us", "east", "gpu", "flaky"}, } // Gets filled below to identify the replica by its address. addrToNode := make(map[string]int32) makeVerifier := func(expOrder rpc.OrderingPolicy, expAddrs []int32) func(rpc.Options, []net.Addr) error { return func(o rpc.Options, addrs []net.Addr) error { if o.Ordering != expOrder { return util.Errorf("unexpected ordering, wanted %v, got %v", expOrder, o.Ordering) } var actualAddrs []int32 for i, a := range addrs { if len(expAddrs) <= i { return util.Errorf("got unexpected address: %s", a) } if expAddrs[i] == 0 { actualAddrs = append(actualAddrs, 0) } else { actualAddrs = append(actualAddrs, addrToNode[a.String()]) } } if !reflect.DeepEqual(expAddrs, actualAddrs) { return util.Errorf("expected %d, but found %d", expAddrs, actualAddrs) } return nil } } testCases := []struct { args proto.Request attrs []string order rpc.OrderingPolicy expReplica []int32 leader int32 // 0 for not caching a leader. // Naming is somewhat off, as eventually consistent reads usually // do not have to go to the leader when a node has a read lease. // Would really want CONSENSUS here, but that is not implemented. // Likely a test setup here will never have a read lease, but good // to keep in mind. consistent bool }{ // Inconsistent Scan without matching attributes. { args: &proto.ScanRequest{}, attrs: []string{}, order: rpc.OrderRandom, expReplica: []int32{1, 2, 3, 4, 5}, }, // Inconsistent Scan with matching attributes. // Should move the two nodes matching the attributes to the front and // go stable. { args: &proto.ScanRequest{}, attrs: nodeAttrs[5], order: rpc.OrderStable, // Compare only the first two resulting addresses. expReplica: []int32{5, 4, 0, 0, 0}, }, // Scan without matching attributes that requires but does not find // a leader. { args: &proto.ScanRequest{}, attrs: []string{}, order: rpc.OrderRandom, expReplica: []int32{1, 2, 3, 4, 5}, consistent: true, }, // Put without matching attributes that requires but does not find leader. // Should go random and not change anything. { args: &proto.PutRequest{}, attrs: []string{"nomatch"}, order: rpc.OrderRandom, expReplica: []int32{1, 2, 3, 4, 5}, }, // Put with matching attributes but no leader. // Should move the two nodes matching the attributes to the front and // go stable. { args: &proto.PutRequest{}, attrs: append(nodeAttrs[5], "irrelevant"), // Compare only the first two resulting addresses. order: rpc.OrderStable, expReplica: []int32{5, 4, 0, 0, 0}, }, // Put with matching attributes that finds the leader (node 3). // Should address the leader and the two nodes matching the attributes // (the last and second to last) in that order. { args: &proto.PutRequest{}, attrs: append(nodeAttrs[5], "irrelevant"), // Compare only the first resulting addresses as we have a leader // and that means we're only trying to send there. order: rpc.OrderStable, expReplica: []int32{2, 5, 4, 0, 0}, leader: 2, }, // Inconsistent Get without matching attributes but leader (node 3). Should just // go random as the leader does not matter. { args: &proto.GetRequest{}, attrs: []string{}, order: rpc.OrderRandom, expReplica: []int32{1, 2, 3, 4, 5}, leader: 2, }, } descriptor := proto.RangeDescriptor{ StartKey: proto.KeyMin, EndKey: proto.KeyMax, RangeID: rangeID, Replicas: nil, } // Stub to be changed in each test case. var verifyCall func(rpc.Options, []net.Addr) error var testFn rpcSendFn = func(opts rpc.Options, method string, addrs []net.Addr, _ func(addr net.Addr) gogoproto.Message, getReply func() gogoproto.Message, _ *rpc.Context) ([]gogoproto.Message, error) { if err := verifyCall(opts, addrs); err != nil { return nil, err } return []gogoproto.Message{getReply()}, nil } ctx := &DistSenderContext{ RPCSend: testFn, RangeDescriptorDB: mockRangeDescriptorDB(func(proto.Key, lookupOptions) ([]proto.RangeDescriptor, error) { return []proto.RangeDescriptor{descriptor}, nil }), } ds := NewDistSender(ctx, g) for n, tc := range testCases { verifyCall = makeVerifier(tc.order, tc.expReplica) descriptor.Replicas = nil // could do this once above, but more convenient here for i := int32(1); i <= 5; i++ { addr := util.MakeUnresolvedAddr("tcp", fmt.Sprintf("node%d", i)) addrToNode[addr.String()] = i nd := &proto.NodeDescriptor{ NodeID: proto.NodeID(i), Address: util.MakeUnresolvedAddr(addr.Network(), addr.String()), Attrs: proto.Attributes{ Attrs: nodeAttrs[i], }, } if err := g.AddInfoProto(gossip.MakeNodeIDKey(proto.NodeID(i)), nd, time.Hour); err != nil { t.Fatal(err) } descriptor.Replicas = append(descriptor.Replicas, proto.Replica{ NodeID: proto.NodeID(i), StoreID: proto.StoreID(i), }) } { // The local node needs to get its attributes during sendRPC. nd := &proto.NodeDescriptor{ NodeID: 6, Attrs: proto.Attributes{ Attrs: tc.attrs, }, } if err := g.SetNodeDescriptor(nd); err != nil { t.Fatal(err) } } ds.leaderCache.Update(proto.RangeID(rangeID), proto.Replica{}) if tc.leader > 0 { ds.leaderCache.Update(proto.RangeID(rangeID), descriptor.Replicas[tc.leader-1]) } args := tc.args args.Header().RangeID = rangeID // Not used in this test, but why not. args.Header().Key = proto.Key("a") if proto.IsRange(args) { args.Header().EndKey = proto.Key("b") } if !tc.consistent { args.Header().ReadConsistency = proto.INCONSISTENT } // Kill the cached NodeDescriptor, enforcing a lookup from Gossip. ds.nodeDescriptor = nil call := proto.Call{Args: args, Reply: args.CreateReply()} client.SendCallConverted(ds, context.Background(), call) if err := call.Reply.Header().GoError(); err != nil { t.Errorf("%d: %s", n, err) } } }
func (cds *callDistSender) Send(ctx context.Context, call proto.Call) { client.SendCallConverted((*kv.DistSender)(cds), ctx, call) }
// Send implements the client.Sender interface. The store is looked // up from the store map if specified by header.Replica; otherwise, // the command is being executed locally, and the replica is // determined via lookup through each store's LookupRange method. func (ls *LocalSender) Send(ctx context.Context, call proto.Call) { client.SendCallConverted(ls, ctx, call) return }