1// Copyright 2016 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
14// Copyright (c) 2013, The Prometheus Authors
15// All rights reserved.
16//
17// Use of this source code is governed by a BSD-style license that can be found
18// in the LICENSE file.
19
20package push
21
22import (
23	"bytes"
24	"io/ioutil"
25	"net/http"
26	"net/http/httptest"
27	"os"
28	"testing"
29
30	"github.com/prometheus/common/expfmt"
31
32	"github.com/prometheus/client_golang/prometheus"
33)
34
35func TestPush(t *testing.T) {
36
37	var (
38		lastMethod string
39		lastBody   []byte
40		lastPath   string
41	)
42
43	host, err := os.Hostname()
44	if err != nil {
45		t.Error(err)
46	}
47
48	// Fake a Pushgateway that always responds with 202.
49	pgwOK := httptest.NewServer(
50		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
51			lastMethod = r.Method
52			var err error
53			lastBody, err = ioutil.ReadAll(r.Body)
54			if err != nil {
55				t.Fatal(err)
56			}
57			lastPath = r.URL.EscapedPath()
58			w.Header().Set("Content-Type", `text/plain; charset=utf-8`)
59			w.WriteHeader(http.StatusAccepted)
60		}),
61	)
62	defer pgwOK.Close()
63
64	// Fake a Pushgateway that always responds with 500.
65	pgwErr := httptest.NewServer(
66		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
67			http.Error(w, "fake error", http.StatusInternalServerError)
68		}),
69	)
70	defer pgwErr.Close()
71
72	metric1 := prometheus.NewCounter(prometheus.CounterOpts{
73		Name: "testname1",
74		Help: "testhelp1",
75	})
76	metric2 := prometheus.NewGauge(prometheus.GaugeOpts{
77		Name:        "testname2",
78		Help:        "testhelp2",
79		ConstLabels: prometheus.Labels{"foo": "bar", "dings": "bums"},
80	})
81
82	reg := prometheus.NewRegistry()
83	reg.MustRegister(metric1)
84	reg.MustRegister(metric2)
85
86	mfs, err := reg.Gather()
87	if err != nil {
88		t.Fatal(err)
89	}
90
91	buf := &bytes.Buffer{}
92	enc := expfmt.NewEncoder(buf, expfmt.FmtProtoDelim)
93
94	for _, mf := range mfs {
95		if err := enc.Encode(mf); err != nil {
96			t.Fatal(err)
97		}
98	}
99	wantBody := buf.Bytes()
100
101	// PushCollectors, all good.
102	if err := Collectors("testjob", HostnameGroupingKey(), pgwOK.URL, metric1, metric2); err != nil {
103		t.Fatal(err)
104	}
105	if lastMethod != "PUT" {
106		t.Error("want method PUT for PushCollectors, got", lastMethod)
107	}
108	if bytes.Compare(lastBody, wantBody) != 0 {
109		t.Errorf("got body %v, want %v", lastBody, wantBody)
110	}
111	if lastPath != "/metrics/job/testjob/instance/"+host {
112		t.Error("unexpected path:", lastPath)
113	}
114
115	// PushAddCollectors, with nil grouping, all good.
116	if err := AddCollectors("testjob", nil, pgwOK.URL, metric1, metric2); err != nil {
117		t.Fatal(err)
118	}
119	if lastMethod != "POST" {
120		t.Error("want method POST for PushAddCollectors, got", lastMethod)
121	}
122	if bytes.Compare(lastBody, wantBody) != 0 {
123		t.Errorf("got body %v, want %v", lastBody, wantBody)
124	}
125	if lastPath != "/metrics/job/testjob" {
126		t.Error("unexpected path:", lastPath)
127	}
128
129	// PushCollectors with a broken PGW.
130	if err := Collectors("testjob", nil, pgwErr.URL, metric1, metric2); err == nil {
131		t.Error("push to broken Pushgateway succeeded")
132	} else {
133		if got, want := err.Error(), "unexpected status code 500 while pushing to "+pgwErr.URL+"/metrics/job/testjob: fake error\n"; got != want {
134			t.Errorf("got error %q, want %q", got, want)
135		}
136	}
137
138	// PushCollectors with invalid grouping or job.
139	if err := Collectors("testjob", map[string]string{"foo": "bums"}, pgwErr.URL, metric1, metric2); err == nil {
140		t.Error("push with grouping contained in metrics succeeded")
141	}
142	if err := Collectors("test/job", nil, pgwErr.URL, metric1, metric2); err == nil {
143		t.Error("push with invalid job value succeeded")
144	}
145	if err := Collectors("testjob", map[string]string{"foo/bar": "bums"}, pgwErr.URL, metric1, metric2); err == nil {
146		t.Error("push with invalid grouping succeeded")
147	}
148	if err := Collectors("testjob", map[string]string{"foo-bar": "bums"}, pgwErr.URL, metric1, metric2); err == nil {
149		t.Error("push with invalid grouping succeeded")
150	}
151
152	// Push registry, all good.
153	if err := FromGatherer("testjob", HostnameGroupingKey(), pgwOK.URL, reg); err != nil {
154		t.Fatal(err)
155	}
156	if lastMethod != "PUT" {
157		t.Error("want method PUT for Push, got", lastMethod)
158	}
159	if bytes.Compare(lastBody, wantBody) != 0 {
160		t.Errorf("got body %v, want %v", lastBody, wantBody)
161	}
162
163	// PushAdd registry, all good.
164	if err := AddFromGatherer("testjob", map[string]string{"a": "x", "b": "y"}, pgwOK.URL, reg); err != nil {
165		t.Fatal(err)
166	}
167	if lastMethod != "POST" {
168		t.Error("want method POSTT for PushAdd, got", lastMethod)
169	}
170	if bytes.Compare(lastBody, wantBody) != 0 {
171		t.Errorf("got body %v, want %v", lastBody, wantBody)
172	}
173	if lastPath != "/metrics/job/testjob/a/x/b/y" && lastPath != "/metrics/job/testjob/b/y/a/x" {
174		t.Error("unexpected path:", lastPath)
175	}
176}
177