@ -1,12 +1,16 @@
package querier
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/go-kit/log"
"github.com/grafana/dskit/tenant"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
"github.com/weaveworks/common/user"
@ -34,3 +38,156 @@ func TestTailHandler(t *testing.T) {
require . Equal ( t , http . StatusBadRequest , rr . Code )
require . Equal ( t , "multiple org IDs present\n" , rr . Body . String ( ) )
}
type slowConnectionSimulator struct {
sleepFor time . Duration
deadline time . Duration
didTimeout bool
}
func ( s * slowConnectionSimulator ) ServeHTTP ( w http . ResponseWriter , r * http . Request ) {
ctx := r . Context ( )
if err := ctx . Err ( ) ; err != nil {
panic ( fmt . Sprintf ( "context already errored: %s" , err ) )
}
time . Sleep ( s . sleepFor )
select {
case <- ctx . Done ( ) :
switch ctx . Err ( ) {
case context . DeadlineExceeded :
s . didTimeout = true
case context . Canceled :
panic ( "context already canceled" )
}
case <- time . After ( s . deadline ) :
}
}
func TestQueryWrapperMiddleware ( t * testing . T ) {
tenant . WithDefaultResolver ( tenant . NewMultiResolver ( ) )
shortestTimeout := time . Millisecond * 5
t . Run ( "request timeout is the shortest one" , func ( t * testing . T ) {
defaultLimits := defaultLimitsTestConfig ( )
limits , err := validation . NewOverrides ( defaultLimits , nil )
require . NoError ( t , err )
api := NewQuerierAPI ( mockQuerierConfig ( ) , nil , limits , log . NewNopLogger ( ) )
// request timeout is 5ms but it sleeps for 100ms, so timeout injected in the request is expected.
connSimulator := & slowConnectionSimulator {
sleepFor : time . Millisecond * 100 ,
deadline : shortestTimeout ,
}
api . cfg . QueryTimeout = time . Millisecond * 10
midl := WrapQuerySpanAndTimeout ( "mycall" , api ) . Wrap ( connSimulator )
req , err := http . NewRequest ( "GET" , "/loki/api/v1/label" , nil )
ctx , cancelFunc := context . WithTimeout ( user . InjectOrgID ( req . Context ( ) , "fake" ) , shortestTimeout )
defer cancelFunc ( )
req = req . WithContext ( ctx )
require . NoError ( t , err )
rr := httptest . NewRecorder ( )
srv := http . HandlerFunc ( midl . ServeHTTP )
srv . ServeHTTP ( rr , req )
require . Equal ( t , http . StatusOK , rr . Code )
select {
case <- ctx . Done ( ) :
break
case <- time . After ( shortestTimeout ) :
require . FailNow ( t , "should have timed out before %s" , shortestTimeout )
default :
require . FailNow ( t , "timeout expected" )
}
require . True ( t , connSimulator . didTimeout )
} )
t . Run ( "old querier:query_timeout is configured to supersede all others" , func ( t * testing . T ) {
defaultLimits := defaultLimitsTestConfig ( )
defaultLimits . QueryTimeout = model . Duration ( shortestTimeout )
limits , err := validation . NewOverrides ( defaultLimits , nil )
require . NoError ( t , err )
api := NewQuerierAPI ( mockQuerierConfig ( ) , nil , limits , log . NewNopLogger ( ) )
// configure old querier:query_timeout parameter.
// although it is longer than the limits timeout, it should supersede it.
api . cfg . QueryTimeout = time . Millisecond * 100
// although limits:query_timeout is shorter than querier:query_timeout,
// limits:query_timeout should be ignored.
// here we configure it to sleep for 100ms and we want it to timeout at the 100ms.
connSimulator := & slowConnectionSimulator {
sleepFor : api . cfg . QueryTimeout ,
deadline : time . Millisecond * 200 ,
}
midl := WrapQuerySpanAndTimeout ( "mycall" , api ) . Wrap ( connSimulator )
req , err := http . NewRequest ( "GET" , "/loki/api/v1/label" , nil )
ctx , cancelFunc := context . WithTimeout ( user . InjectOrgID ( req . Context ( ) , "fake" ) , time . Millisecond * 200 )
defer cancelFunc ( )
req = req . WithContext ( ctx )
require . NoError ( t , err )
rr := httptest . NewRecorder ( )
srv := http . HandlerFunc ( midl . ServeHTTP )
srv . ServeHTTP ( rr , req )
require . Equal ( t , http . StatusOK , rr . Code )
select {
case <- ctx . Done ( ) :
require . FailNow ( t , fmt . Sprintf ( "should timeout in %s" , api . cfg . QueryTimeout ) )
case <- time . After ( shortestTimeout ) :
// didn't use the limits timeout (i.e: shortest one), exactly what we want.
break
case <- time . After ( api . cfg . QueryTimeout ) :
require . FailNow ( t , fmt . Sprintf ( "should timeout in %s" , api . cfg . QueryTimeout ) )
}
require . True ( t , connSimulator . didTimeout )
} )
t . Run ( "new limits query timeout is configured to supersede all others" , func ( t * testing . T ) {
defaultLimits := defaultLimitsTestConfig ( )
defaultLimits . QueryTimeout = model . Duration ( shortestTimeout )
limits , err := validation . NewOverrides ( defaultLimits , nil )
require . NoError ( t , err )
api := NewQuerierAPI ( mockQuerierConfig ( ) , nil , limits , log . NewNopLogger ( ) )
connSimulator := & slowConnectionSimulator {
sleepFor : time . Millisecond * 100 ,
deadline : shortestTimeout ,
}
midl := WrapQuerySpanAndTimeout ( "mycall" , api ) . Wrap ( connSimulator )
req , err := http . NewRequest ( "GET" , "/loki/api/v1/label" , nil )
ctx , cancelFunc := context . WithTimeout ( user . InjectOrgID ( req . Context ( ) , "fake" ) , time . Millisecond * 100 )
defer cancelFunc ( )
req = req . WithContext ( ctx )
require . NoError ( t , err )
rr := httptest . NewRecorder ( )
srv := http . HandlerFunc ( midl . ServeHTTP )
srv . ServeHTTP ( rr , req )
require . Equal ( t , http . StatusOK , rr . Code )
select {
case <- ctx . Done ( ) :
break
case <- time . After ( shortestTimeout ) :
require . FailNow ( t , "should have timed out before %s" , shortestTimeout )
}
require . True ( t , connSimulator . didTimeout )
} )
}