github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/clients/pkg/promtail/discovery/consulagent/consul_test.go (about) 1 // Copyright 2015 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 package consulagent 15 16 import ( 17 "context" 18 "net/http" 19 "net/http/httptest" 20 "net/url" 21 "testing" 22 "time" 23 24 "github.com/go-kit/log" 25 "github.com/prometheus/common/model" 26 "github.com/stretchr/testify/require" 27 "go.uber.org/goleak" 28 29 "github.com/prometheus/prometheus/discovery/targetgroup" 30 ) 31 32 //nolint:interfacer // this follows the pattern in prometheus service discovery 33 func TestMain(m *testing.M) { 34 goleak.VerifyTestMain(m) 35 } 36 37 func TestConfiguredService(t *testing.T) { 38 conf := &SDConfig{ 39 Services: []string{"configuredServiceName"}} 40 consulDiscovery, err := NewDiscovery(conf, nil) 41 42 if err != nil { 43 t.Errorf("Unexpected error when initializing discovery %v", err) 44 } 45 if !consulDiscovery.shouldWatch("configuredServiceName", []string{""}) { 46 t.Errorf("Expected service %s to be watched", "configuredServiceName") 47 } 48 if consulDiscovery.shouldWatch("nonConfiguredServiceName", []string{""}) { 49 t.Errorf("Expected service %s to not be watched", "nonConfiguredServiceName") 50 } 51 } 52 53 func TestConfiguredServiceWithTag(t *testing.T) { 54 conf := &SDConfig{ 55 Services: []string{"configuredServiceName"}, 56 ServiceTags: []string{"http"}, 57 } 58 consulDiscovery, err := NewDiscovery(conf, nil) 59 60 if err != nil { 61 t.Errorf("Unexpected error when initializing discovery %v", err) 62 } 63 if consulDiscovery.shouldWatch("configuredServiceName", []string{""}) { 64 t.Errorf("Expected service %s to not be watched without tag", "configuredServiceName") 65 } 66 if !consulDiscovery.shouldWatch("configuredServiceName", []string{"http"}) { 67 t.Errorf("Expected service %s to be watched with tag %s", "configuredServiceName", "http") 68 } 69 if consulDiscovery.shouldWatch("nonConfiguredServiceName", []string{""}) { 70 t.Errorf("Expected service %s to not be watched without tag", "nonConfiguredServiceName") 71 } 72 if consulDiscovery.shouldWatch("nonConfiguredServiceName", []string{"http"}) { 73 t.Errorf("Expected service %s to not be watched with tag %s", "nonConfiguredServiceName", "http") 74 } 75 } 76 77 func TestConfiguredServiceWithTags(t *testing.T) { 78 type testcase struct { 79 // What we've configured to watch. 80 conf *SDConfig 81 // The service we're checking if we should watch or not. 82 serviceName string 83 serviceTags []string 84 shouldWatch bool 85 } 86 87 cases := []testcase{ 88 { 89 conf: &SDConfig{ 90 Services: []string{"configuredServiceName"}, 91 ServiceTags: []string{"http", "v1"}, 92 }, 93 serviceName: "configuredServiceName", 94 serviceTags: []string{""}, 95 shouldWatch: false, 96 }, 97 { 98 conf: &SDConfig{ 99 Services: []string{"configuredServiceName"}, 100 ServiceTags: []string{"http", "v1"}, 101 }, 102 serviceName: "configuredServiceName", 103 serviceTags: []string{"http", "v1"}, 104 shouldWatch: true, 105 }, 106 { 107 conf: &SDConfig{ 108 Services: []string{"configuredServiceName"}, 109 ServiceTags: []string{"http", "v1"}, 110 }, 111 serviceName: "nonConfiguredServiceName", 112 serviceTags: []string{""}, 113 shouldWatch: false, 114 }, 115 { 116 conf: &SDConfig{ 117 Services: []string{"configuredServiceName"}, 118 ServiceTags: []string{"http", "v1"}, 119 }, 120 serviceName: "nonConfiguredServiceName", 121 serviceTags: []string{"http, v1"}, 122 shouldWatch: false, 123 }, 124 { 125 conf: &SDConfig{ 126 Services: []string{"configuredServiceName"}, 127 ServiceTags: []string{"http", "v1"}, 128 }, 129 serviceName: "configuredServiceName", 130 serviceTags: []string{"http", "v1", "foo"}, 131 shouldWatch: true, 132 }, 133 { 134 conf: &SDConfig{ 135 Services: []string{"configuredServiceName"}, 136 ServiceTags: []string{"http", "v1", "foo"}, 137 }, 138 serviceName: "configuredServiceName", 139 serviceTags: []string{"http", "v1", "foo"}, 140 shouldWatch: true, 141 }, 142 { 143 conf: &SDConfig{ 144 Services: []string{"configuredServiceName"}, 145 ServiceTags: []string{"http", "v1"}, 146 }, 147 serviceName: "configuredServiceName", 148 serviceTags: []string{"http", "v1", "v1"}, 149 shouldWatch: true, 150 }, 151 } 152 153 for _, tc := range cases { 154 consulDiscovery, err := NewDiscovery(tc.conf, nil) 155 156 if err != nil { 157 t.Errorf("Unexpected error when initializing discovery %v", err) 158 } 159 ret := consulDiscovery.shouldWatch(tc.serviceName, tc.serviceTags) 160 if ret != tc.shouldWatch { 161 t.Errorf("Expected should watch? %t, got %t. Watched service and tags: %s %+v, input was %s %+v", tc.shouldWatch, ret, tc.conf.Services, tc.conf.ServiceTags, tc.serviceName, tc.serviceTags) 162 } 163 164 } 165 } 166 167 func TestNonConfiguredService(t *testing.T) { 168 conf := &SDConfig{} 169 consulDiscovery, err := NewDiscovery(conf, nil) 170 171 if err != nil { 172 t.Errorf("Unexpected error when initializing discovery %v", err) 173 } 174 if !consulDiscovery.shouldWatch("nonConfiguredServiceName", []string{""}) { 175 t.Errorf("Expected service %s to be watched", "nonConfiguredServiceName") 176 } 177 } 178 179 const ( 180 AgentAnswer = `{ 181 "Config": { 182 "Datacenter": "test-dc", 183 "NodeName": "test-node", 184 "NodeID": "efd2573b-4c48-312b-2097-99bffc4352c4", 185 "Revision": "a9322b9c7", 186 "Server": false, 187 "Version": "1.8.3" 188 } 189 }` 190 ServiceTestAnswer = ` 191 [{ 192 "AggregatedStatus": "passing", 193 "Service": { 194 "ID": "test-id-1234", 195 "Service": "test", 196 "Tags": ["tag1"], 197 "Address": "", 198 "Meta": {"version":"1.0.0","environment":"staging"}, 199 "Port": 3341, 200 "Weights": { 201 "Passing": 1, 202 "Warning": 1 203 }, 204 "EnableTagOverride": false, 205 "ProxyDestination": "", 206 "Proxy": {}, 207 "Connect": {}, 208 "CreateIndex": 1, 209 "ModifyIndex": 1 210 }, 211 "Checks": [{ 212 "Node": "node1", 213 "CheckID": "serfHealth", 214 "Name": "Serf Health Status", 215 "Status": "passing" 216 }] 217 }]` 218 ServiceOtherAnswer = ` 219 [{ 220 "AggregatedStatus": "passing", 221 "Service": { 222 "ID": "other-id-5678", 223 "Service": "other", 224 "Tags": ["tag2"], 225 "Address": "", 226 "Meta": {"version":"1.0.0","environment":"staging"}, 227 "Port": 0, 228 "Weights": { 229 "Passing": 1, 230 "Warning": 1 231 }, 232 "EnableTagOverride": false, 233 "ProxyDestination": "", 234 "Proxy": {}, 235 "Connect": {}, 236 "CreateIndex": 1, 237 "ModifyIndex": 1 238 }, 239 "Checks": [{ 240 "Node": "node1", 241 "CheckID": "serfHealth", 242 "Name": "Serf Health Status", 243 "Status": "passing" 244 }] 245 }]` 246 247 ServicesTestAnswer = ` 248 { 249 "test-id-1234": { 250 "ID": "test-id-1234", 251 "Service": "test", 252 "Tags": [ "tag1" ], 253 "Meta": {"version":"1.0.0","environment":"staging"}, 254 "Port": 3341, 255 "Address": "1.1.1.1", 256 "TaggedAddresses": { 257 "lan_ipv4": { 258 "Address": "1.1.1.1", 259 "Port": 4646 260 }, 261 "wan_ipv4": { 262 "Address": "1.1.1.1", 263 "Port": 4646 264 } 265 }, 266 "Weights": { 267 "Passing": 1, 268 "Warning": 1 269 }, 270 "EnableTagOverride": false 271 }, 272 "test-id-5678": { 273 "ID": "test-id-5678", 274 "Service": "test", 275 "Tags": [ "tag1" ], 276 "Meta": {"version":"1.0.0","environment":"staging"}, 277 "Port": 3341, 278 "Address": "1.1.2.2", 279 "TaggedAddresses": { 280 "lan_ipv4": { 281 "Address": "1.1.2.2", 282 "Port": 4646 283 }, 284 "wan_ipv4": { 285 "Address": "1.1.2.2", 286 "Port": 4646 287 } 288 }, 289 "Weights": { 290 "Passing": 1, 291 "Warning": 1 292 }, 293 "EnableTagOverride": false 294 }, 295 "other-id-9876": { 296 "ID": "other-id-9876", 297 "Service": "other", 298 "Tags": [ "tag2" ], 299 "Meta": {"version":"1.0.0","environment":"staging"}, 300 "Port": 0, 301 "Address": "", 302 "Weights": { 303 "Passing": 1, 304 "Warning": 1 305 }, 306 "EnableTagOverride": false 307 } 308 }` 309 ) 310 311 func newServer(t *testing.T) (*httptest.Server, *SDConfig) { 312 // github.com/hashicorp/consul/testutil/ would be nice but it needs a local consul binary. 313 stub := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 314 response := "" 315 switch r.URL.String() { 316 case "/v1/agent/self": 317 response = AgentAnswer 318 case "/v1/agent/health/service/name/test?format=json": 319 response = ServiceTestAnswer 320 case "/v1/agent/health/service/name/other?format=json": 321 response = ServiceOtherAnswer 322 case "/v1/agent/services": 323 response = ServicesTestAnswer 324 default: 325 t.Errorf("Unhandled consul call: %s", r.URL) 326 } 327 w.Header().Add("X-Consul-Index", "1") 328 _, err := w.Write([]byte(response)) 329 require.NoError(t, err) 330 })) 331 stuburl, err := url.Parse(stub.URL) 332 require.NoError(t, err) 333 334 config := &SDConfig{ 335 Server: stuburl.Host, 336 Token: "fake-token", 337 RefreshInterval: model.Duration(1 * time.Second), 338 } 339 return stub, config 340 } 341 342 func newDiscovery(t *testing.T, config *SDConfig) *Discovery { 343 logger := log.NewNopLogger() 344 d, err := NewDiscovery(config, logger) 345 require.NoError(t, err) 346 return d 347 } 348 349 func checkOneTarget(t *testing.T, tg []*targetgroup.Group) { 350 require.Equal(t, 1, len(tg)) 351 target := tg[0] 352 require.Equal(t, "test-dc", string(target.Labels["__meta_consulagent_dc"])) 353 require.Equal(t, target.Source, string(target.Labels["__meta_consulagent_service"])) 354 if target.Source == "test" { 355 // test service should have one node. 356 require.Greater(t, len(target.Targets), 0, "Test service should have one node") 357 } 358 } 359 360 // Watch all the services in the catalog. 361 func TestAllServices(t *testing.T) { 362 stub, config := newServer(t) 363 defer stub.Close() 364 365 d := newDiscovery(t, config) 366 367 ctx, cancel := context.WithCancel(context.Background()) 368 ch := make(chan []*targetgroup.Group) 369 go func() { 370 d.Run(ctx, ch) 371 close(ch) 372 }() 373 checkOneTarget(t, <-ch) 374 checkOneTarget(t, <-ch) 375 cancel() 376 <-ch 377 } 378 379 // targetgroup with no targets is emitted if no services were discovered. 380 func TestNoTargets(t *testing.T) { 381 stub, config := newServer(t) 382 defer stub.Close() 383 config.ServiceTags = []string{"missing"} 384 385 d := newDiscovery(t, config) 386 387 ctx, cancel := context.WithCancel(context.Background()) 388 ch := make(chan []*targetgroup.Group) 389 go d.Run(ctx, ch) 390 391 targets := (<-ch)[0].Targets 392 require.Equal(t, 0, len(targets)) 393 cancel() 394 } 395 396 // Watch only the test service. 397 func TestOneService(t *testing.T) { 398 stub, config := newServer(t) 399 defer stub.Close() 400 401 config.Services = []string{"test"} 402 d := newDiscovery(t, config) 403 404 ctx, cancel := context.WithCancel(context.Background()) 405 ch := make(chan []*targetgroup.Group) 406 go d.Run(ctx, ch) 407 checkOneTarget(t, <-ch) 408 cancel() 409 } 410 411 // Watch the test service with a specific tag and node-meta. 412 func TestAllOptions(t *testing.T) { 413 stub, config := newServer(t) 414 defer stub.Close() 415 416 config.Services = []string{"test"} 417 config.NodeMeta = map[string]string{"rack_name": "2304"} 418 config.ServiceTags = []string{"tag1"} 419 config.AllowStale = true 420 config.Token = "fake-token" 421 422 d := newDiscovery(t, config) 423 424 ctx, cancel := context.WithCancel(context.Background()) 425 ch := make(chan []*targetgroup.Group) 426 go func() { 427 d.Run(ctx, ch) 428 close(ch) 429 }() 430 checkOneTarget(t, <-ch) 431 cancel() 432 <-ch 433 } 434 435 func TestGetDatacenterShouldReturnError(t *testing.T) { 436 for _, tc := range []struct { 437 handler func(http.ResponseWriter, *http.Request) 438 errMessage string 439 }{ 440 { 441 // Define a handler that will return status 500. 442 handler: func(w http.ResponseWriter, r *http.Request) { 443 w.WriteHeader(500) 444 }, 445 errMessage: "Unexpected response code: 500 ()", 446 }, 447 { 448 // Define a handler that will return incorrect response. 449 handler: func(w http.ResponseWriter, r *http.Request) { 450 _, err := w.Write([]byte(`{"Config": {"Not-Datacenter": "test-dc"}}`)) 451 require.NoError(t, err) 452 }, 453 errMessage: "invalid value '<nil>' for Config.Datacenter", 454 }, 455 } { 456 stub := httptest.NewServer(http.HandlerFunc(tc.handler)) 457 stuburl, err := url.Parse(stub.URL) 458 require.NoError(t, err) 459 460 config := &SDConfig{ 461 Server: stuburl.Host, 462 Token: "fake-token", 463 RefreshInterval: model.Duration(1 * time.Second), 464 } 465 defer stub.Close() 466 d := newDiscovery(t, config) 467 468 // Should be empty if not initialized. 469 require.Equal(t, "", d.clientDatacenter) 470 471 err = d.getDatacenter() 472 473 // An error should be returned. 474 require.Equal(t, tc.errMessage, err.Error()) 475 // Should still be empty. 476 require.Equal(t, "", d.clientDatacenter) 477 } 478 }