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 "net/http" 18 "strconv" 19 "testing" 20 "time" 21 22 "github.com/pkg/errors" 23 "github.com/prometheus/common/model" 24 yaml "gopkg.in/yaml.v2" 25 26 "github.com/prometheus/prometheus/config" 27 "github.com/prometheus/prometheus/discovery/targetgroup" 28 "github.com/prometheus/prometheus/pkg/labels" 29 "github.com/prometheus/prometheus/pkg/relabel" 30 "github.com/prometheus/prometheus/util/testutil" 31) 32 33func TestPopulateLabels(t *testing.T) { 34 cases := []struct { 35 in labels.Labels 36 cfg *config.ScrapeConfig 37 res labels.Labels 38 resOrig labels.Labels 39 err error 40 }{ 41 // Regular population of scrape config options. 42 { 43 in: labels.FromMap(map[string]string{ 44 model.AddressLabel: "1.2.3.4:1000", 45 "custom": "value", 46 }), 47 cfg: &config.ScrapeConfig{ 48 Scheme: "https", 49 MetricsPath: "/metrics", 50 JobName: "job", 51 }, 52 res: labels.FromMap(map[string]string{ 53 model.AddressLabel: "1.2.3.4:1000", 54 model.InstanceLabel: "1.2.3.4:1000", 55 model.SchemeLabel: "https", 56 model.MetricsPathLabel: "/metrics", 57 model.JobLabel: "job", 58 "custom": "value", 59 }), 60 resOrig: labels.FromMap(map[string]string{ 61 model.AddressLabel: "1.2.3.4:1000", 62 model.SchemeLabel: "https", 63 model.MetricsPathLabel: "/metrics", 64 model.JobLabel: "job", 65 "custom": "value", 66 }), 67 }, 68 // Pre-define/overwrite scrape config labels. 69 // Leave out port and expect it to be defaulted to scheme. 70 { 71 in: labels.FromMap(map[string]string{ 72 model.AddressLabel: "1.2.3.4", 73 model.SchemeLabel: "http", 74 model.MetricsPathLabel: "/custom", 75 model.JobLabel: "custom-job", 76 }), 77 cfg: &config.ScrapeConfig{ 78 Scheme: "https", 79 MetricsPath: "/metrics", 80 JobName: "job", 81 }, 82 res: labels.FromMap(map[string]string{ 83 model.AddressLabel: "1.2.3.4:80", 84 model.InstanceLabel: "1.2.3.4:80", 85 model.SchemeLabel: "http", 86 model.MetricsPathLabel: "/custom", 87 model.JobLabel: "custom-job", 88 }), 89 resOrig: labels.FromMap(map[string]string{ 90 model.AddressLabel: "1.2.3.4", 91 model.SchemeLabel: "http", 92 model.MetricsPathLabel: "/custom", 93 model.JobLabel: "custom-job", 94 }), 95 }, 96 // Provide instance label. HTTPS port default for IPv6. 97 { 98 in: labels.FromMap(map[string]string{ 99 model.AddressLabel: "[::1]", 100 model.InstanceLabel: "custom-instance", 101 }), 102 cfg: &config.ScrapeConfig{ 103 Scheme: "https", 104 MetricsPath: "/metrics", 105 JobName: "job", 106 }, 107 res: labels.FromMap(map[string]string{ 108 model.AddressLabel: "[::1]:443", 109 model.InstanceLabel: "custom-instance", 110 model.SchemeLabel: "https", 111 model.MetricsPathLabel: "/metrics", 112 model.JobLabel: "job", 113 }), 114 resOrig: labels.FromMap(map[string]string{ 115 model.AddressLabel: "[::1]", 116 model.InstanceLabel: "custom-instance", 117 model.SchemeLabel: "https", 118 model.MetricsPathLabel: "/metrics", 119 model.JobLabel: "job", 120 }), 121 }, 122 // Address label missing. 123 { 124 in: labels.FromStrings("custom", "value"), 125 cfg: &config.ScrapeConfig{ 126 Scheme: "https", 127 MetricsPath: "/metrics", 128 JobName: "job", 129 }, 130 res: nil, 131 resOrig: nil, 132 err: errors.New("no address"), 133 }, 134 // Address label missing, but added in relabelling. 135 { 136 in: labels.FromStrings("custom", "host:1234"), 137 cfg: &config.ScrapeConfig{ 138 Scheme: "https", 139 MetricsPath: "/metrics", 140 JobName: "job", 141 RelabelConfigs: []*relabel.Config{ 142 { 143 Action: relabel.Replace, 144 Regex: relabel.MustNewRegexp("(.*)"), 145 SourceLabels: model.LabelNames{"custom"}, 146 Replacement: "${1}", 147 TargetLabel: string(model.AddressLabel), 148 }, 149 }, 150 }, 151 res: labels.FromMap(map[string]string{ 152 model.AddressLabel: "host:1234", 153 model.InstanceLabel: "host:1234", 154 model.SchemeLabel: "https", 155 model.MetricsPathLabel: "/metrics", 156 model.JobLabel: "job", 157 "custom": "host:1234", 158 }), 159 resOrig: labels.FromMap(map[string]string{ 160 model.SchemeLabel: "https", 161 model.MetricsPathLabel: "/metrics", 162 model.JobLabel: "job", 163 "custom": "host:1234", 164 }), 165 }, 166 // Address label missing, but added in relabelling. 167 { 168 in: labels.FromStrings("custom", "host:1234"), 169 cfg: &config.ScrapeConfig{ 170 Scheme: "https", 171 MetricsPath: "/metrics", 172 JobName: "job", 173 RelabelConfigs: []*relabel.Config{ 174 { 175 Action: relabel.Replace, 176 Regex: relabel.MustNewRegexp("(.*)"), 177 SourceLabels: model.LabelNames{"custom"}, 178 Replacement: "${1}", 179 TargetLabel: string(model.AddressLabel), 180 }, 181 }, 182 }, 183 res: labels.FromMap(map[string]string{ 184 model.AddressLabel: "host:1234", 185 model.InstanceLabel: "host:1234", 186 model.SchemeLabel: "https", 187 model.MetricsPathLabel: "/metrics", 188 model.JobLabel: "job", 189 "custom": "host:1234", 190 }), 191 resOrig: labels.FromMap(map[string]string{ 192 model.SchemeLabel: "https", 193 model.MetricsPathLabel: "/metrics", 194 model.JobLabel: "job", 195 "custom": "host:1234", 196 }), 197 }, 198 // Invalid UTF-8 in label. 199 { 200 in: labels.FromMap(map[string]string{ 201 model.AddressLabel: "1.2.3.4:1000", 202 "custom": "\xbd", 203 }), 204 cfg: &config.ScrapeConfig{ 205 Scheme: "https", 206 MetricsPath: "/metrics", 207 JobName: "job", 208 }, 209 res: nil, 210 resOrig: nil, 211 err: errors.New("invalid label value for \"custom\": \"\\xbd\""), 212 }, 213 } 214 for _, c := range cases { 215 in := c.in.Copy() 216 217 res, orig, err := populateLabels(c.in, c.cfg) 218 testutil.ErrorEqual(err, c.err) 219 testutil.Equals(t, c.in, in) 220 testutil.Equals(t, c.res, res) 221 testutil.Equals(t, c.resOrig, orig) 222 } 223} 224 225func loadConfiguration(t *testing.T, c string) *config.Config { 226 t.Helper() 227 228 cfg := &config.Config{} 229 if err := yaml.UnmarshalStrict([]byte(c), cfg); err != nil { 230 t.Fatalf("Unable to load YAML config: %s", err) 231 } 232 return cfg 233} 234 235func noopLoop() loop { 236 return &testLoop{ 237 startFunc: func(interval, timeout time.Duration, errc chan<- error) {}, 238 stopFunc: func() {}, 239 } 240} 241 242func TestManagerApplyConfig(t *testing.T) { 243 // Valid initial configuration. 244 cfgText1 := ` 245scrape_configs: 246 - job_name: job1 247 static_configs: 248 - targets: ["foo:9090"] 249` 250 // Invalid configuration. 251 cfgText2 := ` 252scrape_configs: 253 - job_name: job1 254 scheme: https 255 static_configs: 256 - targets: ["foo:9090"] 257 tls_config: 258 ca_file: /not/existing/ca/file 259` 260 // Valid configuration. 261 cfgText3 := ` 262scrape_configs: 263 - job_name: job1 264 scheme: https 265 static_configs: 266 - targets: ["foo:9090"] 267` 268 var ( 269 cfg1 = loadConfiguration(t, cfgText1) 270 cfg2 = loadConfiguration(t, cfgText2) 271 cfg3 = loadConfiguration(t, cfgText3) 272 273 ch = make(chan struct{}, 1) 274 ) 275 276 scrapeManager := NewManager(nil, nil) 277 newLoop := func(scrapeLoopOptions) loop { 278 ch <- struct{}{} 279 return noopLoop() 280 } 281 sp := &scrapePool{ 282 appendable: &nopAppendable{}, 283 activeTargets: map[uint64]*Target{}, 284 loops: map[uint64]loop{ 285 1: noopLoop(), 286 }, 287 newLoop: newLoop, 288 logger: nil, 289 config: cfg1.ScrapeConfigs[0], 290 client: http.DefaultClient, 291 } 292 scrapeManager.scrapePools = map[string]*scrapePool{ 293 "job1": sp, 294 } 295 296 // Apply the initial configuration. 297 if err := scrapeManager.ApplyConfig(cfg1); err != nil { 298 t.Fatalf("unable to apply configuration: %s", err) 299 } 300 select { 301 case <-ch: 302 t.Fatal("reload happened") 303 default: 304 } 305 306 // Apply a configuration for which the reload fails. 307 if err := scrapeManager.ApplyConfig(cfg2); err == nil { 308 t.Fatalf("expecting error but got none") 309 } 310 select { 311 case <-ch: 312 t.Fatal("reload happened") 313 default: 314 } 315 316 // Apply a configuration for which the reload succeeds. 317 if err := scrapeManager.ApplyConfig(cfg3); err != nil { 318 t.Fatalf("unable to apply configuration: %s", err) 319 } 320 select { 321 case <-ch: 322 default: 323 t.Fatal("reload didn't happen") 324 } 325 326 // Re-applying the same configuration shouldn't trigger a reload. 327 if err := scrapeManager.ApplyConfig(cfg3); err != nil { 328 t.Fatalf("unable to apply configuration: %s", err) 329 } 330 select { 331 case <-ch: 332 t.Fatal("reload happened") 333 default: 334 } 335} 336 337func TestManagerTargetsUpdates(t *testing.T) { 338 m := NewManager(nil, nil) 339 340 ts := make(chan map[string][]*targetgroup.Group) 341 go m.Run(ts) 342 343 tgSent := make(map[string][]*targetgroup.Group) 344 for x := 0; x < 10; x++ { 345 346 tgSent[strconv.Itoa(x)] = []*targetgroup.Group{ 347 { 348 Source: strconv.Itoa(x), 349 }, 350 } 351 352 select { 353 case ts <- tgSent: 354 case <-time.After(10 * time.Millisecond): 355 t.Error("Scrape manager's channel remained blocked after the set threshold.") 356 } 357 } 358 359 m.mtxScrape.Lock() 360 tsetActual := m.targetSets 361 m.mtxScrape.Unlock() 362 363 // Make sure all updates have been received. 364 testutil.Equals(t, tgSent, tsetActual) 365 366 select { 367 case <-m.triggerReload: 368 default: 369 t.Error("No scrape loops reload was triggered after targets update.") 370 } 371} 372 373func TestSetJitter(t *testing.T) { 374 getConfig := func(prometheus string) *config.Config { 375 cfgText := ` 376global: 377 external_labels: 378 prometheus: '` + prometheus + `' 379` 380 381 cfg := &config.Config{} 382 if err := yaml.UnmarshalStrict([]byte(cfgText), cfg); err != nil { 383 t.Fatalf("Unable to load YAML config cfgYaml: %s", err) 384 } 385 386 return cfg 387 } 388 389 scrapeManager := NewManager(nil, nil) 390 391 // Load the first config. 392 cfg1 := getConfig("ha1") 393 if err := scrapeManager.setJitterSeed(cfg1.GlobalConfig.ExternalLabels); err != nil { 394 t.Error(err) 395 } 396 jitter1 := scrapeManager.jitterSeed 397 398 if jitter1 == 0 { 399 t.Error("Jitter has to be a hash of uint64") 400 } 401 402 // Load the first config. 403 cfg2 := getConfig("ha2") 404 if err := scrapeManager.setJitterSeed(cfg2.GlobalConfig.ExternalLabels); err != nil { 405 t.Error(err) 406 } 407 jitter2 := scrapeManager.jitterSeed 408 409 if jitter1 == jitter2 { 410 t.Error("Jitter should not be the same on different set of external labels") 411 } 412} 413