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	"io/ioutil"
21	"net/http"
22	"net/http/httptest"
23	"net/url"
24	"strings"
25	"testing"
26	"time"
27
28	config_util "github.com/prometheus/common/config"
29	"github.com/prometheus/common/model"
30	"github.com/stretchr/testify/require"
31
32	"github.com/prometheus/prometheus/config"
33	"github.com/prometheus/prometheus/discovery/targetgroup"
34	"github.com/prometheus/prometheus/pkg/labels"
35)
36
37const (
38	caCertPath = "testdata/ca.cer"
39)
40
41func TestTargetLabels(t *testing.T) {
42	target := newTestTarget("example.com:80", 0, labels.FromStrings("job", "some_job", "foo", "bar"))
43	want := labels.FromStrings(model.JobLabel, "some_job", "foo", "bar")
44	got := target.Labels()
45	require.Equal(t, want, got)
46}
47
48func TestTargetOffset(t *testing.T) {
49	interval := 10 * time.Second
50	jitter := uint64(0)
51
52	offsets := make([]time.Duration, 10000)
53
54	// Calculate offsets for 10000 different targets.
55	for i := range offsets {
56		target := newTestTarget("example.com:80", 0, labels.FromStrings(
57			"label", fmt.Sprintf("%d", i),
58		))
59		offsets[i] = target.offset(interval, jitter)
60	}
61
62	// Put the offsets into buckets and validate that they are all
63	// within bounds.
64	bucketSize := 1 * time.Second
65	buckets := make([]int, interval/bucketSize)
66
67	for _, offset := range offsets {
68		if offset < 0 || offset >= interval {
69			t.Fatalf("Offset %v out of bounds", offset)
70		}
71
72		bucket := offset / bucketSize
73		buckets[bucket]++
74	}
75
76	t.Log(buckets)
77
78	// Calculate whether the number of targets per bucket
79	// does not differ more than a given tolerance.
80	avg := len(offsets) / len(buckets)
81	tolerance := 0.15
82
83	for _, bucket := range buckets {
84		diff := bucket - avg
85		if diff < 0 {
86			diff = -diff
87		}
88
89		if float64(diff)/float64(avg) > tolerance {
90			t.Fatalf("Bucket out of tolerance bounds")
91		}
92	}
93}
94
95func TestTargetURL(t *testing.T) {
96	params := url.Values{
97		"abc": []string{"foo", "bar", "baz"},
98		"xyz": []string{"hoo"},
99	}
100	labels := labels.FromMap(map[string]string{
101		model.AddressLabel:     "example.com:1234",
102		model.SchemeLabel:      "https",
103		model.MetricsPathLabel: "/metricz",
104		"__param_abc":          "overwrite",
105		"__param_cde":          "huu",
106	})
107	target := NewTarget(labels, labels, params)
108
109	// The reserved labels are concatenated into a full URL. The first value for each
110	// URL query parameter can be set/modified via labels as well.
111	expectedParams := url.Values{
112		"abc": []string{"overwrite", "bar", "baz"},
113		"cde": []string{"huu"},
114		"xyz": []string{"hoo"},
115	}
116	expectedURL := &url.URL{
117		Scheme:   "https",
118		Host:     "example.com:1234",
119		Path:     "/metricz",
120		RawQuery: expectedParams.Encode(),
121	}
122
123	require.Equal(t, expectedURL, target.URL())
124}
125
126func newTestTarget(targetURL string, deadline time.Duration, lbls labels.Labels) *Target {
127	lb := labels.NewBuilder(lbls)
128	lb.Set(model.SchemeLabel, "http")
129	lb.Set(model.AddressLabel, strings.TrimPrefix(targetURL, "http://"))
130	lb.Set(model.MetricsPathLabel, "/metrics")
131
132	return &Target{labels: lb.Labels()}
133}
134
135func TestNewHTTPBearerToken(t *testing.T) {
136	server := httptest.NewServer(
137		http.HandlerFunc(
138			func(w http.ResponseWriter, r *http.Request) {
139				expected := "Bearer 1234"
140				received := r.Header.Get("Authorization")
141				if expected != received {
142					t.Fatalf("Authorization header was not set correctly: expected '%v', got '%v'", expected, received)
143				}
144			},
145		),
146	)
147	defer server.Close()
148
149	cfg := config_util.HTTPClientConfig{
150		BearerToken: "1234",
151	}
152	c, err := config_util.NewClientFromConfig(cfg, "test")
153	if err != nil {
154		t.Fatal(err)
155	}
156	_, err = c.Get(server.URL)
157	if err != nil {
158		t.Fatal(err)
159	}
160}
161
162func TestNewHTTPBearerTokenFile(t *testing.T) {
163	server := httptest.NewServer(
164		http.HandlerFunc(
165			func(w http.ResponseWriter, r *http.Request) {
166				expected := "Bearer 12345"
167				received := r.Header.Get("Authorization")
168				if expected != received {
169					t.Fatalf("Authorization header was not set correctly: expected '%v', got '%v'", expected, received)
170				}
171			},
172		),
173	)
174	defer server.Close()
175
176	cfg := config_util.HTTPClientConfig{
177		BearerTokenFile: "testdata/bearertoken.txt",
178	}
179	c, err := config_util.NewClientFromConfig(cfg, "test")
180	if err != nil {
181		t.Fatal(err)
182	}
183	_, err = c.Get(server.URL)
184	if err != nil {
185		t.Fatal(err)
186	}
187}
188
189func TestNewHTTPBasicAuth(t *testing.T) {
190	server := httptest.NewServer(
191		http.HandlerFunc(
192			func(w http.ResponseWriter, r *http.Request) {
193				username, password, ok := r.BasicAuth()
194				if !(ok && username == "user" && password == "password123") {
195					t.Fatalf("Basic authorization header was not set correctly: expected '%v:%v', got '%v:%v'", "user", "password123", username, password)
196				}
197			},
198		),
199	)
200	defer server.Close()
201
202	cfg := config_util.HTTPClientConfig{
203		BasicAuth: &config_util.BasicAuth{
204			Username: "user",
205			Password: "password123",
206		},
207	}
208	c, err := config_util.NewClientFromConfig(cfg, "test")
209	if err != nil {
210		t.Fatal(err)
211	}
212	_, err = c.Get(server.URL)
213	if err != nil {
214		t.Fatal(err)
215	}
216}
217
218func TestNewHTTPCACert(t *testing.T) {
219	server := httptest.NewUnstartedServer(
220		http.HandlerFunc(
221			func(w http.ResponseWriter, r *http.Request) {
222				w.Header().Set("Content-Type", `text/plain; version=0.0.4`)
223				w.Write([]byte{})
224			},
225		),
226	)
227	server.TLS = newTLSConfig("server", t)
228	server.StartTLS()
229	defer server.Close()
230
231	cfg := config_util.HTTPClientConfig{
232		TLSConfig: config_util.TLSConfig{
233			CAFile: caCertPath,
234		},
235	}
236	c, err := config_util.NewClientFromConfig(cfg, "test")
237	if err != nil {
238		t.Fatal(err)
239	}
240	_, err = c.Get(server.URL)
241	if err != nil {
242		t.Fatal(err)
243	}
244}
245
246func TestNewHTTPClientCert(t *testing.T) {
247	server := httptest.NewUnstartedServer(
248		http.HandlerFunc(
249			func(w http.ResponseWriter, r *http.Request) {
250				w.Header().Set("Content-Type", `text/plain; version=0.0.4`)
251				w.Write([]byte{})
252			},
253		),
254	)
255	tlsConfig := newTLSConfig("server", t)
256	tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
257	tlsConfig.ClientCAs = tlsConfig.RootCAs
258	server.TLS = tlsConfig
259	server.StartTLS()
260	defer server.Close()
261
262	cfg := config_util.HTTPClientConfig{
263		TLSConfig: config_util.TLSConfig{
264			CAFile:   caCertPath,
265			CertFile: "testdata/client.cer",
266			KeyFile:  "testdata/client.key",
267		},
268	}
269	c, err := config_util.NewClientFromConfig(cfg, "test")
270	if err != nil {
271		t.Fatal(err)
272	}
273	_, err = c.Get(server.URL)
274	if err != nil {
275		t.Fatal(err)
276	}
277}
278
279func TestNewHTTPWithServerName(t *testing.T) {
280	server := httptest.NewUnstartedServer(
281		http.HandlerFunc(
282			func(w http.ResponseWriter, r *http.Request) {
283				w.Header().Set("Content-Type", `text/plain; version=0.0.4`)
284				w.Write([]byte{})
285			},
286		),
287	)
288	server.TLS = newTLSConfig("servername", t)
289	server.StartTLS()
290	defer server.Close()
291
292	cfg := config_util.HTTPClientConfig{
293		TLSConfig: config_util.TLSConfig{
294			CAFile:     caCertPath,
295			ServerName: "prometheus.rocks",
296		},
297	}
298	c, err := config_util.NewClientFromConfig(cfg, "test")
299	if err != nil {
300		t.Fatal(err)
301	}
302	_, err = c.Get(server.URL)
303	if err != nil {
304		t.Fatal(err)
305	}
306}
307
308func TestNewHTTPWithBadServerName(t *testing.T) {
309	server := httptest.NewUnstartedServer(
310		http.HandlerFunc(
311			func(w http.ResponseWriter, r *http.Request) {
312				w.Header().Set("Content-Type", `text/plain; version=0.0.4`)
313				w.Write([]byte{})
314			},
315		),
316	)
317	server.TLS = newTLSConfig("servername", t)
318	server.StartTLS()
319	defer server.Close()
320
321	cfg := config_util.HTTPClientConfig{
322		TLSConfig: config_util.TLSConfig{
323			CAFile:     caCertPath,
324			ServerName: "badname",
325		},
326	}
327	c, err := config_util.NewClientFromConfig(cfg, "test")
328	if err != nil {
329		t.Fatal(err)
330	}
331	_, err = c.Get(server.URL)
332	if err == nil {
333		t.Fatal("Expected error, got nil.")
334	}
335}
336
337func newTLSConfig(certName string, t *testing.T) *tls.Config {
338	tlsConfig := &tls.Config{}
339	caCertPool := x509.NewCertPool()
340	caCert, err := ioutil.ReadFile(caCertPath)
341	if err != nil {
342		t.Fatalf("Couldn't set up TLS server: %v", err)
343	}
344	caCertPool.AppendCertsFromPEM(caCert)
345	tlsConfig.RootCAs = caCertPool
346	tlsConfig.ServerName = "127.0.0.1"
347	certPath := fmt.Sprintf("testdata/%s.cer", certName)
348	keyPath := fmt.Sprintf("testdata/%s.key", certName)
349	cert, err := tls.LoadX509KeyPair(certPath, keyPath)
350	if err != nil {
351		t.Errorf("Unable to use specified server cert (%s) & key (%v): %s", certPath, keyPath, err)
352	}
353	tlsConfig.Certificates = []tls.Certificate{cert}
354	return tlsConfig
355}
356
357func TestNewClientWithBadTLSConfig(t *testing.T) {
358	cfg := config_util.HTTPClientConfig{
359		TLSConfig: config_util.TLSConfig{
360			CAFile:   "testdata/nonexistent_ca.cer",
361			CertFile: "testdata/nonexistent_client.cer",
362			KeyFile:  "testdata/nonexistent_client.key",
363		},
364	}
365	_, err := config_util.NewClientFromConfig(cfg, "test")
366	if err == nil {
367		t.Fatalf("Expected error, got nil.")
368	}
369}
370
371func TestTargetsFromGroup(t *testing.T) {
372	expectedError := "instance 0 in group : no address"
373
374	targets, failures := targetsFromGroup(&targetgroup.Group{Targets: []model.LabelSet{{}, {model.AddressLabel: "localhost:9090"}}}, &config.ScrapeConfig{})
375	if len(targets) != 1 {
376		t.Fatalf("Expected 1 target, got %v", len(targets))
377	}
378	if len(failures) != 1 {
379		t.Fatalf("Expected 1 failure, got %v", len(failures))
380	}
381	if failures[0].Error() != expectedError {
382		t.Fatalf("Expected error %s, got %s", expectedError, failures[0])
383	}
384}
385
386func TestTargetHash(t *testing.T) {
387	target1 := &Target{
388		labels: labels.Labels{
389			{Name: model.AddressLabel, Value: "localhost"},
390			{Name: model.SchemeLabel, Value: "http"},
391			{Name: model.MetricsPathLabel, Value: "/metrics"},
392			{Name: model.ScrapeIntervalLabel, Value: "15s"},
393			{Name: model.ScrapeTimeoutLabel, Value: "500ms"},
394		},
395	}
396	hash1 := target1.hash()
397
398	target2 := &Target{
399		labels: labels.Labels{
400			{Name: model.AddressLabel, Value: "localhost"},
401			{Name: model.SchemeLabel, Value: "http"},
402			{Name: model.MetricsPathLabel, Value: "/metrics"},
403			{Name: model.ScrapeIntervalLabel, Value: "14s"},
404			{Name: model.ScrapeTimeoutLabel, Value: "600ms"},
405		},
406	}
407	hash2 := target2.hash()
408
409	require.Equal(t, hash1, hash2, "Scrape interval and duration labels should not effect hash.")
410}
411