@@ -530,3 +530,91 @@ func TestQueryFrontendNoRetryChunkPool(t *testing.T) {
530530 // We shouldn't be able to see any retries.
531531 require .NoError (t , queryFrontend .WaitSumMetricsWithOptions (e2e .Equals (0 ), []string {"cortex_query_frontend_retries" }, e2e .WaitMissingMetrics ))
532532}
533+
534+ func TestQueryFrontendErrorResponseFormat (t * testing.T ) {
535+ const blockRangePeriod = 5 * time .Second
536+
537+ s , err := e2e .NewScenario (networkName )
538+ require .NoError (t , err )
539+ defer s .Close ()
540+
541+ // Configure the blocks storage to frequently compact TSDB head
542+ // and ship blocks to the storage.
543+ flags := mergeFlags (BlocksStorageFlags (), map [string ]string {
544+ "-blocks-storage.tsdb.block-ranges-period" : blockRangePeriod .String (),
545+ "-blocks-storage.tsdb.ship-interval" : "1s" ,
546+ "-blocks-storage.tsdb.retention-period" : ((blockRangePeriod * 2 ) - 1 ).String (),
547+ "-blocks-storage.bucket-store.max-chunk-pool-bytes" : "1" ,
548+ })
549+
550+ // Start dependencies.
551+ consul := e2edb .NewConsul ()
552+ minio := e2edb .NewMinio (9000 , flags ["-blocks-storage.s3.bucket-name" ])
553+ require .NoError (t , s .StartAndWaitReady (consul , minio ))
554+
555+ // Start Cortex components for the write path.
556+ distributor := e2ecortex .NewDistributor ("distributor" , e2ecortex .RingStoreConsul , consul .NetworkHTTPEndpoint (), flags , "" )
557+ ingester := e2ecortex .NewIngester ("ingester" , e2ecortex .RingStoreConsul , consul .NetworkHTTPEndpoint (), flags , "" )
558+ require .NoError (t , s .StartAndWaitReady (distributor , ingester ))
559+
560+ // Wait until the distributor has updated the ring.
561+ require .NoError (t , distributor .WaitSumMetrics (e2e .Equals (512 ), "cortex_ring_tokens_total" ))
562+
563+ // Push some series to Cortex.
564+ c , err := e2ecortex .NewClient (distributor .HTTPEndpoint (), "" , "" , "" , "user-1" )
565+ require .NoError (t , err )
566+
567+ seriesTimestamp := time .Now ()
568+ series2Timestamp := seriesTimestamp .Add (blockRangePeriod * 2 )
569+ series1 , _ := generateSeries ("series_1" , seriesTimestamp , prompb.Label {Name : "job" , Value : "test" })
570+ series2 , _ := generateSeries ("series_2" , series2Timestamp , prompb.Label {Name : "job" , Value : "test" })
571+
572+ res , err := c .Push (series1 )
573+ require .NoError (t , err )
574+ require .Equal (t , 200 , res .StatusCode )
575+
576+ res , err = c .Push (series2 )
577+ require .NoError (t , err )
578+ require .Equal (t , 200 , res .StatusCode )
579+
580+ // Wait until the TSDB head is compacted and shipped to the storage.
581+ // The shipped block contains the 1st series, while the 2ns series is in the head.
582+ require .NoError (t , ingester .WaitSumMetrics (e2e .Equals (1 ), "cortex_ingester_shipper_uploads_total" ))
583+ require .NoError (t , ingester .WaitSumMetrics (e2e .Equals (2 ), "cortex_ingester_memory_series_created_total" ))
584+ require .NoError (t , ingester .WaitSumMetrics (e2e .Equals (1 ), "cortex_ingester_memory_series_removed_total" ))
585+ require .NoError (t , ingester .WaitSumMetrics (e2e .Equals (1 ), "cortex_ingester_memory_series" ))
586+
587+ queryFrontend := e2ecortex .NewQueryFrontendWithConfigFile ("query-frontend" , "" , flags , "" )
588+ require .NoError (t , s .Start (queryFrontend ))
589+
590+ // Start the querier and store-gateway, and configure them to frequently sync blocks fast enough to trigger consistency check.
591+ storeGateway := e2ecortex .NewStoreGateway ("store-gateway" , e2ecortex .RingStoreConsul , consul .NetworkHTTPEndpoint (), mergeFlags (flags , map [string ]string {
592+ "-blocks-storage.bucket-store.sync-interval" : "5s" ,
593+ }), "" )
594+ querier := e2ecortex .NewQuerier ("querier" , e2ecortex .RingStoreConsul , consul .NetworkHTTPEndpoint (), mergeFlags (flags , map [string ]string {
595+ "-blocks-storage.bucket-store.sync-interval" : "1s" ,
596+ "-querier.frontend-address" : queryFrontend .NetworkGRPCEndpoint (),
597+ }), "" )
598+ require .NoError (t , s .StartAndWaitReady (querier , storeGateway ))
599+
600+ // Wait until the querier and store-gateway have updated the ring, and wait until the blocks are old enough for consistency check
601+ require .NoError (t , querier .WaitSumMetrics (e2e .Equals (512 * 2 ), "cortex_ring_tokens_total" ))
602+ require .NoError (t , storeGateway .WaitSumMetrics (e2e .Equals (512 ), "cortex_ring_tokens_total" ))
603+ require .NoError (t , querier .WaitSumMetricsWithOptions (e2e .GreaterOrEqual (4 ), []string {"cortex_querier_blocks_scan_duration_seconds" }, e2e .WithMetricCount ))
604+
605+ // Sleep 3 * bucket sync interval to make sure consistency checker
606+ // doesn't consider block is uploaded recently.
607+ time .Sleep (3 * time .Second )
608+
609+ // Query back the series.
610+ c , err = e2ecortex .NewClient ("" , queryFrontend .HTTPEndpoint (), "" , "" , "user-1" )
611+ require .NoError (t , err )
612+
613+ // We expect request to hit chunk pool exhaustion.
614+ resp , body , err := c .QueryRaw (`{job="test"}` , series2Timestamp )
615+ require .NoError (t , err )
616+ require .Equal (t , http .StatusInternalServerError , resp .StatusCode )
617+ require .Contains (t , string (body ), pool .ErrPoolExhausted .Error ())
618+ // We shouldn't be able to see any retries.
619+ require .NoError (t , queryFrontend .WaitSumMetricsWithOptions (e2e .Equals (0 ), []string {"cortex_query_frontend_retries" }, e2e .WaitMissingMetrics ))
620+ }
0 commit comments