github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/prometheus/remote/write_test.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package remote 22 23 import ( 24 "bytes" 25 "context" 26 "errors" 27 "fmt" 28 "io/ioutil" 29 "math" 30 "net/http" 31 "net/http/httptest" 32 "strings" 33 "testing" 34 "time" 35 36 "github.com/golang/mock/gomock" 37 "github.com/stretchr/testify/assert" 38 "github.com/stretchr/testify/require" 39 "github.com/uber-go/tally" 40 41 "github.com/m3db/m3/src/cmd/services/m3coordinator/ingest" 42 "github.com/m3db/m3/src/cmd/services/m3query/config" 43 "github.com/m3db/m3/src/dbnode/generated/proto/annotation" 44 "github.com/m3db/m3/src/metrics/policy" 45 "github.com/m3db/m3/src/query/api/v1/handler/prometheus/handleroptions" 46 "github.com/m3db/m3/src/query/api/v1/handler/prometheus/remote/test" 47 "github.com/m3db/m3/src/query/api/v1/options" 48 "github.com/m3db/m3/src/query/generated/proto/prompb" 49 "github.com/m3db/m3/src/query/models" 50 "github.com/m3db/m3/src/query/storage/m3/storagemetadata" 51 xclock "github.com/m3db/m3/src/x/clock" 52 xerrors "github.com/m3db/m3/src/x/errors" 53 "github.com/m3db/m3/src/x/headers" 54 "github.com/m3db/m3/src/x/instrument" 55 xtest "github.com/m3db/m3/src/x/test" 56 ) 57 58 func makeOptions(ds ingest.DownsamplerAndWriter) options.HandlerOptions { 59 return options.EmptyHandlerOptions(). 60 SetNowFn(time.Now). 61 SetDownsamplerAndWriter(ds). 62 SetTagOptions(models.NewTagOptions()). 63 SetConfig(config.Configuration{ 64 WriteForwarding: config.WriteForwardingConfiguration{ 65 PromRemoteWrite: handleroptions.PromWriteHandlerForwardingOptions{}, 66 }, 67 }). 68 SetStoreMetricsType(true) 69 } 70 71 func TestPromWriteParsing(t *testing.T) { 72 ctrl := xtest.NewController(t) 73 defer ctrl.Finish() 74 75 mockDownsamplerAndWriter := ingest.NewMockDownsamplerAndWriter(ctrl) 76 handlerOpts := makeOptions(mockDownsamplerAndWriter) 77 handler, err := NewPromWriteHandler(handlerOpts) 78 require.NoError(t, err) 79 80 promReq := test.GeneratePromWriteRequest() 81 promReqBody := test.GeneratePromWriteRequestBody(t, promReq) 82 req := httptest.NewRequest(PromWriteHTTPMethod, PromWriteURL, promReqBody) 83 84 r, err := handler.(*PromWriteHandler).parseRequest(req) 85 require.Nil(t, err, "unable to parse request") 86 require.Equal(t, len(r.Request.Timeseries), 2) 87 require.Equal(t, ingest.WriteOptions{}, r.Options) 88 } 89 90 func TestPromWrite(t *testing.T) { 91 ctrl := xtest.NewController(t) 92 defer ctrl.Finish() 93 94 mockDownsamplerAndWriter := ingest.NewMockDownsamplerAndWriter(ctrl) 95 mockDownsamplerAndWriter. 96 EXPECT(). 97 WriteBatch(gomock.Any(), gomock.Any(), gomock.Any()) 98 99 opts := makeOptions(mockDownsamplerAndWriter) 100 handler, err := NewPromWriteHandler(opts) 101 require.NoError(t, err) 102 103 promReq := test.GeneratePromWriteRequest() 104 promReqBody := test.GeneratePromWriteRequestBody(t, promReq) 105 req := httptest.NewRequest(PromWriteHTTPMethod, PromWriteURL, promReqBody) 106 107 writer := httptest.NewRecorder() 108 handler.ServeHTTP(writer, req) 109 resp := writer.Result() 110 require.Equal(t, http.StatusOK, resp.StatusCode) 111 } 112 113 func TestPromWriteError(t *testing.T) { 114 ctrl := xtest.NewController(t) 115 defer ctrl.Finish() 116 117 multiErr := xerrors.NewMultiError().Add(errors.New("an error")) 118 batchErr := ingest.BatchError(multiErr) 119 120 mockDownsamplerAndWriter := ingest.NewMockDownsamplerAndWriter(ctrl) 121 mockDownsamplerAndWriter.EXPECT(). 122 WriteBatch(gomock.Any(), gomock.Any(), gomock.Any()). 123 Return(batchErr) 124 125 opts := makeOptions(mockDownsamplerAndWriter) 126 handler, err := NewPromWriteHandler(opts) 127 require.NoError(t, err) 128 129 promReq := test.GeneratePromWriteRequest() 130 promReqBody := test.GeneratePromWriteRequestBody(t, promReq) 131 req := httptest.NewRequest(PromWriteHTTPMethod, PromWriteURL, promReqBody) 132 require.NoError(t, err) 133 134 writer := httptest.NewRecorder() 135 handler.ServeHTTP(writer, req) 136 resp := writer.Result() 137 require.Equal(t, http.StatusInternalServerError, resp.StatusCode) 138 139 body, err := ioutil.ReadAll(resp.Body) 140 require.NoError(t, err) 141 require.True(t, bytes.Contains(body, []byte(batchErr.Error()))) 142 } 143 144 func TestWriteErrorMetricCount(t *testing.T) { 145 ctrl := xtest.NewController(t) 146 defer ctrl.Finish() 147 148 mockDownsamplerAndWriter := ingest.NewMockDownsamplerAndWriter(ctrl) 149 150 scope := tally.NewTestScope("", 151 map[string]string{"test": "error-metric-test"}) 152 153 iopts := instrument.NewOptions().SetMetricsScope(scope) 154 opts := makeOptions(mockDownsamplerAndWriter).SetInstrumentOpts(iopts) 155 handler, err := NewPromWriteHandler(opts) 156 require.NoError(t, err) 157 158 req := httptest.NewRequest(PromWriteHTTPMethod, PromWriteURL, nil) 159 handler.ServeHTTP(httptest.NewRecorder(), req) 160 161 foundMetric := xclock.WaitUntil(func() bool { 162 found, ok := scope.Snapshot().Counters()["write.errors+code=4XX,handler=remote-write,test=error-metric-test"] 163 return ok && found.Value() == 1 164 }, 5*time.Second) 165 require.True(t, foundMetric) 166 } 167 168 func TestWriteDatapointDelayMetric(t *testing.T) { 169 ctrl := xtest.NewController(t) 170 defer ctrl.Finish() 171 172 mockDownsamplerAndWriter := ingest.NewMockDownsamplerAndWriter(ctrl) 173 mockDownsamplerAndWriter. 174 EXPECT(). 175 WriteBatch(gomock.Any(), gomock.Any(), gomock.Any()) 176 177 scope := tally.NewTestScope("", 178 map[string]string{"test": "delay-metric-test"}) 179 180 iopts := instrument.NewOptions().SetMetricsScope(scope) 181 opts := makeOptions(mockDownsamplerAndWriter).SetInstrumentOpts(iopts) 182 handler, err := NewPromWriteHandler(opts) 183 require.NoError(t, err) 184 185 writeHandler, ok := handler.(*PromWriteHandler) 186 require.True(t, ok) 187 188 buckets := writeHandler.metrics.ingestLatencyBuckets 189 190 // NB(r): Bucket length is tested just to sanity check how many buckets we are creating 191 require.Equal(t, 80, len(buckets.AsDurations())) 192 193 // NB(r): Bucket values are tested to sanity check they look right 194 expected := "[0s 100ms 200ms 300ms 400ms 500ms 600ms 700ms 800ms 900ms 1s 1.5s 2s 2.5s 3s 3.5s 4s 4.5s 5s 5.5s 6s 6.5s 7s 7.5s 8s 8.5s 9s 9.5s 10s 15s 20s 25s 30s 35s 40s 45s 50s 55s 1m0s 5m0s 10m0s 15m0s 20m0s 25m0s 30m0s 35m0s 40m0s 45m0s 50m0s 55m0s 1h0m0s 1h30m0s 2h0m0s 2h30m0s 3h0m0s 3h30m0s 4h0m0s 4h30m0s 5h0m0s 5h30m0s 6h0m0s 6h30m0s 7h0m0s 8h0m0s 9h0m0s 10h0m0s 11h0m0s 12h0m0s 13h0m0s 14h0m0s 15h0m0s 16h0m0s 17h0m0s 18h0m0s 19h0m0s 20h0m0s 21h0m0s 22h0m0s 23h0m0s 24h0m0s]" 195 actual := fmt.Sprintf("%v", buckets.AsDurations()) 196 require.Equal(t, expected, actual) 197 198 // Ensure buckets increasing in order 199 lastValue := time.Duration(math.MinInt64) 200 for _, value := range buckets.AsDurations() { 201 require.True(t, value > lastValue, 202 fmt.Sprintf("%s must be greater than last bucket value %s", value, lastValue)) 203 lastValue = value 204 } 205 206 promReq := test.GeneratePromWriteRequest() 207 promReqBody := test.GeneratePromWriteRequestBody(t, promReq) 208 req := httptest.NewRequest(PromWriteHTTPMethod, PromWriteURL, promReqBody) 209 handler.ServeHTTP(httptest.NewRecorder(), req) 210 211 foundMetric := xclock.WaitUntil(func() bool { 212 values, found := scope.Snapshot().Histograms()["ingest.latency+handler=remote-write,test=delay-metric-test"] 213 if !found { 214 return false 215 } 216 for _, valuesInBucket := range values.Durations() { 217 if valuesInBucket > 0 { 218 return true 219 } 220 } 221 return false 222 }, 5*time.Second) 223 require.True(t, foundMetric) 224 } 225 226 func TestPromWriteUnaggregatedMetricsWithHeader(t *testing.T) { 227 ctrl := xtest.NewController(t) 228 defer ctrl.Finish() 229 230 expectedIngestWriteOptions := ingest.WriteOptions{ 231 DownsampleOverride: true, 232 DownsampleMappingRules: nil, 233 WriteOverride: false, 234 WriteStoragePolicies: nil, 235 } 236 237 mockDownsamplerAndWriter := ingest.NewMockDownsamplerAndWriter(ctrl) 238 mockDownsamplerAndWriter. 239 EXPECT(). 240 WriteBatch(gomock.Any(), gomock.Any(), expectedIngestWriteOptions) 241 242 opts := makeOptions(mockDownsamplerAndWriter) 243 handler, err := NewPromWriteHandler(opts) 244 require.NoError(t, err) 245 246 promReq := test.GeneratePromWriteRequest() 247 promReqBody := test.GeneratePromWriteRequestBody(t, promReq) 248 req := httptest.NewRequest(PromWriteHTTPMethod, PromWriteURL, promReqBody) 249 req.Header.Add(headers.MetricsTypeHeader, 250 storagemetadata.UnaggregatedMetricsType.String()) 251 252 writer := httptest.NewRecorder() 253 handler.ServeHTTP(writer, req) 254 resp := writer.Result() 255 require.Equal(t, http.StatusOK, resp.StatusCode) 256 } 257 258 func TestPromWriteAggregatedMetricsWithHeader(t *testing.T) { 259 ctrl := xtest.NewController(t) 260 defer ctrl.Finish() 261 262 expectedIngestWriteOptions := ingest.WriteOptions{ 263 DownsampleOverride: true, 264 DownsampleMappingRules: nil, 265 WriteOverride: true, 266 WriteStoragePolicies: policy.StoragePolicies{ 267 policy.MustParseStoragePolicy("1m:21d"), 268 }, 269 } 270 271 mockDownsamplerAndWriter := ingest.NewMockDownsamplerAndWriter(ctrl) 272 mockDownsamplerAndWriter. 273 EXPECT(). 274 WriteBatch(gomock.Any(), gomock.Any(), expectedIngestWriteOptions) 275 276 opts := makeOptions(mockDownsamplerAndWriter) 277 writeHandler, err := NewPromWriteHandler(opts) 278 require.NoError(t, err) 279 280 promReq := test.GeneratePromWriteRequest() 281 promReqBody := test.GeneratePromWriteRequestBody(t, promReq) 282 req := httptest.NewRequest(PromWriteHTTPMethod, PromWriteURL, promReqBody) 283 req.Header.Add(headers.MetricsTypeHeader, 284 storagemetadata.AggregatedMetricsType.String()) 285 req.Header.Add(headers.MetricsStoragePolicyHeader, 286 "1m:21d") 287 288 writer := httptest.NewRecorder() 289 writeHandler.ServeHTTP(writer, req) 290 resp := writer.Result() 291 require.Equal(t, http.StatusOK, resp.StatusCode) 292 } 293 294 func TestPromWriteOpenMetricsTypes(t *testing.T) { 295 ctrl := xtest.NewController(t) 296 defer ctrl.Finish() 297 298 var capturedIter ingest.DownsampleAndWriteIter 299 mockDownsamplerAndWriter := ingest.NewMockDownsamplerAndWriter(ctrl) 300 mockDownsamplerAndWriter. 301 EXPECT(). 302 WriteBatch(gomock.Any(), gomock.Any(), gomock.Any()). 303 Do(func(_ context.Context, iter ingest.DownsampleAndWriteIter, _ ingest.WriteOptions) ingest.BatchError { 304 capturedIter = iter 305 return nil 306 }) 307 308 opts := makeOptions(mockDownsamplerAndWriter) 309 310 promReq := &prompb.WriteRequest{ 311 Timeseries: []prompb.TimeSeries{ 312 {Type: prompb.MetricType_UNKNOWN}, 313 {Type: prompb.MetricType_COUNTER}, 314 {Type: prompb.MetricType_GAUGE}, 315 {Type: prompb.MetricType_GAUGE}, 316 {Type: prompb.MetricType_SUMMARY}, 317 {Type: prompb.MetricType_HISTOGRAM}, 318 {Type: prompb.MetricType_GAUGE_HISTOGRAM}, 319 {Type: prompb.MetricType_INFO}, 320 {Type: prompb.MetricType_STATESET}, 321 {}, 322 }, 323 } 324 325 executeWriteRequest(t, opts, promReq) 326 327 firstValue := verifyIterValueAnnotation(t, capturedIter, annotation.OpenMetricsFamilyType_UNKNOWN, false) 328 secondValue := verifyIterValueAnnotation(t, capturedIter, annotation.OpenMetricsFamilyType_COUNTER, true) 329 verifyIterValueAnnotation(t, capturedIter, annotation.OpenMetricsFamilyType_GAUGE, false) 330 verifyIterValueAnnotation(t, capturedIter, annotation.OpenMetricsFamilyType_GAUGE, false) 331 verifyIterValueAnnotation(t, capturedIter, annotation.OpenMetricsFamilyType_SUMMARY, false) 332 verifyIterValueAnnotation(t, capturedIter, annotation.OpenMetricsFamilyType_HISTOGRAM, true) 333 verifyIterValueAnnotation(t, capturedIter, annotation.OpenMetricsFamilyType_GAUGE_HISTOGRAM, false) 334 verifyIterValueAnnotation(t, capturedIter, annotation.OpenMetricsFamilyType_INFO, false) 335 verifyIterValueAnnotation(t, capturedIter, annotation.OpenMetricsFamilyType_STATESET, false) 336 verifyIterValueAnnotation(t, capturedIter, annotation.OpenMetricsFamilyType_UNKNOWN, false) 337 338 require.False(t, capturedIter.Next()) 339 require.NoError(t, capturedIter.Error()) 340 341 assert.Nil(t, firstValue.Annotation, "first annotation invalidation") 342 343 secondAnnotationPayload := unmarshalAnnotation(t, secondValue.Annotation) 344 assert.Equal(t, annotation.Payload{ 345 OpenMetricsFamilyType: annotation.OpenMetricsFamilyType_COUNTER, 346 OpenMetricsHandleValueResets: true, 347 }, secondAnnotationPayload, "second annotation invalidated") 348 } 349 350 func TestPromWriteGraphiteMetricsTypes(t *testing.T) { 351 ctrl := xtest.NewController(t) 352 defer ctrl.Finish() 353 354 var capturedIter ingest.DownsampleAndWriteIter 355 mockDownsamplerAndWriter := ingest.NewMockDownsamplerAndWriter(ctrl) 356 mockDownsamplerAndWriter. 357 EXPECT(). 358 WriteBatch(gomock.Any(), gomock.Any(), gomock.Any()). 359 Do(func(_ context.Context, iter ingest.DownsampleAndWriteIter, _ ingest.WriteOptions) ingest.BatchError { 360 capturedIter = iter 361 return nil 362 }) 363 364 opts := makeOptions(mockDownsamplerAndWriter) 365 366 promReq := &prompb.WriteRequest{ 367 Timeseries: []prompb.TimeSeries{ 368 {Source: prompb.Source_GRAPHITE, M3Type: prompb.M3Type_M3_TIMER}, 369 {Source: prompb.Source_GRAPHITE, M3Type: prompb.M3Type_M3_COUNTER}, 370 {Source: prompb.Source_GRAPHITE, M3Type: prompb.M3Type_M3_GAUGE}, 371 {Source: prompb.Source_GRAPHITE, M3Type: prompb.M3Type_M3_GAUGE}, 372 {Source: prompb.Source_GRAPHITE, M3Type: prompb.M3Type_M3_TIMER}, 373 {Source: prompb.Source_GRAPHITE, M3Type: prompb.M3Type_M3_COUNTER}, 374 }, 375 } 376 377 executeWriteRequest(t, opts, promReq) 378 379 verifyIterValueAnnotationGraphite(t, capturedIter, annotation.GraphiteType_GRAPHITE_TIMER) 380 verifyIterValueAnnotationGraphite(t, capturedIter, annotation.GraphiteType_GRAPHITE_COUNTER) 381 verifyIterValueAnnotationGraphite(t, capturedIter, annotation.GraphiteType_GRAPHITE_GAUGE) 382 verifyIterValueAnnotationGraphite(t, capturedIter, annotation.GraphiteType_GRAPHITE_GAUGE) 383 verifyIterValueAnnotationGraphite(t, capturedIter, annotation.GraphiteType_GRAPHITE_TIMER) 384 verifyIterValueAnnotationGraphite(t, capturedIter, annotation.GraphiteType_GRAPHITE_COUNTER) 385 386 require.False(t, capturedIter.Next()) 387 require.NoError(t, capturedIter.Error()) 388 } 389 390 func TestPromWriteDisabledMetricsTypes(t *testing.T) { 391 ctrl := xtest.NewController(t) 392 defer ctrl.Finish() 393 394 var capturedIter ingest.DownsampleAndWriteIter 395 mockDownsamplerAndWriter := ingest.NewMockDownsamplerAndWriter(ctrl) 396 mockDownsamplerAndWriter. 397 EXPECT(). 398 WriteBatch(gomock.Any(), gomock.Any(), gomock.Any()). 399 Do(func(_ context.Context, iter ingest.DownsampleAndWriteIter, _ ingest.WriteOptions) ingest.BatchError { 400 capturedIter = iter 401 return nil 402 }) 403 404 opts := makeOptions(mockDownsamplerAndWriter).SetStoreMetricsType(false) 405 406 promReq := &prompb.WriteRequest{ 407 Timeseries: []prompb.TimeSeries{ 408 {Type: prompb.MetricType_COUNTER}, 409 {}, 410 }, 411 } 412 413 executeWriteRequest(t, opts, promReq) 414 415 verifyIterValueNoAnnotation(t, capturedIter) 416 verifyIterValueNoAnnotation(t, capturedIter) 417 418 require.False(t, capturedIter.Next()) 419 require.NoError(t, capturedIter.Error()) 420 } 421 422 func TestPromWriteLiteralIsTooLongError(t *testing.T) { 423 ctrl := xtest.NewController(t) 424 defer ctrl.Finish() 425 426 opts := makeOptions(ingest.NewMockDownsamplerAndWriter(ctrl)) 427 handler, err := NewPromWriteHandler(opts) 428 require.NoError(t, err) 429 430 veryLongLiteral := strings.Repeat("x", int(opts.TagOptions().MaxTagLiteralLength())+1) 431 promReq := &prompb.WriteRequest{ 432 Timeseries: []prompb.TimeSeries{ 433 { 434 Labels: []prompb.Label{ 435 {Name: []byte("name1"), Value: []byte("value1")}, 436 {Name: []byte("name2"), Value: []byte(veryLongLiteral)}, 437 }, 438 }, 439 }, 440 } 441 442 for i := 0; i < maxLiteralIsTooLongLogCount*2; i++ { 443 promReqBody := test.GeneratePromWriteRequestBody(t, promReq) 444 req := httptest.NewRequest(PromWriteHTTPMethod, PromWriteURL, promReqBody) 445 writer := httptest.NewRecorder() 446 handler.ServeHTTP(writer, req) 447 resp := writer.Result() 448 require.Equal(t, http.StatusBadRequest, resp.StatusCode) 449 require.NoError(t, resp.Body.Close()) 450 } 451 } 452 453 func BenchmarkWriteDatapoints(b *testing.B) { 454 ctrl := xtest.NewController(b) 455 defer ctrl.Finish() 456 457 mockDownsamplerAndWriter := ingest.NewMockDownsamplerAndWriter(ctrl) 458 mockDownsamplerAndWriter. 459 EXPECT(). 460 WriteBatch(gomock.Any(), gomock.Any(), gomock.Any()). 461 AnyTimes() 462 463 opts := makeOptions(mockDownsamplerAndWriter) 464 handler, err := NewPromWriteHandler(opts) 465 require.NoError(b, err) 466 467 promReq := test.GeneratePromWriteRequest() 468 promReqBody := test.GeneratePromWriteRequestBodyBytes(b, promReq) 469 promReqBodyReader := bytes.NewReader(nil) 470 471 for i := 0; i < b.N; i++ { 472 promReqBodyReader.Reset(promReqBody) 473 req := httptest.NewRequest(PromWriteHTTPMethod, PromWriteURL, promReqBodyReader) 474 handler.ServeHTTP(httptest.NewRecorder(), req) 475 } 476 } 477 478 func verifyIterValueAnnotation( 479 t *testing.T, 480 iter ingest.DownsampleAndWriteIter, 481 expectedMetricType annotation.OpenMetricsFamilyType, 482 expectedHandleValueResets bool, 483 ) ingest.IterValue { 484 require.True(t, iter.Next()) 485 value := iter.Current() 486 487 expectedPayload := annotation.Payload{ 488 SourceFormat: annotation.SourceFormat_OPEN_METRICS, 489 OpenMetricsFamilyType: expectedMetricType, 490 OpenMetricsHandleValueResets: expectedHandleValueResets, 491 } 492 assert.Equal(t, expectedPayload, unmarshalAnnotation(t, value.Annotation)) 493 494 return value 495 } 496 497 func verifyIterValueAnnotationGraphite( 498 t *testing.T, 499 iter ingest.DownsampleAndWriteIter, 500 expectedMetricType annotation.GraphiteType, 501 ) { 502 require.True(t, iter.Next()) 503 value := iter.Current() 504 505 expectedPayload := annotation.Payload{ 506 SourceFormat: annotation.SourceFormat_GRAPHITE, 507 GraphiteType: expectedMetricType, 508 } 509 assert.Equal(t, expectedPayload, unmarshalAnnotation(t, value.Annotation)) 510 } 511 512 func verifyIterValueNoAnnotation(t *testing.T, iter ingest.DownsampleAndWriteIter) { 513 require.True(t, iter.Next()) 514 value := iter.Current() 515 assert.Nil(t, value.Annotation) 516 } 517 518 func unmarshalAnnotation(t *testing.T, annot []byte) annotation.Payload { 519 payload := annotation.Payload{} 520 require.NoError(t, payload.Unmarshal(annot)) 521 return payload 522 } 523 524 func executeWriteRequest(t *testing.T, handlerOpts options.HandlerOptions, promReq *prompb.WriteRequest) { 525 handler, err := NewPromWriteHandler(handlerOpts) 526 require.NoError(t, err) 527 528 promReqBody := test.GeneratePromWriteRequestBody(t, promReq) 529 req := httptest.NewRequest(PromWriteHTTPMethod, PromWriteURL, promReqBody) 530 531 writer := httptest.NewRecorder() 532 handler.ServeHTTP(writer, req) 533 resp := writer.Result() 534 require.Equal(t, http.StatusOK, resp.StatusCode) 535 }