1package api 2 3import ( 4 "fmt" 5 "testing" 6 7 "github.com/hashicorp/consul/sdk/testutil" 8 "github.com/hashicorp/consul/sdk/testutil/retry" 9 "github.com/stretchr/testify/require" 10) 11 12func TestAPI_HealthNode(t *testing.T) { 13 t.Parallel() 14 c, s := makeClient(t) 15 defer s.Stop() 16 17 agent := c.Agent() 18 health := c.Health() 19 20 info, err := agent.Self() 21 if err != nil { 22 t.Fatalf("err: %v", err) 23 } 24 name := info["Config"]["NodeName"].(string) 25 retry.Run(t, func(r *retry.R) { 26 checks, meta, err := health.Node(name, nil) 27 if err != nil { 28 r.Fatal(err) 29 } 30 if meta.LastIndex == 0 { 31 r.Fatalf("bad: %v", meta) 32 } 33 if len(checks) == 0 { 34 r.Fatalf("bad: %v", checks) 35 } 36 }) 37} 38 39func TestAPI_HealthNode_Filter(t *testing.T) { 40 t.Parallel() 41 c, s := makeClient(t) 42 defer s.Stop() 43 44 // this sets up the catalog entries with things we can filter on 45 testNodeServiceCheckRegistrations(t, c, "dc1") 46 47 health := c.Health() 48 49 // filter for just the redis service checks 50 checks, _, err := health.Node("foo", &QueryOptions{Filter: "ServiceName == redis"}) 51 require.NoError(t, err) 52 require.Len(t, checks, 2) 53 54 // filter out service checks 55 checks, _, err = health.Node("foo", &QueryOptions{Filter: "ServiceID == ``"}) 56 require.NoError(t, err) 57 require.Len(t, checks, 2) 58} 59 60func TestAPI_HealthChecks_AggregatedStatus(t *testing.T) { 61 t.Parallel() 62 63 cases := []struct { 64 name string 65 checks HealthChecks 66 exp string 67 }{ 68 { 69 "empty", 70 nil, 71 HealthPassing, 72 }, 73 { 74 "passing", 75 HealthChecks{ 76 &HealthCheck{ 77 Status: HealthPassing, 78 }, 79 }, 80 HealthPassing, 81 }, 82 { 83 "warning", 84 HealthChecks{ 85 &HealthCheck{ 86 Status: HealthWarning, 87 }, 88 }, 89 HealthWarning, 90 }, 91 { 92 "critical", 93 HealthChecks{ 94 &HealthCheck{ 95 Status: HealthCritical, 96 }, 97 }, 98 HealthCritical, 99 }, 100 { 101 "node_maintenance", 102 HealthChecks{ 103 &HealthCheck{ 104 CheckID: NodeMaint, 105 }, 106 }, 107 HealthMaint, 108 }, 109 { 110 "service_maintenance", 111 HealthChecks{ 112 &HealthCheck{ 113 CheckID: ServiceMaintPrefix + "service", 114 }, 115 }, 116 HealthMaint, 117 }, 118 { 119 "unknown", 120 HealthChecks{ 121 &HealthCheck{ 122 Status: "nope-nope-noper", 123 }, 124 }, 125 "", 126 }, 127 { 128 "maintenance_over_critical", 129 HealthChecks{ 130 &HealthCheck{ 131 CheckID: NodeMaint, 132 }, 133 &HealthCheck{ 134 Status: HealthCritical, 135 }, 136 }, 137 HealthMaint, 138 }, 139 { 140 "critical_over_warning", 141 HealthChecks{ 142 &HealthCheck{ 143 Status: HealthCritical, 144 }, 145 &HealthCheck{ 146 Status: HealthWarning, 147 }, 148 }, 149 HealthCritical, 150 }, 151 { 152 "warning_over_passing", 153 HealthChecks{ 154 &HealthCheck{ 155 Status: HealthWarning, 156 }, 157 &HealthCheck{ 158 Status: HealthPassing, 159 }, 160 }, 161 HealthWarning, 162 }, 163 { 164 "lots", 165 HealthChecks{ 166 &HealthCheck{ 167 Status: HealthPassing, 168 }, 169 &HealthCheck{ 170 Status: HealthPassing, 171 }, 172 &HealthCheck{ 173 Status: HealthPassing, 174 }, 175 &HealthCheck{ 176 Status: HealthWarning, 177 }, 178 }, 179 HealthWarning, 180 }, 181 } 182 183 for i, tc := range cases { 184 t.Run(fmt.Sprintf("%d_%s", i, tc.name), func(t *testing.T) { 185 act := tc.checks.AggregatedStatus() 186 if tc.exp != act { 187 t.Errorf("\nexp: %#v\nact: %#v", tc.exp, act) 188 } 189 }) 190 } 191} 192 193func TestAPI_HealthChecks(t *testing.T) { 194 t.Parallel() 195 c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) { 196 conf.NodeName = "node123" 197 }) 198 defer s.Stop() 199 200 agent := c.Agent() 201 health := c.Health() 202 203 // Make a service with a check 204 reg := &AgentServiceRegistration{ 205 Name: "foo", 206 Tags: []string{"bar"}, 207 Check: &AgentServiceCheck{ 208 TTL: "15s", 209 }, 210 } 211 if err := agent.ServiceRegister(reg); err != nil { 212 t.Fatalf("err: %v", err) 213 } 214 215 retry.Run(t, func(r *retry.R) { 216 checks := HealthChecks{ 217 &HealthCheck{ 218 Node: "node123", 219 CheckID: "service:foo", 220 Name: "Service 'foo' check", 221 Status: "critical", 222 ServiceID: "foo", 223 ServiceName: "foo", 224 ServiceTags: []string{"bar"}, 225 Type: "ttl", 226 Namespace: defaultNamespace, 227 }, 228 } 229 230 out, meta, err := health.Checks("foo", nil) 231 if err != nil { 232 r.Fatal(err) 233 } 234 if meta.LastIndex == 0 { 235 r.Fatalf("bad: %v", meta) 236 } 237 checks[0].CreateIndex = out[0].CreateIndex 238 checks[0].ModifyIndex = out[0].ModifyIndex 239 require.Equal(r, checks, out) 240 }) 241} 242 243func TestAPI_HealthChecks_NodeMetaFilter(t *testing.T) { 244 t.Parallel() 245 meta := map[string]string{"somekey": "somevalue"} 246 c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) { 247 conf.NodeMeta = meta 248 }) 249 defer s.Stop() 250 251 agent := c.Agent() 252 health := c.Health() 253 254 s.WaitForSerfCheck(t) 255 256 // Make a service with a check 257 reg := &AgentServiceRegistration{ 258 Name: "foo", 259 Check: &AgentServiceCheck{ 260 TTL: "15s", 261 }, 262 } 263 if err := agent.ServiceRegister(reg); err != nil { 264 t.Fatalf("err: %v", err) 265 } 266 267 retry.Run(t, func(r *retry.R) { 268 checks, meta, err := health.Checks("foo", &QueryOptions{NodeMeta: meta}) 269 if err != nil { 270 r.Fatal(err) 271 } 272 if meta.LastIndex == 0 { 273 r.Fatalf("bad: %v", meta) 274 } 275 if len(checks) != 1 { 276 r.Fatalf("expected 1 check, got %d", len(checks)) 277 } 278 if checks[0].Type != "ttl" { 279 r.Fatalf("expected type ttl, got %s", checks[0].Type) 280 } 281 }) 282} 283 284func TestAPI_HealthChecks_Filter(t *testing.T) { 285 t.Parallel() 286 c, s := makeClient(t) 287 defer s.Stop() 288 289 // this sets up the catalog entries with things we can filter on 290 testNodeServiceCheckRegistrations(t, c, "dc1") 291 292 health := c.Health() 293 294 checks, _, err := health.Checks("redis", &QueryOptions{Filter: "Node == foo"}) 295 require.NoError(t, err) 296 // 1 service check for each instance 297 require.Len(t, checks, 2) 298 299 checks, _, err = health.Checks("redis", &QueryOptions{Filter: "Node == bar"}) 300 require.NoError(t, err) 301 // 1 service check for each instance 302 require.Len(t, checks, 1) 303 304 checks, _, err = health.Checks("redis", &QueryOptions{Filter: "Node == foo and v1 in ServiceTags"}) 305 require.NoError(t, err) 306 // 1 service check for the matching instance 307 require.Len(t, checks, 1) 308} 309 310func TestAPI_HealthService(t *testing.T) { 311 t.Parallel() 312 c, s := makeClient(t) 313 defer s.Stop() 314 315 health := c.Health() 316 retry.Run(t, func(r *retry.R) { 317 // consul service should always exist... 318 checks, meta, err := health.Service("consul", "", true, nil) 319 if err != nil { 320 r.Fatal(err) 321 } 322 if meta.LastIndex == 0 { 323 r.Fatalf("bad: %v", meta) 324 } 325 if len(checks) == 0 { 326 r.Fatalf("Bad: %v", checks) 327 } 328 if _, ok := checks[0].Node.TaggedAddresses["wan"]; !ok { 329 r.Fatalf("Bad: %v", checks[0].Node) 330 } 331 if checks[0].Node.Datacenter != "dc1" { 332 r.Fatalf("Bad datacenter: %v", checks[0].Node) 333 } 334 }) 335} 336 337func TestAPI_HealthService_SingleTag(t *testing.T) { 338 t.Parallel() 339 c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) { 340 conf.NodeName = "node123" 341 }) 342 defer s.Stop() 343 agent := c.Agent() 344 health := c.Health() 345 reg := &AgentServiceRegistration{ 346 Name: "foo", 347 ID: "foo1", 348 Tags: []string{"bar"}, 349 Check: &AgentServiceCheck{ 350 Status: HealthPassing, 351 TTL: "15s", 352 }, 353 } 354 require.NoError(t, agent.ServiceRegister(reg)) 355 retry.Run(t, func(r *retry.R) { 356 services, meta, err := health.Service("foo", "bar", true, nil) 357 require.NoError(r, err) 358 require.NotEqual(r, meta.LastIndex, 0) 359 require.Len(r, services, 1) 360 require.Equal(r, services[0].Service.ID, "foo1") 361 362 for _, check := range services[0].Checks { 363 if check.CheckID == "service:foo1" && check.Type != "ttl" { 364 r.Fatalf("expected type ttl, got %s", check.Type) 365 } 366 } 367 }) 368} 369func TestAPI_HealthService_MultipleTags(t *testing.T) { 370 t.Parallel() 371 c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) { 372 conf.NodeName = "node123" 373 }) 374 defer s.Stop() 375 376 agent := c.Agent() 377 health := c.Health() 378 379 // Make two services with a check 380 reg := &AgentServiceRegistration{ 381 Name: "foo", 382 ID: "foo1", 383 Tags: []string{"bar"}, 384 Check: &AgentServiceCheck{ 385 Status: HealthPassing, 386 TTL: "15s", 387 }, 388 } 389 require.NoError(t, agent.ServiceRegister(reg)) 390 391 reg2 := &AgentServiceRegistration{ 392 Name: "foo", 393 ID: "foo2", 394 Tags: []string{"bar", "v2"}, 395 Check: &AgentServiceCheck{ 396 Status: HealthPassing, 397 TTL: "15s", 398 }, 399 } 400 require.NoError(t, agent.ServiceRegister(reg2)) 401 402 // Test searching with one tag (two results) 403 retry.Run(t, func(r *retry.R) { 404 services, meta, err := health.ServiceMultipleTags("foo", []string{"bar"}, true, nil) 405 406 require.NoError(r, err) 407 require.NotEqual(r, meta.LastIndex, 0) 408 require.Len(r, services, 2) 409 }) 410 411 // Test searching with two tags (one result) 412 retry.Run(t, func(r *retry.R) { 413 services, meta, err := health.ServiceMultipleTags("foo", []string{"bar", "v2"}, true, nil) 414 415 require.NoError(r, err) 416 require.NotEqual(r, meta.LastIndex, 0) 417 require.Len(r, services, 1) 418 require.Equal(r, services[0].Service.ID, "foo2") 419 }) 420} 421 422func TestAPI_HealthService_NodeMetaFilter(t *testing.T) { 423 t.Parallel() 424 meta := map[string]string{"somekey": "somevalue"} 425 c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) { 426 conf.NodeMeta = meta 427 }) 428 defer s.Stop() 429 430 s.WaitForSerfCheck(t) 431 432 health := c.Health() 433 retry.Run(t, func(r *retry.R) { 434 // consul service should always exist... 435 checks, meta, err := health.Service("consul", "", true, &QueryOptions{NodeMeta: meta}) 436 require.NoError(r, err) 437 require.NotEqual(r, meta.LastIndex, 0) 438 require.NotEqual(r, len(checks), 0) 439 require.Equal(r, checks[0].Node.Datacenter, "dc1") 440 require.Contains(r, checks[0].Node.TaggedAddresses, "wan") 441 }) 442} 443 444func TestAPI_HealthService_Filter(t *testing.T) { 445 t.Parallel() 446 c, s := makeClient(t) 447 defer s.Stop() 448 449 // this sets up the catalog entries with things we can filter on 450 testNodeServiceCheckRegistrations(t, c, "dc1") 451 452 health := c.Health() 453 454 services, _, err := health.Service("redis", "", false, &QueryOptions{Filter: "Service.Meta.version == 2"}) 455 require.NoError(t, err) 456 require.Len(t, services, 1) 457 458 services, _, err = health.Service("web", "", false, &QueryOptions{Filter: "Node.Meta.os == linux"}) 459 require.NoError(t, err) 460 require.Len(t, services, 2) 461 require.Equal(t, "baz", services[0].Node.Node) 462 require.Equal(t, "baz", services[1].Node.Node) 463 464 services, _, err = health.Service("web", "", false, &QueryOptions{Filter: "Node.Meta.os == linux and Service.Meta.version == 1"}) 465 require.NoError(t, err) 466 require.Len(t, services, 1) 467} 468 469func TestAPI_HealthConnect(t *testing.T) { 470 t.Parallel() 471 c, s := makeClient(t) 472 defer s.Stop() 473 474 agent := c.Agent() 475 health := c.Health() 476 477 s.WaitForSerfCheck(t) 478 479 // Make a service with a proxy 480 reg := &AgentServiceRegistration{ 481 Name: "foo", 482 Port: 8000, 483 } 484 err := agent.ServiceRegister(reg) 485 require.NoError(t, err) 486 487 // Register the proxy 488 proxyReg := &AgentServiceRegistration{ 489 Name: "foo-proxy", 490 Port: 8001, 491 Kind: ServiceKindConnectProxy, 492 Proxy: &AgentServiceConnectProxyConfig{ 493 DestinationServiceName: "foo", 494 }, 495 } 496 err = agent.ServiceRegister(proxyReg) 497 require.NoError(t, err) 498 499 retry.Run(t, func(r *retry.R) { 500 services, meta, err := health.Connect("foo", "", true, nil) 501 if err != nil { 502 r.Fatal(err) 503 } 504 if meta.LastIndex == 0 { 505 r.Fatalf("bad: %v", meta) 506 } 507 // Should be exactly 1 service - the original shouldn't show up as a connect 508 // endpoint, only it's proxy. 509 if len(services) != 1 { 510 r.Fatalf("Bad: %v", services) 511 } 512 if services[0].Node.Datacenter != "dc1" { 513 r.Fatalf("Bad datacenter: %v", services[0].Node) 514 } 515 if services[0].Service.Port != proxyReg.Port { 516 r.Fatalf("Bad port: %v", services[0]) 517 } 518 }) 519} 520 521func TestAPI_HealthConnect_Filter(t *testing.T) { 522 t.Parallel() 523 c, s := makeClient(t) 524 defer s.Stop() 525 526 // this sets up the catalog entries with things we can filter on 527 testNodeServiceCheckRegistrations(t, c, "dc1") 528 529 health := c.Health() 530 531 services, _, err := health.Connect("web", "", false, &QueryOptions{Filter: "Node.Meta.os == linux"}) 532 require.NoError(t, err) 533 require.Len(t, services, 2) 534 require.Equal(t, "baz", services[0].Node.Node) 535 require.Equal(t, "baz", services[1].Node.Node) 536 537 services, _, err = health.Service("web", "", false, &QueryOptions{Filter: "Node.Meta.os == linux and Service.Meta.version == 1"}) 538 require.NoError(t, err) 539 require.Len(t, services, 1) 540} 541 542func TestAPI_HealthIngress(t *testing.T) { 543 t.Parallel() 544 c, s := makeClient(t) 545 defer s.Stop() 546 547 agent := c.Agent() 548 health := c.Health() 549 550 s.WaitForSerfCheck(t) 551 552 // Make a service with a proxy 553 reg := &AgentServiceRegistration{ 554 Name: "foo", 555 Port: 8000, 556 } 557 err := agent.ServiceRegister(reg) 558 require.NoError(t, err) 559 560 // Register the gateway 561 gatewayReg := &AgentServiceRegistration{ 562 Name: "foo-gateway", 563 Port: 8001, 564 Kind: ServiceKindIngressGateway, 565 } 566 err = agent.ServiceRegister(gatewayReg) 567 require.NoError(t, err) 568 569 // Associate service and gateway 570 gatewayConfig := &IngressGatewayConfigEntry{ 571 Kind: IngressGateway, 572 Name: "foo-gateway", 573 Listeners: []IngressListener{ 574 { 575 Port: 2222, 576 Protocol: "tcp", 577 Services: []IngressService{ 578 { 579 Name: "foo", 580 }, 581 }, 582 }, 583 }, 584 } 585 _, wm, err := c.ConfigEntries().Set(gatewayConfig, nil) 586 require.NoError(t, err) 587 require.NotNil(t, wm) 588 589 retry.Run(t, func(r *retry.R) { 590 services, meta, err := health.Ingress("foo", true, nil) 591 require.NoError(r, err) 592 593 require.NotZero(r, meta.LastIndex) 594 595 // Should be exactly 1 service - the original shouldn't show up as a connect 596 // endpoint, only it's proxy. 597 require.Len(r, services, 1) 598 require.Equal(r, services[0].Node.Datacenter, "dc1") 599 require.Equal(r, services[0].Service.Service, gatewayReg.Name) 600 }) 601} 602 603func TestAPI_HealthState(t *testing.T) { 604 t.Parallel() 605 c, s := makeClient(t) 606 defer s.Stop() 607 608 health := c.Health() 609 retry.Run(t, func(r *retry.R) { 610 checks, meta, err := health.State("any", nil) 611 if err != nil { 612 r.Fatal(err) 613 } 614 if meta.LastIndex == 0 { 615 r.Fatalf("bad: %v", meta) 616 } 617 if len(checks) == 0 { 618 r.Fatalf("Bad: %v", checks) 619 } 620 }) 621} 622 623func TestAPI_HealthState_NodeMetaFilter(t *testing.T) { 624 t.Parallel() 625 meta := map[string]string{"somekey": "somevalue"} 626 c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) { 627 conf.NodeMeta = meta 628 }) 629 defer s.Stop() 630 631 health := c.Health() 632 retry.Run(t, func(r *retry.R) { 633 checks, meta, err := health.State("any", &QueryOptions{NodeMeta: meta}) 634 if err != nil { 635 r.Fatal(err) 636 } 637 if meta.LastIndex == 0 { 638 r.Fatalf("bad: %v", meta) 639 } 640 if len(checks) == 0 { 641 r.Fatalf("Bad: %v", checks) 642 } 643 }) 644} 645 646func TestAPI_HealthState_Filter(t *testing.T) { 647 t.Parallel() 648 c, s := makeClient(t) 649 defer s.Stop() 650 651 // this sets up the catalog entries with things we can filter on 652 testNodeServiceCheckRegistrations(t, c, "dc1") 653 654 health := c.Health() 655 656 checks, _, err := health.State(HealthAny, &QueryOptions{Filter: "Node == baz"}) 657 require.NoError(t, err) 658 require.Len(t, checks, 6) 659 660 checks, _, err = health.State(HealthAny, &QueryOptions{Filter: "Status == warning or Status == critical"}) 661 require.NoError(t, err) 662 require.Len(t, checks, 2) 663 664 checks, _, err = health.State(HealthCritical, &QueryOptions{Filter: "Node == baz"}) 665 require.NoError(t, err) 666 require.Len(t, checks, 1) 667 668 checks, _, err = health.State(HealthWarning, &QueryOptions{Filter: "Node == baz"}) 669 require.NoError(t, err) 670 require.Len(t, checks, 1) 671} 672