Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@ func (h *HTTPGateway) getTrace(w http.ResponseWriter, r *http.Request) {
}

func (h *HTTPGateway) findTraces(w http.ResponseWriter, r *http.Request) {
queryParams, shouldReturn := h.parseFindTracesQuery(r.URL.Query(), w)
if shouldReturn {
queryParams, err := parseFindTracesQuery(r.URL.Query())
if h.tryHandleError(w, err, http.StatusBadRequest) {
return
}

Expand All @@ -190,8 +190,8 @@ func (h *HTTPGateway) findTraces(w http.ResponseWriter, r *http.Request) {
}

func (h *HTTPGateway) findTraceSummaries(w http.ResponseWriter, r *http.Request) {
queryParams, shouldReturn := h.parseFindTracesQuery(r.URL.Query(), w)
if shouldReturn {
queryParams, err := parseFindTracesQuery(r.URL.Query())
if h.tryHandleError(w, err, http.StatusBadRequest) {
return
}
// Summaries always use adjusted, aggregated data; raw_traces has no effect here.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -353,120 +353,18 @@ func mockFindQueries() (url.Values, tracestore.TraceQueryParams) {
}

func TestHTTPGatewayFindTracesErrors(t *testing.T) {
goodTimeV := time.Now()
goodTime := goodTimeV.Format(time.RFC3339Nano)
goodDuration := "1s"
timeRangeErr := "query.startTimeMin and query.startTimeMax are required"
testCases := []struct {
name string
params map[string]string
expErr string
}{
{
name: "no time range",
expErr: timeRangeErr,
},
{
name: "no max time",
params: map[string]string{"query.startTimeMin": goodTime},
expErr: timeRangeErr,
},
{
name: "no min time",
params: map[string]string{"query.startTimeMax": goodTime},
expErr: timeRangeErr,
},
{
name: "bad startTimeMin (canonical)",
params: map[string]string{"query.startTimeMin": "NaN", "query.startTimeMax": goodTime},
expErr: "query.startTimeMin",
},
{
name: "bad start_time_min (deprecated)",
params: map[string]string{"query.start_time_min": "NaN", "query.start_time_max": goodTime},
expErr: "query.start_time_min",
},
{
name: "bad startTimeMax (canonical)",
params: map[string]string{"query.startTimeMin": goodTime, "query.startTimeMax": "NaN"},
expErr: "query.startTimeMax",
},
{
name: "bad start_time_max (deprecated)",
params: map[string]string{"query.start_time_min": goodTime, "query.start_time_max": "NaN"},
expErr: "query.start_time_max",
},
{
name: "bad searchDepth (canonical)",
params: map[string]string{"query.startTimeMin": goodTime, "query.startTimeMax": goodTime, "query.searchDepth": "NaN"},
expErr: "query.searchDepth",
},
{
name: "bad search_depth (deprecated)",
params: map[string]string{"query.startTimeMin": goodTime, "query.startTimeMax": goodTime, "query.search_depth": "NaN"},
expErr: "query.search_depth",
},
{
name: "bad num_traces (deprecated alias)",
params: map[string]string{"query.startTimeMin": goodTime, "query.startTimeMax": goodTime, "query.num_traces": "NaN"},
expErr: "query.num_traces",
},
{
name: "bad durationMin (canonical)",
params: map[string]string{"query.startTimeMin": goodTime, "query.startTimeMax": goodTime, "query.durationMin": "NaN"},
expErr: "query.durationMin",
},
{
name: "bad duration_min (deprecated)",
params: map[string]string{"query.startTimeMin": goodTime, "query.startTimeMax": goodTime, "query.duration_min": "NaN"},
expErr: "query.duration_min",
},
{
name: "bad durationMax (canonical)",
params: map[string]string{"query.startTimeMin": goodTime, "query.startTimeMax": goodTime, "query.durationMax": "NaN"},
expErr: "query.durationMax",
},
{
name: "bad duration_max (deprecated)",
params: map[string]string{"query.startTimeMin": goodTime, "query.startTimeMax": goodTime, "query.duration_max": "NaN"},
expErr: "query.duration_max",
},
{
name: "bad rawTraces (canonical)",
params: map[string]string{
"query.startTimeMin": goodTime,
"query.startTimeMax": goodTime,
"query.durationMax": goodDuration,
"query.rawTraces": "foobar",
},
expErr: "query.rawTraces",
},
{
name: "bad raw_traces (deprecated)",
params: map[string]string{
"query.startTimeMin": goodTime,
"query.startTimeMax": goodTime,
"query.duration_max": goodDuration,
"query.raw_traces": "foobar",
},
expErr: "query.raw_traces",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
q := url.Values{}
for k, v := range tc.params {
q.Set(k, v)
}
r, err := http.NewRequest(http.MethodGet, "/api/v3/traces?"+q.Encode(), http.NoBody)
require.NoError(t, err)
w := httptest.NewRecorder()
t.Run("parse error returns 400", func(t *testing.T) {
// Detailed parse error cases are covered by TestParseFindTracesQuery.
// Here we only verify that any parse error is propagated as HTTP 400.
r, err := http.NewRequest(http.MethodGet, "/api/v3/traces", http.NoBody)
require.NoError(t, err)
w := httptest.NewRecorder()

gw := setupHTTPGatewayNoServer(t, "")
gw.router.ServeHTTP(w, r)
assert.Contains(t, w.Body.String(), tc.expErr)
})
}
gw := setupHTTPGatewayNoServer(t, "")
gw.router.ServeHTTP(w, r)
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, w.Body.String(), "query.startTimeMin and query.startTimeMax are required")
})
t.Run("span reader error", func(t *testing.T) {
q, qp := mockFindQueries()
r, err := http.NewRequest(http.MethodGet, "/api/v3/traces?"+q.Encode(), http.NoBody)
Expand All @@ -485,6 +383,41 @@ func TestHTTPGatewayFindTracesErrors(t *testing.T) {
})
}

func TestHTTPGatewayFindTracesAttributes(t *testing.T) {
time1 := time.Now().UTC().Truncate(time.Nanosecond)
time2 := time1.Add(-time.Second).UTC().Truncate(time.Nanosecond)

q := url.Values{}
q.Set(paramServiceName, "svc")
q.Set(paramTimeMin, time1.Format(time.RFC3339Nano))
q.Set(paramTimeMax, time2.Format(time.RFC3339Nano))
Comment thread
yurishkuro marked this conversation as resolved.
Outdated
q.Set(paramAttributes, `{"http.status_code":"200","error":"true"}`)

gw := setupHTTPGatewayNoServer(t, "")
gw.reader.
On("FindTraces", matchContext, mock.MatchedBy(func(qp tracestore.TraceQueryParams) bool {
v1, ok1 := qp.Attributes.Get("http.status_code")
v2, ok2 := qp.Attributes.Get("error")
return qp.ServiceName == "svc" &&
qp.StartTimeMin.Equal(time1) &&
qp.StartTimeMax.Equal(time2) &&
Comment thread
yurishkuro marked this conversation as resolved.
Outdated
qp.SearchDepth == defaultSearchDepth &&
qp.Attributes.Len() == 2 &&
ok1 && v1.AsString() == "200" &&
ok2 && v2.AsString() == "true"
})).
Return(iter.Seq2[[]ptrace.Traces, error](func(yield func([]ptrace.Traces, error) bool) {
yield([]ptrace.Traces{makeTestTrace()}, nil)
})).Once()

r, err := http.NewRequest(http.MethodGet, "/api/v3/traces?"+q.Encode(), http.NoBody)
require.NoError(t, err)
w := httptest.NewRecorder()
gw.router.ServeHTTP(w, r)
assert.Equal(t, http.StatusOK, w.Code)
gw.reader.AssertExpectations(t)
}

func TestHTTPGatewayGetServicesErrors(t *testing.T) {
gw := setupHTTPGatewayNoServer(t, "")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
package apiv3

import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"time"

"go.opentelemetry.io/collector/pdata/pcommon"

"github.com/jaegertracing/jaeger/cmd/jaeger/internal/extension/jaegerquery/querysvc"
"github.com/jaegertracing/jaeger/internal/jptrace"
"github.com/jaegertracing/jaeger/internal/storage/v2/api/tracestore"
)

Expand All @@ -33,6 +34,7 @@ const (
paramDurationMin = "query.durationMin"
paramDurationMax = "query.durationMax"
paramQueryRawTraces = "query.rawTraces"
paramAttributes = "query.attributes"
paramSpanKind = "spanKind"

// Deprecated snake_case aliases kept for backward compatibility.
Expand Down Expand Up @@ -60,31 +62,37 @@ func getQueryParam(q url.Values, canonical, deprecated string) (value string, pa
return q.Get(deprecated), deprecated
}

func (h *HTTPGateway) parseFindTracesQuery(q url.Values, w http.ResponseWriter) (*querysvc.TraceQueryParams, bool) {
func parseFindTracesQuery(q url.Values) (*querysvc.TraceQueryParams, error) {
serviceName, _ := getQueryParam(q, paramServiceName, paramServiceNameDeprecated)
operationName, _ := getQueryParam(q, paramOperationName, paramOperationNameDeprecated)

queryParams := &querysvc.TraceQueryParams{
TraceQueryParams: tracestore.TraceQueryParams{
ServiceName: serviceName,
OperationName: operationName,
Attributes: pcommon.NewMap(), // most curiously not supported by grpc-gateway
Attributes: pcommon.NewMap(),
},
}
if attrsParam := q.Get(paramAttributes); attrsParam != "" {
var attrsMap map[string]string
if err := json.Unmarshal([]byte(attrsParam), &attrsMap); err != nil {
return nil, fmt.Errorf("malformed parameter %s: %w", paramAttributes, err)
}
queryParams.Attributes = jptrace.PlainMapToPcommonMap(attrsMap)
}

timeMinStr, timeMinParam := getQueryParam(q, paramTimeMin, paramTimeMinDeprecated)
timeMaxStr, timeMaxParam := getQueryParam(q, paramTimeMax, paramTimeMaxDeprecated)
if timeMinStr == "" || timeMaxStr == "" {
err := fmt.Errorf("%s and %s are required", paramTimeMin, paramTimeMax)
h.tryHandleError(w, err, http.StatusBadRequest)
return nil, true
return nil, fmt.Errorf("%s and %s are required", paramTimeMin, paramTimeMax)
}
timeMinParsed, err := time.Parse(time.RFC3339Nano, timeMinStr)
if h.tryParamError(w, err, timeMinParam) {
return nil, true
if err != nil {
return nil, fmt.Errorf("malformed parameter %s: %w", timeMinParam, err)
}
timeMaxParsed, err := time.Parse(time.RFC3339Nano, timeMaxStr)
if h.tryParamError(w, err, timeMaxParam) {
return nil, true
if err != nil {
return nil, fmt.Errorf("malformed parameter %s: %w", timeMaxParam, err)
}
queryParams.StartTimeMin = timeMinParsed
queryParams.StartTimeMax = timeMaxParsed
Expand All @@ -96,8 +104,8 @@ func (h *HTTPGateway) parseFindTracesQuery(q url.Values, w http.ResponseWriter)
}
if n != "" {
searchDepth, err := strconv.Atoi(n)
if h.tryParamError(w, err, searchDepthParam) {
return nil, true
if err != nil {
return nil, fmt.Errorf("malformed parameter %s: %w", searchDepthParam, err)
}
queryParams.SearchDepth = searchDepth
} else {
Expand All @@ -106,24 +114,24 @@ func (h *HTTPGateway) parseFindTracesQuery(q url.Values, w http.ResponseWriter)

if d, paramName := getQueryParam(q, paramDurationMin, paramDurationMinDeprecated); d != "" {
dur, err := time.ParseDuration(d)
if h.tryParamError(w, err, paramName) {
return nil, true
if err != nil {
return nil, fmt.Errorf("malformed parameter %s: %w", paramName, err)
}
queryParams.DurationMin = dur
}
if d, paramName := getQueryParam(q, paramDurationMax, paramDurationMaxDeprecated); d != "" {
dur, err := time.ParseDuration(d)
if h.tryParamError(w, err, paramName) {
return nil, true
if err != nil {
return nil, fmt.Errorf("malformed parameter %s: %w", paramName, err)
}
queryParams.DurationMax = dur
}
if r, paramName := getQueryParam(q, paramQueryRawTraces, paramQueryRawTracesDeprecated); r != "" {
rawTraces, err := strconv.ParseBool(r)
if h.tryParamError(w, err, paramName) {
return nil, true
if err != nil {
return nil, fmt.Errorf("malformed parameter %s: %w", paramName, err)
}
queryParams.RawTraces = rawTraces
}
return queryParams, false
return queryParams, nil
}
Loading
Loading