prometheus
606 строк · 16.6 Кб
1// Copyright 2013 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package scrape
15
16import (
17"crypto/tls"
18"crypto/x509"
19"fmt"
20"net/http"
21"net/http/httptest"
22"net/url"
23"os"
24"strconv"
25"strings"
26"testing"
27"time"
28
29config_util "github.com/prometheus/common/config"
30"github.com/prometheus/common/model"
31"github.com/stretchr/testify/require"
32
33"github.com/prometheus/prometheus/config"
34"github.com/prometheus/prometheus/discovery/targetgroup"
35"github.com/prometheus/prometheus/model/histogram"
36"github.com/prometheus/prometheus/model/labels"
37)
38
39const (
40caCertPath = "testdata/ca.cer"
41)
42
43func TestTargetLabels(t *testing.T) {
44target := newTestTarget("example.com:80", 0, labels.FromStrings("job", "some_job", "foo", "bar"))
45want := labels.FromStrings(model.JobLabel, "some_job", "foo", "bar")
46b := labels.NewScratchBuilder(0)
47got := target.Labels(&b)
48require.Equal(t, want, got)
49i := 0
50target.LabelsRange(func(l labels.Label) {
51switch i {
52case 0:
53require.Equal(t, labels.Label{Name: "foo", Value: "bar"}, l)
54case 1:
55require.Equal(t, labels.Label{Name: model.JobLabel, Value: "some_job"}, l)
56}
57i++
58})
59require.Equal(t, 2, i)
60}
61
62func TestTargetOffset(t *testing.T) {
63interval := 10 * time.Second
64offsetSeed := uint64(0)
65
66offsets := make([]time.Duration, 10000)
67
68// Calculate offsets for 10000 different targets.
69for i := range offsets {
70target := newTestTarget("example.com:80", 0, labels.FromStrings(
71"label", strconv.Itoa(i),
72))
73offsets[i] = target.offset(interval, offsetSeed)
74}
75
76// Put the offsets into buckets and validate that they are all
77// within bounds.
78bucketSize := 1 * time.Second
79buckets := make([]int, interval/bucketSize)
80
81for _, offset := range offsets {
82require.InDelta(t, time.Duration(0), offset, float64(interval), "Offset %v out of bounds.", offset)
83
84bucket := offset / bucketSize
85buckets[bucket]++
86}
87
88t.Log(buckets)
89
90// Calculate whether the number of targets per bucket
91// does not differ more than a given tolerance.
92avg := len(offsets) / len(buckets)
93tolerance := 0.15
94
95for _, bucket := range buckets {
96diff := bucket - avg
97if diff < 0 {
98diff = -diff
99}
100
101require.LessOrEqual(t, float64(diff)/float64(avg), tolerance, "Bucket out of tolerance bounds.")
102}
103}
104
105func TestTargetURL(t *testing.T) {
106params := url.Values{
107"abc": []string{"foo", "bar", "baz"},
108"xyz": []string{"hoo"},
109}
110labels := labels.FromMap(map[string]string{
111model.AddressLabel: "example.com:1234",
112model.SchemeLabel: "https",
113model.MetricsPathLabel: "/metricz",
114"__param_abc": "overwrite",
115"__param_cde": "huu",
116})
117target := NewTarget(labels, labels, params)
118
119// The reserved labels are concatenated into a full URL. The first value for each
120// URL query parameter can be set/modified via labels as well.
121expectedParams := url.Values{
122"abc": []string{"overwrite", "bar", "baz"},
123"cde": []string{"huu"},
124"xyz": []string{"hoo"},
125}
126expectedURL := &url.URL{
127Scheme: "https",
128Host: "example.com:1234",
129Path: "/metricz",
130RawQuery: expectedParams.Encode(),
131}
132
133require.Equal(t, expectedURL, target.URL())
134}
135
136func newTestTarget(targetURL string, _ time.Duration, lbls labels.Labels) *Target {
137lb := labels.NewBuilder(lbls)
138lb.Set(model.SchemeLabel, "http")
139lb.Set(model.AddressLabel, strings.TrimPrefix(targetURL, "http://"))
140lb.Set(model.MetricsPathLabel, "/metrics")
141
142return &Target{labels: lb.Labels()}
143}
144
145func TestNewHTTPBearerToken(t *testing.T) {
146server := httptest.NewServer(
147http.HandlerFunc(
148func(w http.ResponseWriter, r *http.Request) {
149expected := "Bearer 1234"
150received := r.Header.Get("Authorization")
151require.Equal(t, expected, received, "Authorization header was not set correctly.")
152},
153),
154)
155defer server.Close()
156
157cfg := config_util.HTTPClientConfig{
158BearerToken: "1234",
159}
160c, err := config_util.NewClientFromConfig(cfg, "test")
161require.NoError(t, err)
162_, err = c.Get(server.URL)
163require.NoError(t, err)
164}
165
166func TestNewHTTPBearerTokenFile(t *testing.T) {
167server := httptest.NewServer(
168http.HandlerFunc(
169func(w http.ResponseWriter, r *http.Request) {
170expected := "Bearer 12345"
171received := r.Header.Get("Authorization")
172require.Equal(t, expected, received, "Authorization header was not set correctly.")
173},
174),
175)
176defer server.Close()
177
178cfg := config_util.HTTPClientConfig{
179BearerTokenFile: "testdata/bearertoken.txt",
180}
181c, err := config_util.NewClientFromConfig(cfg, "test")
182require.NoError(t, err)
183_, err = c.Get(server.URL)
184require.NoError(t, err)
185}
186
187func TestNewHTTPBasicAuth(t *testing.T) {
188server := httptest.NewServer(
189http.HandlerFunc(
190func(w http.ResponseWriter, r *http.Request) {
191username, password, ok := r.BasicAuth()
192require.True(t, ok, "Basic authorization header was not set correctly.")
193require.Equal(t, "user", username)
194require.Equal(t, "password123", password)
195},
196),
197)
198defer server.Close()
199
200cfg := config_util.HTTPClientConfig{
201BasicAuth: &config_util.BasicAuth{
202Username: "user",
203Password: "password123",
204},
205}
206c, err := config_util.NewClientFromConfig(cfg, "test")
207require.NoError(t, err)
208_, err = c.Get(server.URL)
209require.NoError(t, err)
210}
211
212func TestNewHTTPCACert(t *testing.T) {
213server := httptest.NewUnstartedServer(
214http.HandlerFunc(
215func(w http.ResponseWriter, r *http.Request) {
216w.Header().Set("Content-Type", `text/plain; version=0.0.4`)
217w.Write([]byte{})
218},
219),
220)
221server.TLS = newTLSConfig("server", t)
222server.StartTLS()
223defer server.Close()
224
225cfg := config_util.HTTPClientConfig{
226TLSConfig: config_util.TLSConfig{
227CAFile: caCertPath,
228},
229}
230c, err := config_util.NewClientFromConfig(cfg, "test")
231require.NoError(t, err)
232_, err = c.Get(server.URL)
233require.NoError(t, err)
234}
235
236func TestNewHTTPClientCert(t *testing.T) {
237server := httptest.NewUnstartedServer(
238http.HandlerFunc(
239func(w http.ResponseWriter, r *http.Request) {
240w.Header().Set("Content-Type", `text/plain; version=0.0.4`)
241w.Write([]byte{})
242},
243),
244)
245tlsConfig := newTLSConfig("server", t)
246tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
247tlsConfig.ClientCAs = tlsConfig.RootCAs
248server.TLS = tlsConfig
249server.StartTLS()
250defer server.Close()
251
252cfg := config_util.HTTPClientConfig{
253TLSConfig: config_util.TLSConfig{
254CAFile: caCertPath,
255CertFile: "testdata/client.cer",
256KeyFile: "testdata/client.key",
257},
258}
259c, err := config_util.NewClientFromConfig(cfg, "test")
260require.NoError(t, err)
261_, err = c.Get(server.URL)
262require.NoError(t, err)
263}
264
265func TestNewHTTPWithServerName(t *testing.T) {
266server := httptest.NewUnstartedServer(
267http.HandlerFunc(
268func(w http.ResponseWriter, r *http.Request) {
269w.Header().Set("Content-Type", `text/plain; version=0.0.4`)
270w.Write([]byte{})
271},
272),
273)
274server.TLS = newTLSConfig("servername", t)
275server.StartTLS()
276defer server.Close()
277
278cfg := config_util.HTTPClientConfig{
279TLSConfig: config_util.TLSConfig{
280CAFile: caCertPath,
281ServerName: "prometheus.rocks",
282},
283}
284c, err := config_util.NewClientFromConfig(cfg, "test")
285require.NoError(t, err)
286_, err = c.Get(server.URL)
287require.NoError(t, err)
288}
289
290func TestNewHTTPWithBadServerName(t *testing.T) {
291server := httptest.NewUnstartedServer(
292http.HandlerFunc(
293func(w http.ResponseWriter, r *http.Request) {
294w.Header().Set("Content-Type", `text/plain; version=0.0.4`)
295w.Write([]byte{})
296},
297),
298)
299server.TLS = newTLSConfig("servername", t)
300server.StartTLS()
301defer server.Close()
302
303cfg := config_util.HTTPClientConfig{
304TLSConfig: config_util.TLSConfig{
305CAFile: caCertPath,
306ServerName: "badname",
307},
308}
309c, err := config_util.NewClientFromConfig(cfg, "test")
310require.NoError(t, err)
311_, err = c.Get(server.URL)
312require.Error(t, err)
313}
314
315func newTLSConfig(certName string, t *testing.T) *tls.Config {
316tlsConfig := &tls.Config{}
317caCertPool := x509.NewCertPool()
318caCert, err := os.ReadFile(caCertPath)
319require.NoError(t, err, "Couldn't read CA cert.")
320caCertPool.AppendCertsFromPEM(caCert)
321tlsConfig.RootCAs = caCertPool
322tlsConfig.ServerName = "127.0.0.1"
323certPath := fmt.Sprintf("testdata/%s.cer", certName)
324keyPath := fmt.Sprintf("testdata/%s.key", certName)
325cert, err := tls.LoadX509KeyPair(certPath, keyPath)
326require.NoError(t, err, "Unable to use specified server cert (%s) & key (%v).", certPath, keyPath)
327tlsConfig.Certificates = []tls.Certificate{cert}
328return tlsConfig
329}
330
331func TestNewClientWithBadTLSConfig(t *testing.T) {
332cfg := config_util.HTTPClientConfig{
333TLSConfig: config_util.TLSConfig{
334CAFile: "testdata/nonexistent_ca.cer",
335CertFile: "testdata/nonexistent_client.cer",
336KeyFile: "testdata/nonexistent_client.key",
337},
338}
339_, err := config_util.NewClientFromConfig(cfg, "test")
340require.Error(t, err)
341}
342
343func TestTargetsFromGroup(t *testing.T) {
344expectedError := "instance 0 in group : no address"
345
346cfg := config.ScrapeConfig{
347ScrapeTimeout: model.Duration(10 * time.Second),
348ScrapeInterval: model.Duration(1 * time.Minute),
349}
350lb := labels.NewBuilder(labels.EmptyLabels())
351targets, failures := TargetsFromGroup(&targetgroup.Group{Targets: []model.LabelSet{{}, {model.AddressLabel: "localhost:9090"}}}, &cfg, false, nil, lb)
352require.Len(t, targets, 1)
353require.Len(t, failures, 1)
354require.EqualError(t, failures[0], expectedError)
355}
356
357func BenchmarkTargetsFromGroup(b *testing.B) {
358// Simulate Kubernetes service-discovery and use subset of rules from typical Prometheus config.
359cfgText := `
360scrape_configs:
361- job_name: job1
362scrape_interval: 15s
363scrape_timeout: 10s
364relabel_configs:
365- source_labels: [__meta_kubernetes_pod_container_port_name]
366separator: ;
367regex: .*-metrics
368replacement: $1
369action: keep
370- source_labels: [__meta_kubernetes_pod_phase]
371separator: ;
372regex: Succeeded|Failed
373replacement: $1
374action: drop
375- source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_pod_label_name]
376separator: /
377regex: (.*)
378target_label: job
379replacement: $1
380action: replace
381- source_labels: [__meta_kubernetes_namespace]
382separator: ;
383regex: (.*)
384target_label: namespace
385replacement: $1
386action: replace
387- source_labels: [__meta_kubernetes_pod_name]
388separator: ;
389regex: (.*)
390target_label: pod
391replacement: $1
392action: replace
393- source_labels: [__meta_kubernetes_pod_container_name]
394separator: ;
395regex: (.*)
396target_label: container
397replacement: $1
398action: replace
399- source_labels: [__meta_kubernetes_pod_name, __meta_kubernetes_pod_container_name,
400__meta_kubernetes_pod_container_port_name]
401separator: ':'
402regex: (.*)
403target_label: instance
404replacement: $1
405action: replace
406- separator: ;
407regex: (.*)
408target_label: cluster
409replacement: dev-us-central-0
410action: replace
411`
412config := loadConfiguration(b, cfgText)
413for _, nTargets := range []int{1, 10, 100} {
414b.Run(fmt.Sprintf("%d_targets", nTargets), func(b *testing.B) {
415targets := []model.LabelSet{}
416for i := 0; i < nTargets; i++ {
417labels := model.LabelSet{
418model.AddressLabel: model.LabelValue(fmt.Sprintf("localhost:%d", i)),
419"__meta_kubernetes_namespace": "some_namespace",
420"__meta_kubernetes_pod_container_name": "some_container",
421"__meta_kubernetes_pod_container_port_name": "http-metrics",
422"__meta_kubernetes_pod_container_port_number": "80",
423"__meta_kubernetes_pod_label_name": "some_name",
424"__meta_kubernetes_pod_name": "some_pod",
425"__meta_kubernetes_pod_phase": "Running",
426}
427// Add some more labels, because Kubernetes SD generates a lot
428for i := 0; i < 10; i++ {
429labels[model.LabelName(fmt.Sprintf("__meta_kubernetes_pod_label_extra%d", i))] = "a_label_abcdefgh"
430labels[model.LabelName(fmt.Sprintf("__meta_kubernetes_pod_labelpresent_extra%d", i))] = "true"
431}
432targets = append(targets, labels)
433}
434var tgets []*Target
435lb := labels.NewBuilder(labels.EmptyLabels())
436group := &targetgroup.Group{Targets: targets}
437for i := 0; i < b.N; i++ {
438tgets, _ = TargetsFromGroup(group, config.ScrapeConfigs[0], false, tgets, lb)
439if len(targets) != nTargets {
440b.Fatalf("Expected %d targets, got %d", nTargets, len(targets))
441}
442}
443})
444}
445}
446
447func TestBucketLimitAppender(t *testing.T) {
448example := histogram.Histogram{
449Schema: 0,
450Count: 21,
451Sum: 33,
452ZeroThreshold: 0.001,
453ZeroCount: 3,
454PositiveSpans: []histogram.Span{
455{Offset: 0, Length: 3},
456},
457PositiveBuckets: []int64{3, 0, 0},
458NegativeSpans: []histogram.Span{
459{Offset: 0, Length: 3},
460},
461NegativeBuckets: []int64{3, 0, 0},
462}
463
464bigGap := histogram.Histogram{
465Schema: 0,
466Count: 21,
467Sum: 33,
468ZeroThreshold: 0.001,
469ZeroCount: 3,
470PositiveSpans: []histogram.Span{
471{Offset: 1, Length: 1}, // in (1, 2]
472{Offset: 2, Length: 1}, // in (8, 16]
473},
474PositiveBuckets: []int64{1, 0}, // 1, 1
475}
476
477cases := []struct {
478h histogram.Histogram
479limit int
480expectError bool
481expectBucketCount int
482expectSchema int32
483}{
484{
485h: example,
486limit: 3,
487expectError: true,
488},
489{
490h: example,
491limit: 4,
492expectError: false,
493expectBucketCount: 4,
494expectSchema: -1,
495},
496{
497h: example,
498limit: 10,
499expectError: false,
500expectBucketCount: 6,
501expectSchema: 0,
502},
503{
504h: bigGap,
505limit: 1,
506expectError: false,
507expectBucketCount: 1,
508expectSchema: -2,
509},
510}
511
512resApp := &collectResultAppender{}
513
514for _, c := range cases {
515for _, floatHisto := range []bool{true, false} {
516t.Run(fmt.Sprintf("floatHistogram=%t", floatHisto), func(t *testing.T) {
517app := &bucketLimitAppender{Appender: resApp, limit: c.limit}
518ts := int64(10 * time.Minute / time.Millisecond)
519lbls := labels.FromStrings("__name__", "sparse_histogram_series")
520var err error
521if floatHisto {
522fh := c.h.Copy().ToFloat(nil)
523_, err = app.AppendHistogram(0, lbls, ts, nil, fh)
524if c.expectError {
525require.Error(t, err)
526} else {
527require.Equal(t, c.expectSchema, fh.Schema)
528require.Equal(t, c.expectBucketCount, len(fh.NegativeBuckets)+len(fh.PositiveBuckets))
529require.NoError(t, err)
530}
531} else {
532h := c.h.Copy()
533_, err = app.AppendHistogram(0, lbls, ts, h, nil)
534if c.expectError {
535require.Error(t, err)
536} else {
537require.Equal(t, c.expectSchema, h.Schema)
538require.Equal(t, c.expectBucketCount, len(h.NegativeBuckets)+len(h.PositiveBuckets))
539require.NoError(t, err)
540}
541}
542require.NoError(t, app.Commit())
543})
544}
545}
546}
547
548func TestMaxSchemaAppender(t *testing.T) {
549example := histogram.Histogram{
550Schema: 0,
551Count: 21,
552Sum: 33,
553ZeroThreshold: 0.001,
554ZeroCount: 3,
555PositiveSpans: []histogram.Span{
556{Offset: 0, Length: 3},
557},
558PositiveBuckets: []int64{3, 0, 0},
559NegativeSpans: []histogram.Span{
560{Offset: 0, Length: 3},
561},
562NegativeBuckets: []int64{3, 0, 0},
563}
564
565cases := []struct {
566h histogram.Histogram
567maxSchema int32
568expectSchema int32
569}{
570{
571h: example,
572maxSchema: -1,
573expectSchema: -1,
574},
575{
576h: example,
577maxSchema: 0,
578expectSchema: 0,
579},
580}
581
582resApp := &collectResultAppender{}
583
584for _, c := range cases {
585for _, floatHisto := range []bool{true, false} {
586t.Run(fmt.Sprintf("floatHistogram=%t", floatHisto), func(t *testing.T) {
587app := &maxSchemaAppender{Appender: resApp, maxSchema: c.maxSchema}
588ts := int64(10 * time.Minute / time.Millisecond)
589lbls := labels.FromStrings("__name__", "sparse_histogram_series")
590var err error
591if floatHisto {
592fh := c.h.Copy().ToFloat(nil)
593_, err = app.AppendHistogram(0, lbls, ts, nil, fh)
594require.Equal(t, c.expectSchema, fh.Schema)
595require.NoError(t, err)
596} else {
597h := c.h.Copy()
598_, err = app.AppendHistogram(0, lbls, ts, h, nil)
599require.Equal(t, c.expectSchema, h.Schema)
600require.NoError(t, err)
601}
602require.NoError(t, app.Commit())
603})
604}
605}
606}
607