// ServeHTTP serves the key-value API by treating the request URL path // as the method, the request body as the arguments, and sets the // response body as the method reply. The request body is unmarshalled // into arguments based on the Content-Type request header. Protobuf // and JSON-encoded requests are supported. The response body is // encoded according the the request's Accept header, or if not // present, in the same format as the request's incoming Content-Type // header. func (s *DBServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { method := r.URL.Path if !strings.HasPrefix(method, DBPrefix) { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } method = strings.TrimPrefix(method, DBPrefix) if !proto.IsPublic(method) { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } // Unmarshal the request. reqBody, err := ioutil.ReadAll(r.Body) defer r.Body.Close() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } args, reply, err := proto.CreateArgsAndReply(method) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if err := util.UnmarshalRequest(r, reqBody, args, allowedEncodings); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Verify the request for public API. if err := verifyRequest(args); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Create a call and invoke through sender. call := &client.Call{ Method: method, Args: args, Reply: reply, } s.sender.Send(call) // Marshal the response. body, contentType, err := util.MarshalResponse(r, reply, allowedEncodings) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", contentType) w.Write(body) }
// TestKVClientRetryNonTxn verifies that non-transactional client will // succeed despite write/write and read/write conflicts. In the case // where the non-transactional put can push the txn, we expect the // transaction's value to be written after all retries are complete. func TestKVClientRetryNonTxn(t *testing.T) { s := StartTestServer(t) defer s.Stop() s.SetRangeRetryOptions(util.RetryOptions{ Backoff: 1 * time.Millisecond, MaxBackoff: 5 * time.Millisecond, Constant: 2, MaxAttempts: 2, }) kvClient := createTestClient(s.HTTPAddr) kvClient.User = storage.UserRoot testCases := []struct { method string isolation proto.IsolationType canPush bool expAttempts int }{ // Write/write conflicts. {proto.Put, proto.SNAPSHOT, true, 2}, {proto.Put, proto.SERIALIZABLE, true, 2}, {proto.Put, proto.SNAPSHOT, false, 1}, {proto.Put, proto.SERIALIZABLE, false, 1}, // Read/write conflicts. {proto.Get, proto.SNAPSHOT, true, 1}, {proto.Get, proto.SERIALIZABLE, true, 2}, {proto.Get, proto.SNAPSHOT, false, 1}, {proto.Get, proto.SERIALIZABLE, false, 1}, } // Lay down a write intent using a txn and attempt to write to same // key. Try this twice--once with priorities which will allow the // intent to be pushed and once with priorities which will not. for i, test := range testCases { log.Infof("starting test case %d", i) key := proto.Key(fmt.Sprintf("key-%d", i)) txnPri := int32(-1) clientPri := int32(-1) if test.canPush { clientPri = -2 } else { txnPri = -2 } kvClient.UserPriority = clientPri // doneCall signals when the non-txn read or write has completed. doneCall := make(chan struct{}) count := 0 // keeps track of retries if err := kvClient.RunTransaction(&client.TransactionOptions{Isolation: test.isolation}, func(txn *client.KV) error { txn.UserPriority = txnPri count++ // Lay down the intent. if err := txn.Call(proto.Put, proto.PutArgs(key, []byte("txn-value")), &proto.PutResponse{}); err != nil { return err } // The wait group lets us pause txn until after the non-txn method has run once. wg := sync.WaitGroup{} // On the first true, send the non-txn put or get. if count == 1 { // We use a "notifying" sender here, which allows us to know exactly when the // call has been processed; otherwise, we'd be dependent on timing. kvClient.Sender().(*notifyingSender).reset(&wg) // We must try the non-txn put or get in a goroutine because // it might have to retry and will only succeed immediately in // the event we can push. go func() { args, reply, err := proto.CreateArgsAndReply(test.method) if err != nil { t.Errorf("error creating args and reply for method %s: %s", test.method, err) } args.Header().Key = key if test.method == proto.Put { args.(*proto.PutRequest).Value.Bytes = []byte("value") } for i := 0; ; i++ { err = kvClient.Call(test.method, args, reply) if _, ok := err.(*proto.WriteIntentError); !ok { break } } close(doneCall) if err != nil { t.Fatalf("%d: expected success on non-txn call to %s; got %s", i, err, test.method) } }() kvClient.Sender().(*notifyingSender).wait() } return nil }); err != nil { t.Fatalf("%d: expected success writing transactionally; got %s", i, err) } // Make sure non-txn put or get has finished. <-doneCall // Get the current value to verify whether the txn happened first. getReply := &proto.GetResponse{} if err := kvClient.Call(proto.Get, proto.GetArgs(key), getReply); err != nil { t.Fatalf("%d: expected success getting %q: %s", i, key, err) } if test.canPush || test.method == proto.Get { if !bytes.Equal(getReply.Value.Bytes, []byte("txn-value")) { t.Errorf("%d: expected \"txn-value\"; got %q", i, getReply.Value.Bytes) } } else { if !bytes.Equal(getReply.Value.Bytes, []byte("value")) { t.Errorf("%d: expected \"value\"; got %q", i, getReply.Value.Bytes) } } if count != test.expAttempts { t.Errorf("%d: expected %d attempt(s); got %d", i, test.expAttempts, count) } } }