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 web 15 16import ( 17 "bytes" 18 "net/http/httptest" 19 "sort" 20 "strings" 21 "testing" 22 23 "github.com/prometheus/common/model" 24 "github.com/prometheus/prometheus/config" 25 "github.com/prometheus/prometheus/pkg/labels" 26 "github.com/prometheus/prometheus/promql" 27) 28 29var scenarios = map[string]struct { 30 params string 31 externalLabels labels.Labels 32 code int 33 body string 34}{ 35 "empty": { 36 params: "", 37 code: 200, 38 body: ``, 39 }, 40 "match nothing": { 41 params: "match[]=does_not_match_anything", 42 code: 200, 43 body: ``, 44 }, 45 "invalid params from the beginning": { 46 params: "match[]=-not-a-valid-metric-name", 47 code: 400, 48 body: `1:1: parse error: unexpected <op:-> 49`, 50 }, 51 "invalid params somewhere in the middle": { 52 params: "match[]=not-a-valid-metric-name", 53 code: 400, 54 body: `1:4: parse error: unexpected <op:-> 55`, 56 }, 57 "test_metric1": { 58 params: "match[]=test_metric1", 59 code: 200, 60 body: `# TYPE test_metric1 untyped 61test_metric1{foo="bar",instance="i"} 10000 6000000 62test_metric1{foo="boo",instance="i"} 1 6000000 63`, 64 }, 65 "test_metric2": { 66 params: "match[]=test_metric2", 67 code: 200, 68 body: `# TYPE test_metric2 untyped 69test_metric2{foo="boo",instance="i"} 1 6000000 70`, 71 }, 72 "test_metric_without_labels": { 73 params: "match[]=test_metric_without_labels", 74 code: 200, 75 body: `# TYPE test_metric_without_labels untyped 76test_metric_without_labels{instance=""} 1001 6000000 77`, 78 }, 79 "test_stale_metric": { 80 params: "match[]=test_metric_stale", 81 code: 200, 82 body: ``, 83 }, 84 "test_old_metric": { 85 params: "match[]=test_metric_old", 86 code: 200, 87 body: `# TYPE test_metric_old untyped 88test_metric_old{instance=""} 981 5880000 89`, 90 }, 91 "{foo='boo'}": { 92 params: "match[]={foo='boo'}", 93 code: 200, 94 body: `# TYPE test_metric1 untyped 95test_metric1{foo="boo",instance="i"} 1 6000000 96# TYPE test_metric2 untyped 97test_metric2{foo="boo",instance="i"} 1 6000000 98`, 99 }, 100 "two matchers": { 101 params: "match[]=test_metric1&match[]=test_metric2", 102 code: 200, 103 body: `# TYPE test_metric1 untyped 104test_metric1{foo="bar",instance="i"} 10000 6000000 105test_metric1{foo="boo",instance="i"} 1 6000000 106# TYPE test_metric2 untyped 107test_metric2{foo="boo",instance="i"} 1 6000000 108`, 109 }, 110 "everything": { 111 params: "match[]={__name__=~'.%2b'}", // '%2b' is an URL-encoded '+'. 112 code: 200, 113 body: `# TYPE test_metric1 untyped 114test_metric1{foo="bar",instance="i"} 10000 6000000 115test_metric1{foo="boo",instance="i"} 1 6000000 116# TYPE test_metric2 untyped 117test_metric2{foo="boo",instance="i"} 1 6000000 118# TYPE test_metric_old untyped 119test_metric_old{instance=""} 981 5880000 120# TYPE test_metric_without_labels untyped 121test_metric_without_labels{instance=""} 1001 6000000 122`, 123 }, 124 "empty label value matches everything that doesn't have that label": { 125 params: "match[]={foo='',__name__=~'.%2b'}", 126 code: 200, 127 body: `# TYPE test_metric_old untyped 128test_metric_old{instance=""} 981 5880000 129# TYPE test_metric_without_labels untyped 130test_metric_without_labels{instance=""} 1001 6000000 131`, 132 }, 133 "empty label value for a label that doesn't exist at all, matches everything": { 134 params: "match[]={bar='',__name__=~'.%2b'}", 135 code: 200, 136 body: `# TYPE test_metric1 untyped 137test_metric1{foo="bar",instance="i"} 10000 6000000 138test_metric1{foo="boo",instance="i"} 1 6000000 139# TYPE test_metric2 untyped 140test_metric2{foo="boo",instance="i"} 1 6000000 141# TYPE test_metric_old untyped 142test_metric_old{instance=""} 981 5880000 143# TYPE test_metric_without_labels untyped 144test_metric_without_labels{instance=""} 1001 6000000 145`, 146 }, 147 "external labels are added if not already present": { 148 params: "match[]={__name__=~'.%2b'}", // '%2b' is an URL-encoded '+'. 149 externalLabels: labels.Labels{{Name: "zone", Value: "ie"}, {Name: "foo", Value: "baz"}}, 150 code: 200, 151 body: `# TYPE test_metric1 untyped 152test_metric1{foo="bar",instance="i",zone="ie"} 10000 6000000 153test_metric1{foo="boo",instance="i",zone="ie"} 1 6000000 154# TYPE test_metric2 untyped 155test_metric2{foo="boo",instance="i",zone="ie"} 1 6000000 156# TYPE test_metric_old untyped 157test_metric_old{foo="baz",instance="",zone="ie"} 981 5880000 158# TYPE test_metric_without_labels untyped 159test_metric_without_labels{foo="baz",instance="",zone="ie"} 1001 6000000 160`, 161 }, 162 "instance is an external label": { 163 // This makes no sense as a configuration, but we should 164 // know what it does anyway. 165 params: "match[]={__name__=~'.%2b'}", // '%2b' is an URL-encoded '+'. 166 externalLabels: labels.Labels{{Name: "instance", Value: "baz"}}, 167 code: 200, 168 body: `# TYPE test_metric1 untyped 169test_metric1{foo="bar",instance="i"} 10000 6000000 170test_metric1{foo="boo",instance="i"} 1 6000000 171# TYPE test_metric2 untyped 172test_metric2{foo="boo",instance="i"} 1 6000000 173# TYPE test_metric_old untyped 174test_metric_old{instance="baz"} 981 5880000 175# TYPE test_metric_without_labels untyped 176test_metric_without_labels{instance="baz"} 1001 6000000 177`, 178 }, 179} 180 181func TestFederation(t *testing.T) { 182 suite, err := promql.NewTest(t, ` 183 load 1m 184 test_metric1{foo="bar",instance="i"} 0+100x100 185 test_metric1{foo="boo",instance="i"} 1+0x100 186 test_metric2{foo="boo",instance="i"} 1+0x100 187 test_metric_without_labels 1+10x100 188 test_metric_stale 1+10x99 stale 189 test_metric_old 1+10x98 190 `) 191 if err != nil { 192 t.Fatal(err) 193 } 194 defer suite.Close() 195 196 if err := suite.Run(); err != nil { 197 t.Fatal(err) 198 } 199 200 h := &Handler{ 201 storage: suite.Storage(), 202 queryEngine: suite.QueryEngine(), 203 now: func() model.Time { return 101 * 60 * 1000 }, // 101min after epoch. 204 config: &config.Config{ 205 GlobalConfig: config.GlobalConfig{}, 206 }, 207 } 208 209 for name, scenario := range scenarios { 210 h.config.GlobalConfig.ExternalLabels = scenario.externalLabels 211 req := httptest.NewRequest("GET", "http://example.org/federate?"+scenario.params, nil) 212 res := httptest.NewRecorder() 213 h.federation(res, req) 214 if got, want := res.Code, scenario.code; got != want { 215 t.Errorf("Scenario %q: got code %d, want %d", name, got, want) 216 } 217 if got, want := normalizeBody(res.Body), scenario.body; got != want { 218 t.Errorf("Scenario %q: got body\n%s\n, want\n%s\n", name, got, want) 219 } 220 } 221} 222 223// normalizeBody sorts the lines within a metric to make it easy to verify the body. 224// (Federation is not taking care of sorting within a metric family.) 225func normalizeBody(body *bytes.Buffer) string { 226 var ( 227 lines []string 228 lastHash int 229 ) 230 for line, err := body.ReadString('\n'); err == nil; line, err = body.ReadString('\n') { 231 if line[0] == '#' && len(lines) > 0 { 232 sort.Strings(lines[lastHash+1:]) 233 lastHash = len(lines) 234 } 235 lines = append(lines, line) 236 } 237 if len(lines) > 0 { 238 sort.Strings(lines[lastHash+1:]) 239 } 240 return strings.Join(lines, "") 241} 242