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 14package push 15 16import ( 17 "bytes" 18 "io/ioutil" 19 "net/http" 20 "net/http/httptest" 21 "testing" 22 23 "github.com/prometheus/common/expfmt" 24 25 "github.com/prometheus/client_golang/prometheus" 26) 27 28func TestPush(t *testing.T) { 29 30 var ( 31 lastMethod string 32 lastBody []byte 33 lastPath string 34 ) 35 36 // Fake a Pushgateway that responds with 202 to DELETE and with 200 in 37 // all other cases. 38 pgwOK := httptest.NewServer( 39 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 40 lastMethod = r.Method 41 var err error 42 lastBody, err = ioutil.ReadAll(r.Body) 43 if err != nil { 44 t.Fatal(err) 45 } 46 lastPath = r.URL.EscapedPath() 47 w.Header().Set("Content-Type", `text/plain; charset=utf-8`) 48 if r.Method == http.MethodDelete { 49 w.WriteHeader(http.StatusAccepted) 50 return 51 } 52 w.WriteHeader(http.StatusOK) 53 }), 54 ) 55 defer pgwOK.Close() 56 57 // Fake a Pushgateway that always responds with 500. 58 pgwErr := httptest.NewServer( 59 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 60 http.Error(w, "fake error", http.StatusInternalServerError) 61 }), 62 ) 63 defer pgwErr.Close() 64 65 metric1 := prometheus.NewCounter(prometheus.CounterOpts{ 66 Name: "testname1", 67 Help: "testhelp1", 68 }) 69 metric2 := prometheus.NewGauge(prometheus.GaugeOpts{ 70 Name: "testname2", 71 Help: "testhelp2", 72 ConstLabels: prometheus.Labels{"foo": "bar", "dings": "bums"}, 73 }) 74 75 reg := prometheus.NewRegistry() 76 reg.MustRegister(metric1) 77 reg.MustRegister(metric2) 78 79 mfs, err := reg.Gather() 80 if err != nil { 81 t.Fatal(err) 82 } 83 84 buf := &bytes.Buffer{} 85 enc := expfmt.NewEncoder(buf, expfmt.FmtProtoDelim) 86 87 for _, mf := range mfs { 88 if err := enc.Encode(mf); err != nil { 89 t.Fatal(err) 90 } 91 } 92 wantBody := buf.Bytes() 93 94 // Push some Collectors, all good. 95 if err := New(pgwOK.URL, "testjob"). 96 Collector(metric1). 97 Collector(metric2). 98 Push(); err != nil { 99 t.Fatal(err) 100 } 101 if lastMethod != http.MethodPut { 102 t.Errorf("got method %q for Push, want %q", lastMethod, http.MethodPut) 103 } 104 if !bytes.Equal(lastBody, wantBody) { 105 t.Errorf("got body %v, want %v", lastBody, wantBody) 106 } 107 if lastPath != "/metrics/job/testjob" { 108 t.Error("unexpected path:", lastPath) 109 } 110 111 // Add some Collectors, with nil grouping, all good. 112 if err := New(pgwOK.URL, "testjob"). 113 Collector(metric1). 114 Collector(metric2). 115 Add(); err != nil { 116 t.Fatal(err) 117 } 118 if lastMethod != http.MethodPost { 119 t.Errorf("got method %q for Add, want %q", lastMethod, http.MethodPost) 120 } 121 if !bytes.Equal(lastBody, wantBody) { 122 t.Errorf("got body %v, want %v", lastBody, wantBody) 123 } 124 if lastPath != "/metrics/job/testjob" { 125 t.Error("unexpected path:", lastPath) 126 } 127 128 // Pushes that require base64 encoding. 129 if err := New(pgwOK.URL, "test/job"). 130 Collector(metric1). 131 Collector(metric2). 132 Push(); err != nil { 133 t.Fatal(err) 134 } 135 if lastMethod != http.MethodPut { 136 t.Errorf("got method %q for Push, want %q", lastMethod, http.MethodPut) 137 } 138 if !bytes.Equal(lastBody, wantBody) { 139 t.Errorf("got body %v, want %v", lastBody, wantBody) 140 } 141 if lastPath != "/metrics/job@base64/dGVzdC9qb2I" { 142 t.Error("unexpected path:", lastPath) 143 } 144 if err := New(pgwOK.URL, "testjob"). 145 Grouping("foobar", "bu/ms"). 146 Collector(metric1). 147 Collector(metric2). 148 Push(); err != nil { 149 t.Fatal(err) 150 } 151 if lastMethod != http.MethodPut { 152 t.Errorf("got method %q for Push, want %q", lastMethod, http.MethodPut) 153 } 154 if !bytes.Equal(lastBody, wantBody) { 155 t.Errorf("got body %v, want %v", lastBody, wantBody) 156 } 157 if lastPath != "/metrics/job/testjob/foobar@base64/YnUvbXM" { 158 t.Error("unexpected path:", lastPath) 159 } 160 161 // Push that requires URL encoding. 162 if err := New(pgwOK.URL, "testjob"). 163 Grouping("titan", "Προμηθεύς"). 164 Collector(metric1). 165 Collector(metric2). 166 Push(); err != nil { 167 t.Fatal(err) 168 } 169 if lastMethod != http.MethodPut { 170 t.Errorf("got method %q for Push, want %q", lastMethod, http.MethodPut) 171 } 172 if !bytes.Equal(lastBody, wantBody) { 173 t.Errorf("got body %v, want %v", lastBody, wantBody) 174 } 175 if lastPath != "/metrics/job/testjob/titan/%CE%A0%CF%81%CE%BF%CE%BC%CE%B7%CE%B8%CE%B5%CF%8D%CF%82" { 176 t.Error("unexpected path:", lastPath) 177 } 178 179 // Empty label value triggers special base64 encoding. 180 if err := New(pgwOK.URL, "testjob"). 181 Grouping("empty", ""). 182 Collector(metric1). 183 Collector(metric2). 184 Push(); err != nil { 185 t.Fatal(err) 186 } 187 if lastMethod != http.MethodPut { 188 t.Errorf("got method %q for Push, want %q", lastMethod, http.MethodPut) 189 } 190 if !bytes.Equal(lastBody, wantBody) { 191 t.Errorf("got body %v, want %v", lastBody, wantBody) 192 } 193 if lastPath != "/metrics/job/testjob/empty@base64/=" { 194 t.Error("unexpected path:", lastPath) 195 } 196 197 // Empty job name results in error. 198 if err := New(pgwErr.URL, ""). 199 Collector(metric1). 200 Collector(metric2). 201 Push(); err == nil { 202 t.Error("push with empty job succeded") 203 } else { 204 if got, want := err, errJobEmpty; got != want { 205 t.Errorf("got error %q, want %q", got, want) 206 } 207 } 208 209 // Push some Collectors with a broken PGW. 210 if err := New(pgwErr.URL, "testjob"). 211 Collector(metric1). 212 Collector(metric2). 213 Push(); err == nil { 214 t.Error("push to broken Pushgateway succeeded") 215 } else { 216 if got, want := err.Error(), "unexpected status code 500 while pushing to "+pgwErr.URL+"/metrics/job/testjob: fake error\n"; got != want { 217 t.Errorf("got error %q, want %q", got, want) 218 } 219 } 220 221 // Push some Collectors with invalid grouping or job. 222 if err := New(pgwOK.URL, "testjob"). 223 Grouping("foo", "bums"). 224 Collector(metric1). 225 Collector(metric2). 226 Push(); err == nil { 227 t.Error("push with grouping contained in metrics succeeded") 228 } 229 if err := New(pgwOK.URL, "testjob"). 230 Grouping("foo-bar", "bums"). 231 Collector(metric1). 232 Collector(metric2). 233 Push(); err == nil { 234 t.Error("push with invalid grouping succeeded") 235 } 236 237 // Push registry, all good. 238 if err := New(pgwOK.URL, "testjob"). 239 Gatherer(reg). 240 Push(); err != nil { 241 t.Fatal(err) 242 } 243 if lastMethod != http.MethodPut { 244 t.Errorf("got method %q for Push, want %q", lastMethod, http.MethodPut) 245 } 246 if !bytes.Equal(lastBody, wantBody) { 247 t.Errorf("got body %v, want %v", lastBody, wantBody) 248 } 249 250 // Add registry, all good. 251 if err := New(pgwOK.URL, "testjob"). 252 Grouping("a", "x"). 253 Grouping("b", "y"). 254 Gatherer(reg). 255 Add(); err != nil { 256 t.Fatal(err) 257 } 258 if lastMethod != http.MethodPost { 259 t.Errorf("got method %q for Add, want %q", lastMethod, http.MethodPost) 260 } 261 if !bytes.Equal(lastBody, wantBody) { 262 t.Errorf("got body %v, want %v", lastBody, wantBody) 263 } 264 if lastPath != "/metrics/job/testjob/a/x/b/y" && lastPath != "/metrics/job/testjob/b/y/a/x" { 265 t.Error("unexpected path:", lastPath) 266 } 267 268 // Delete, all good. 269 if err := New(pgwOK.URL, "testjob"). 270 Grouping("a", "x"). 271 Grouping("b", "y"). 272 Delete(); err != nil { 273 t.Fatal(err) 274 } 275 if lastMethod != http.MethodDelete { 276 t.Errorf("got method %q for Delete, want %q", lastMethod, http.MethodDelete) 277 } 278 if len(lastBody) != 0 { 279 t.Errorf("got body of length %d, want empty body", len(lastBody)) 280 } 281 if lastPath != "/metrics/job/testjob/a/x/b/y" && lastPath != "/metrics/job/testjob/b/y/a/x" { 282 t.Error("unexpected path:", lastPath) 283 } 284} 285