consul/api/health_test.go

677 lines
15 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package api
import (
"fmt"
"testing"
"github.com/hashicorp/consul/sdk/testutil"
"github.com/hashicorp/consul/sdk/testutil/retry"
"github.com/stretchr/testify/require"
)
func TestAPI_HealthNode(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
agent := c.Agent()
health := c.Health()
info, err := agent.Self()
if err != nil {
t.Fatalf("err: %v", err)
}
name := info["Config"]["NodeName"].(string)
retry.Run(t, func(r *retry.R) {
checks, meta, err := health.Node(name, nil)
if err != nil {
r.Fatal(err)
}
if meta.LastIndex == 0 {
r.Fatalf("bad: %v", meta)
}
if len(checks) == 0 {
r.Fatalf("bad: %v", checks)
}
})
}
func TestAPI_HealthNode_Filter(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
// this sets up the catalog entries with things we can filter on
testNodeServiceCheckRegistrations(t, c, "dc1")
health := c.Health()
// filter for just the redis service checks
checks, _, err := health.Node("foo", &QueryOptions{Filter: "ServiceName == redis"})
require.NoError(t, err)
require.Len(t, checks, 2)
// filter out service checks
checks, _, err = health.Node("foo", &QueryOptions{Filter: "ServiceID == ``"})
require.NoError(t, err)
require.Len(t, checks, 2)
}
func TestAPI_HealthChecks_AggregatedStatus(t *testing.T) {
t.Parallel()
cases := []struct {
name string
checks HealthChecks
exp string
}{
{
"empty",
nil,
HealthPassing,
},
{
"passing",
HealthChecks{
&HealthCheck{
Status: HealthPassing,
},
},
HealthPassing,
},
{
"warning",
HealthChecks{
&HealthCheck{
Status: HealthWarning,
},
},
HealthWarning,
},
{
"critical",
HealthChecks{
&HealthCheck{
Status: HealthCritical,
},
},
HealthCritical,
},
{
"node_maintenance",
HealthChecks{
&HealthCheck{
CheckID: NodeMaint,
},
},
HealthMaint,
},
{
"service_maintenance",
HealthChecks{
&HealthCheck{
CheckID: ServiceMaintPrefix + "service",
},
},
HealthMaint,
},
{
"unknown",
HealthChecks{
&HealthCheck{
Status: "nope-nope-noper",
},
},
"",
},
{
"maintenance_over_critical",
HealthChecks{
&HealthCheck{
CheckID: NodeMaint,
},
&HealthCheck{
Status: HealthCritical,
},
},
HealthMaint,
},
{
"critical_over_warning",
HealthChecks{
&HealthCheck{
Status: HealthCritical,
},
&HealthCheck{
Status: HealthWarning,
},
},
HealthCritical,
},
{
"warning_over_passing",
HealthChecks{
&HealthCheck{
Status: HealthWarning,
},
&HealthCheck{
Status: HealthPassing,
},
},
HealthWarning,
},
{
"lots",
HealthChecks{
&HealthCheck{
Status: HealthPassing,
},
&HealthCheck{
Status: HealthPassing,
},
&HealthCheck{
Status: HealthPassing,
},
&HealthCheck{
Status: HealthWarning,
},
},
HealthWarning,
},
}
for i, tc := range cases {
t.Run(fmt.Sprintf("%d_%s", i, tc.name), func(t *testing.T) {
act := tc.checks.AggregatedStatus()
if tc.exp != act {
t.Errorf("\nexp: %#v\nact: %#v", tc.exp, act)
}
})
}
}
func TestAPI_HealthChecks(t *testing.T) {
t.Parallel()
const nodename = "node123"
c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
conf.NodeName = nodename
})
defer s.Stop()
agent := c.Agent()
health := c.Health()
// Make a service with a check
reg := &AgentServiceRegistration{
Name: "foo",
Tags: []string{"bar"},
Check: &AgentServiceCheck{
TTL: "15s",
},
}
if err := agent.ServiceRegister(reg); err != nil {
t.Fatalf("err: %v", err)
}
retry.Run(t, func(r *retry.R) {
checks := HealthChecks{
&HealthCheck{
Node: nodename,
CheckID: "service:foo",
Name: "Service 'foo' check",
Status: "critical",
ServiceID: "foo",
ServiceName: "foo",
ServiceTags: []string{"bar"},
Type: "ttl",
Partition: defaultPartition,
Namespace: defaultNamespace,
},
}
out, meta, err := health.Checks("foo", nil)
if err != nil {
r.Fatal(err)
}
if meta.LastIndex == 0 {
r.Fatalf("bad: %v", meta)
}
checks[0].CreateIndex = out[0].CreateIndex
checks[0].ModifyIndex = out[0].ModifyIndex
require.Equal(r, checks, out)
})
}
func TestAPI_HealthChecks_NodeMetaFilter(t *testing.T) {
t.Parallel()
meta := map[string]string{"somekey": "somevalue"}
c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
conf.NodeMeta = meta
})
defer s.Stop()
agent := c.Agent()
health := c.Health()
s.WaitForSerfCheck(t)
// Make a service with a check
reg := &AgentServiceRegistration{
Name: "foo",
Check: &AgentServiceCheck{
TTL: "15s",
},
}
if err := agent.ServiceRegister(reg); err != nil {
t.Fatalf("err: %v", err)
}
retry.Run(t, func(r *retry.R) {
checks, meta, err := health.Checks("foo", &QueryOptions{NodeMeta: meta})
if err != nil {
r.Fatal(err)
}
if meta.LastIndex == 0 {
r.Fatalf("bad: %v", meta)
}
if len(checks) != 1 {
r.Fatalf("expected 1 check, got %d", len(checks))
}
if checks[0].Type != "ttl" {
r.Fatalf("expected type ttl, got %s", checks[0].Type)
}
})
}
func TestAPI_HealthChecks_Filter(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
// this sets up the catalog entries with things we can filter on
testNodeServiceCheckRegistrations(t, c, "dc1")
health := c.Health()
checks, _, err := health.Checks("redis", &QueryOptions{Filter: "Node == foo"})
require.NoError(t, err)
// 1 service check for each instance
require.Len(t, checks, 2)
checks, _, err = health.Checks("redis", &QueryOptions{Filter: "Node == bar"})
require.NoError(t, err)
// 1 service check for each instance
require.Len(t, checks, 1)
checks, _, err = health.Checks("redis", &QueryOptions{Filter: "Node == foo and v1 in ServiceTags"})
require.NoError(t, err)
// 1 service check for the matching instance
require.Len(t, checks, 1)
}
func TestAPI_HealthService(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
health := c.Health()
retry.Run(t, func(r *retry.R) {
// consul service should always exist...
checks, meta, err := health.Service("consul", "", true, nil)
if err != nil {
r.Fatal(err)
}
if meta.LastIndex == 0 {
r.Fatalf("bad: %v", meta)
}
if len(checks) == 0 {
r.Fatalf("Bad: %v", checks)
}
if _, ok := checks[0].Node.TaggedAddresses["wan"]; !ok {
r.Fatalf("Bad: %v", checks[0].Node)
}
if checks[0].Node.Datacenter != "dc1" {
r.Fatalf("Bad datacenter: %v", checks[0].Node)
}
})
}
func TestAPI_HealthService_SingleTag(t *testing.T) {
t.Parallel()
c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
conf.NodeName = "node123"
})
defer s.Stop()
agent := c.Agent()
health := c.Health()
reg := &AgentServiceRegistration{
Name: "foo",
ID: "foo1",
Tags: []string{"bar"},
Check: &AgentServiceCheck{
Status: HealthPassing,
TTL: "15s",
},
}
require.NoError(t, agent.ServiceRegister(reg))
retry.Run(t, func(r *retry.R) {
services, meta, err := health.Service("foo", "bar", true, nil)
require.NoError(r, err)
require.NotEqual(r, meta.LastIndex, 0)
require.Len(r, services, 1)
require.Equal(r, services[0].Service.ID, "foo1")
for _, check := range services[0].Checks {
if check.CheckID == "service:foo1" && check.Type != "ttl" {
r.Fatalf("expected type ttl, got %s", check.Type)
}
}
})
}
func TestAPI_HealthService_MultipleTags(t *testing.T) {
t.Parallel()
c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
conf.NodeName = "node123"
})
defer s.Stop()
agent := c.Agent()
health := c.Health()
// Make two services with a check
reg := &AgentServiceRegistration{
Name: "foo",
ID: "foo1",
Tags: []string{"bar"},
Check: &AgentServiceCheck{
Status: HealthPassing,
TTL: "15s",
},
}
require.NoError(t, agent.ServiceRegister(reg))
reg2 := &AgentServiceRegistration{
Name: "foo",
ID: "foo2",
Tags: []string{"bar", "v2"},
Check: &AgentServiceCheck{
Status: HealthPassing,
TTL: "15s",
},
}
require.NoError(t, agent.ServiceRegister(reg2))
// Test searching with one tag (two results)
retry.Run(t, func(r *retry.R) {
services, meta, err := health.ServiceMultipleTags("foo", []string{"bar"}, true, nil)
require.NoError(r, err)
require.NotEqual(r, meta.LastIndex, 0)
require.Len(r, services, 2)
})
// Test searching with two tags (one result)
retry.Run(t, func(r *retry.R) {
services, meta, err := health.ServiceMultipleTags("foo", []string{"bar", "v2"}, true, nil)
require.NoError(r, err)
require.NotEqual(r, meta.LastIndex, 0)
require.Len(r, services, 1)
require.Equal(r, services[0].Service.ID, "foo2")
})
}
func TestAPI_HealthService_NodeMetaFilter(t *testing.T) {
t.Parallel()
meta := map[string]string{"somekey": "somevalue"}
c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
conf.NodeMeta = meta
})
defer s.Stop()
s.WaitForSerfCheck(t)
health := c.Health()
retry.Run(t, func(r *retry.R) {
// consul service should always exist...
checks, meta, err := health.Service("consul", "", true, &QueryOptions{NodeMeta: meta})
require.NoError(r, err)
require.NotEqual(r, meta.LastIndex, 0)
require.NotEqual(r, len(checks), 0)
require.Equal(r, checks[0].Node.Datacenter, "dc1")
require.Contains(r, checks[0].Node.TaggedAddresses, "wan")
})
}
func TestAPI_HealthService_Filter(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
// this sets up the catalog entries with things we can filter on
testNodeServiceCheckRegistrations(t, c, "dc1")
health := c.Health()
services, _, err := health.Service("redis", "", false, &QueryOptions{Filter: "Service.Meta.version == 2"})
require.NoError(t, err)
require.Len(t, services, 1)
services, _, err = health.Service("web", "", false, &QueryOptions{Filter: "Node.Meta.os == linux"})
require.NoError(t, err)
require.Len(t, services, 2)
require.Equal(t, "baz", services[0].Node.Node)
require.Equal(t, "baz", services[1].Node.Node)
services, _, err = health.Service("web", "", false, &QueryOptions{Filter: "Node.Meta.os == linux and Service.Meta.version == 1"})
require.NoError(t, err)
require.Len(t, services, 1)
}
func TestAPI_HealthConnect(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
agent := c.Agent()
health := c.Health()
s.WaitForSerfCheck(t)
// Make a service with a proxy
reg := &AgentServiceRegistration{
Name: "foo",
Port: 8000,
}
err := agent.ServiceRegister(reg)
require.NoError(t, err)
// Register the proxy
proxyReg := &AgentServiceRegistration{
Name: "foo-proxy",
Port: 8001,
Kind: ServiceKindConnectProxy,
Proxy: &AgentServiceConnectProxyConfig{
DestinationServiceName: "foo",
},
}
err = agent.ServiceRegister(proxyReg)
require.NoError(t, err)
retry.Run(t, func(r *retry.R) {
services, meta, err := health.Connect("foo", "", true, nil)
if err != nil {
r.Fatal(err)
}
if meta.LastIndex == 0 {
r.Fatalf("bad: %v", meta)
}
// Should be exactly 1 service - the original shouldn't show up as a connect
// endpoint, only it's proxy.
if len(services) != 1 {
r.Fatalf("Bad: %v", services)
}
if services[0].Node.Datacenter != "dc1" {
r.Fatalf("Bad datacenter: %v", services[0].Node)
}
if services[0].Service.Port != proxyReg.Port {
r.Fatalf("Bad port: %v", services[0])
}
})
}
func TestAPI_HealthConnect_Filter(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
// this sets up the catalog entries with things we can filter on
testNodeServiceCheckRegistrations(t, c, "dc1")
health := c.Health()
services, _, err := health.Connect("web", "", false, &QueryOptions{Filter: "Node.Meta.os == linux"})
require.NoError(t, err)
require.Len(t, services, 2)
require.Equal(t, "baz", services[0].Node.Node)
require.Equal(t, "baz", services[1].Node.Node)
services, _, err = health.Service("web", "", false, &QueryOptions{Filter: "Node.Meta.os == linux and Service.Meta.version == 1"})
require.NoError(t, err)
require.Len(t, services, 1)
}
func TestAPI_HealthIngress(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
agent := c.Agent()
health := c.Health()
s.WaitForSerfCheck(t)
// Make a service with a proxy
reg := &AgentServiceRegistration{
Name: "foo",
Port: 8000,
}
err := agent.ServiceRegister(reg)
require.NoError(t, err)
// Register the gateway
gatewayReg := &AgentServiceRegistration{
Name: "foo-gateway",
Port: 8001,
Kind: ServiceKindIngressGateway,
}
err = agent.ServiceRegister(gatewayReg)
require.NoError(t, err)
// Associate service and gateway
gatewayConfig := &IngressGatewayConfigEntry{
Kind: IngressGateway,
Name: "foo-gateway",
Listeners: []IngressListener{
{
Port: 2222,
Protocol: "tcp",
Services: []IngressService{
{
Name: "foo",
},
},
},
},
}
_, wm, err := c.ConfigEntries().Set(gatewayConfig, nil)
require.NoError(t, err)
require.NotNil(t, wm)
retry.Run(t, func(r *retry.R) {
services, meta, err := health.Ingress("foo", true, nil)
require.NoError(r, err)
require.NotZero(r, meta.LastIndex)
// Should be exactly 1 service - the original shouldn't show up as a connect
// endpoint, only it's proxy.
require.Len(r, services, 1)
require.Equal(r, services[0].Node.Datacenter, "dc1")
require.Equal(r, services[0].Service.Service, gatewayReg.Name)
})
}
func TestAPI_HealthState(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
health := c.Health()
retry.Run(t, func(r *retry.R) {
checks, meta, err := health.State("any", nil)
if err != nil {
r.Fatal(err)
}
if meta.LastIndex == 0 {
r.Fatalf("bad: %v", meta)
}
if len(checks) == 0 {
r.Fatalf("Bad: %v", checks)
}
})
}
func TestAPI_HealthState_NodeMetaFilter(t *testing.T) {
t.Parallel()
meta := map[string]string{"somekey": "somevalue"}
c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
conf.NodeMeta = meta
})
defer s.Stop()
health := c.Health()
retry.Run(t, func(r *retry.R) {
checks, meta, err := health.State("any", &QueryOptions{NodeMeta: meta})
if err != nil {
r.Fatal(err)
}
if meta.LastIndex == 0 {
r.Fatalf("bad: %v", meta)
}
if len(checks) == 0 {
r.Fatalf("Bad: %v", checks)
}
})
}
func TestAPI_HealthState_Filter(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
// this sets up the catalog entries with things we can filter on
testNodeServiceCheckRegistrations(t, c, "dc1")
health := c.Health()
checks, _, err := health.State(HealthAny, &QueryOptions{Filter: "Node == baz"})
require.NoError(t, err)
require.Len(t, checks, 6)
checks, _, err = health.State(HealthAny, &QueryOptions{Filter: "Status == warning or Status == critical"})
require.NoError(t, err)
require.Len(t, checks, 2)
checks, _, err = health.State(HealthCritical, &QueryOptions{Filter: "Node == baz"})
require.NoError(t, err)
require.Len(t, checks, 1)
checks, _, err = health.State(HealthWarning, &QueryOptions{Filter: "Node == baz"})
require.NoError(t, err)
require.Len(t, checks, 1)
}