github.com/thanos-io/thanos@v0.32.5/test/e2e/exemplars_api_test.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package e2e_test 5 6 import ( 7 "context" 8 "fmt" 9 "testing" 10 "time" 11 12 "github.com/efficientgo/e2e" 13 e2emon "github.com/efficientgo/e2e/monitoring" 14 "github.com/pkg/errors" 15 "github.com/prometheus/prometheus/model/timestamp" 16 17 "github.com/efficientgo/core/testutil" 18 "github.com/thanos-io/thanos/pkg/exemplars/exemplarspb" 19 "github.com/thanos-io/thanos/pkg/store/labelpb" 20 "github.com/thanos-io/thanos/test/e2e/e2ethanos" 21 ) 22 23 const ( 24 traceIDLabel = "traceID" 25 ) 26 27 func TestExemplarsAPI_Fanout(t *testing.T) { 28 t.Parallel() 29 var ( 30 prom1, prom2 *e2emon.InstrumentedRunnable 31 sidecar1, sidecar2 *e2emon.InstrumentedRunnable 32 err error 33 e *e2e.DockerEnvironment 34 ) 35 36 e, err = e2e.NewDockerEnvironment("exemplars-fanout") 37 testutil.Ok(t, err) 38 t.Cleanup(e2ethanos.CleanScenario(t, e)) 39 40 qBuilder := e2ethanos.NewQuerierBuilder(e, "query") 41 42 prom1, sidecar1 = e2ethanos.NewPrometheusWithSidecar( 43 e, 44 "prom1", 45 e2ethanos.DefaultPromConfig("ha", 0, "", "", "localhost:9090", qBuilder.InternalEndpoint("http"), e2ethanos.LocalPrometheusTarget), 46 "", 47 e2ethanos.DefaultPrometheusImage(), 48 "", 49 e2ethanos.FeatureExemplarStorage, 50 ) 51 prom2, sidecar2 = e2ethanos.NewPrometheusWithSidecar( 52 e, 53 "prom2", 54 e2ethanos.DefaultPromConfig("ha", 1, "", "", "localhost:9090", qBuilder.InternalEndpoint("http"), e2ethanos.LocalPrometheusTarget), 55 "", 56 e2ethanos.DefaultPrometheusImage(), 57 "", 58 e2ethanos.FeatureExemplarStorage, 59 ) 60 61 tracingCfg := fmt.Sprintf(`type: JAEGER 62 config: 63 sampler_type: const 64 sampler_param: 1 65 service_name: %s`, qBuilder.Name()) 66 67 stores := []string{sidecar1.InternalEndpoint("grpc"), sidecar2.InternalEndpoint("grpc")} 68 69 qBuilder = qBuilder. 70 WithStoreAddresses(stores...). 71 WithExemplarAddresses(stores...). 72 WithTracingConfig(tracingCfg) 73 74 q := qBuilder.Init() 75 testutil.Ok(t, e2e.StartAndWaitReady(q)) 76 77 testutil.Ok(t, err) 78 testutil.Ok(t, e2e.StartAndWaitReady(prom1, sidecar1, prom2, sidecar2)) 79 80 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) 81 t.Cleanup(cancel) 82 83 testutil.Ok(t, q.WaitSumMetricsWithOptions(e2emon.Equals(2), []string{"thanos_store_nodes_grpc_connections"}, e2emon.WaitMissingMetrics())) 84 testutil.Ok(t, q.WaitSumMetricsWithOptions(e2emon.Equals(2), []string{"thanos_query_exemplar_apis_dns_provider_results"}, e2emon.WaitMissingMetrics())) 85 86 now := time.Now() 87 start := timestamp.FromTime(now.Add(-time.Hour)) 88 end := timestamp.FromTime(now.Add(time.Hour)) 89 90 // Send HTTP requests to thanos query to trigger exemplars. 91 labelNames(t, ctx, q.Endpoint("http"), nil, start, end, func(res []string) bool { return true }) 92 93 t.Run("Basic exemplars query", func(t *testing.T) { 94 queryExemplars(t, ctx, q.Endpoint("http"), `http_request_duration_seconds_bucket{handler="label_names"}`, start, end, exemplarsOnExpectedSeries(map[string]string{ 95 "__name__": "http_request_duration_seconds_bucket", 96 "handler": "label_names", 97 "job": "myself", 98 "method": "get", 99 "prometheus": "ha", 100 })) 101 }) 102 103 t.Run("Exemplars query with matched external label", func(t *testing.T) { 104 // Here replica is an external label. 105 queryExemplars(t, ctx, q.Endpoint("http"), `http_request_duration_seconds_bucket{handler="label_names", replica="0"}`, start, end, exemplarsOnExpectedSeries(map[string]string{ 106 "__name__": "http_request_duration_seconds_bucket", 107 "handler": "label_names", 108 "job": "myself", 109 "method": "get", 110 "prometheus": "ha", 111 })) 112 }) 113 114 t.Run("Exemplars query doesn't match external label", func(t *testing.T) { 115 // Here replica is an external label, but it doesn't match. 116 queryExemplars(t, ctx, q.Endpoint("http"), `http_request_duration_seconds_bucket{handler="label_names", replica="foo"}`, 117 start, end, func(data []*exemplarspb.ExemplarData) error { 118 if len(data) > 0 { 119 return errors.Errorf("expected no examplers, got %v", data) 120 } 121 return nil 122 }) 123 }) 124 } 125 126 func exemplarsOnExpectedSeries(requiredSeriesLabels map[string]string) func(data []*exemplarspb.ExemplarData) error { 127 return func(data []*exemplarspb.ExemplarData) error { 128 if len(data) != 1 { 129 return errors.Errorf("unexpected result size, expected 1, got: %v", len(data)) 130 } 131 132 // Compare series labels. 133 seriesLabels := labelpb.ZLabelSetsToPromLabelSets(data[0].SeriesLabels) 134 for _, lbls := range seriesLabels { 135 for k, v := range requiredSeriesLabels { 136 if lbls.Get(k) != v { 137 return errors.Errorf("unexpected labels in result, expected %v, got: %v", requiredSeriesLabels, seriesLabels) 138 } 139 } 140 } 141 142 // Make sure the exemplar contains the correct traceID label. 143 for _, exemplar := range data[0].Exemplars { 144 for _, lbls := range labelpb.ZLabelSetsToPromLabelSets(exemplar.Labels) { 145 if !lbls.Has(traceIDLabel) { 146 return errors.Errorf("unexpected labels in exemplar, expected %v, got: %v", traceIDLabel, exemplar.Labels) 147 } 148 } 149 } 150 return nil 151 } 152 }