From b0659ec8591f229dc23c6369c5378473e94e7293 Mon Sep 17 00:00:00 2001 From: Aaditya Sondhi <20070511+aadityasondhi@users.noreply.github.com> Date: Thu, 23 Mar 2023 10:28:22 -0400 Subject: [PATCH] roachtest: make `follower-reads` test use protobuf encoded requests Previously, the roachtest for `follower-reads/mixed-version/single-region` used JSON encoding for the `ts/query` endpoint. This caused the client to send the newly updated `timerseries` proto to an older cluster in JSON format. Although this was defined as an `optional` field in the proto, since it was encoded in JSON, that information was lost over the wire. In other places, such as DBConsole, we expect the client to be sending protobuf encoded messages and not JSON. In the same way, roachtests (another client) should do the same. When this message is sent as a protobuf, the `optional` tag of the field is encoded in the message and the server is able to process is it as such. There are related links in the issue: https://github.com/cockroachdb/cockroach/issues/99117. Fixes #99117. Release note: None --- pkg/cmd/roachtest/tests/follower_reads.go | 2 +- pkg/util/httputil/http.go | 37 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/roachtest/tests/follower_reads.go b/pkg/cmd/roachtest/tests/follower_reads.go index 712d68f2e141..50680ad89128 100644 --- a/pkg/cmd/roachtest/tests/follower_reads.go +++ b/pkg/cmd/roachtest/tests/follower_reads.go @@ -730,7 +730,7 @@ func verifyHighFollowerReadRatios( } var response tspb.TimeSeriesQueryResponse - if err := httputil.PostJSON(http.Client{}, url, &request, &response); err != nil { + if err := httputil.PostProtobuf(ctx, http.Client{}, url, &request, &response); err != nil { t.Fatal(err) } diff --git a/pkg/util/httputil/http.go b/pkg/util/httputil/http.go index 5b3edfd6b813..cddc99314ee3 100644 --- a/pkg/util/httputil/http.go +++ b/pkg/util/httputil/http.go @@ -12,6 +12,7 @@ package httputil import ( "bytes" + "context" "io" "net/http" "strconv" @@ -110,6 +111,42 @@ func PostJSONWithRequest( return doJSONRequest(httpClient, req, response) } +// PostProtobuf uses the supplied client to POST request to the URL specified by +// the parameters and unmarshal the result into response, using a +// protobuf-encoded request body. +func PostProtobuf( + ctx context.Context, httpClient http.Client, path string, request, response protoutil.Message, +) error { + buf, err := protoutil.Marshal(request) + if err != nil { + return err + } + reader := bytes.NewReader(buf) + req, err := http.NewRequestWithContext(ctx, "POST", path, reader) + if err != nil { + return err + } + if timeout := httpClient.Timeout; timeout > 0 { + req.Header.Set("Grpc-Timeout", strconv.FormatInt(timeout.Nanoseconds(), 10)+"n") + } + req.Header.Set(AcceptHeader, ProtoContentType) + req.Header.Set(ContentTypeHeader, ProtoContentType) + resp, err := httpClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + b, err := io.ReadAll(resp.Body) + if contentType := resp.Header.Get(ContentTypeHeader); !(resp.StatusCode == http.StatusOK && contentType == ProtoContentType) { + // NB: errors.Wrapf(nil, ...) returns nil. + // nolint:errwrap + return errors.Errorf( + "status: %s, content-type: %s, body: %s, error: %v", resp.Status, contentType, b, err, + ) + } + return protoutil.Unmarshal(b, response) +} + func doJSONRequest( httpClient http.Client, req *http.Request, response protoutil.Message, ) (*http.Response, error) {