2020-09-30 21:38:13 +00:00
|
|
|
package health
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2021-02-03 17:23:10 +00:00
|
|
|
"strings"
|
2020-09-30 21:38:13 +00:00
|
|
|
|
|
|
|
"github.com/hashicorp/consul/agent/cache"
|
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Client struct {
|
2020-10-26 15:55:49 +00:00
|
|
|
NetRPC NetRPC
|
|
|
|
Cache CacheGetter
|
|
|
|
// CacheName to use for service health.
|
2020-10-05 21:31:35 +00:00
|
|
|
CacheName string
|
2020-10-26 15:55:49 +00:00
|
|
|
// CacheNameConnect is the name of the cache to use for connect service health.
|
|
|
|
CacheNameConnect string
|
2020-09-30 21:38:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type NetRPC interface {
|
|
|
|
RPC(method string, args interface{}, reply interface{}) error
|
|
|
|
}
|
|
|
|
|
|
|
|
type CacheGetter interface {
|
|
|
|
Get(ctx context.Context, t string, r cache.Request) (interface{}, cache.ResultMeta, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) ServiceNodes(
|
|
|
|
ctx context.Context,
|
|
|
|
req structs.ServiceSpecificRequest,
|
|
|
|
) (structs.IndexedCheckServiceNodes, cache.ResultMeta, error) {
|
|
|
|
out, md, err := c.getServiceNodes(ctx, req)
|
|
|
|
if err != nil {
|
|
|
|
return out, md, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: DNSServer emitted a metric here, do we still need it?
|
|
|
|
if req.QueryOptions.AllowStale && req.QueryOptions.MaxStaleDuration > 0 && out.QueryMeta.LastContact > req.MaxStaleDuration {
|
|
|
|
req.AllowStale = false
|
|
|
|
err := c.NetRPC.RPC("Health.ServiceNodes", &req, &out)
|
|
|
|
return out, cache.ResultMeta{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return out, md, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) getServiceNodes(
|
|
|
|
ctx context.Context,
|
|
|
|
req structs.ServiceSpecificRequest,
|
|
|
|
) (structs.IndexedCheckServiceNodes, cache.ResultMeta, error) {
|
|
|
|
var out structs.IndexedCheckServiceNodes
|
|
|
|
|
2021-02-03 17:23:10 +00:00
|
|
|
req.ServiceName = strings.ToLower(req.ServiceName)
|
2020-09-30 21:38:13 +00:00
|
|
|
if !req.QueryOptions.UseCache {
|
|
|
|
err := c.NetRPC.RPC("Health.ServiceNodes", &req, &out)
|
|
|
|
return out, cache.ResultMeta{}, err
|
|
|
|
}
|
|
|
|
|
2020-10-26 15:55:49 +00:00
|
|
|
cacheName := c.CacheName
|
|
|
|
if req.Connect {
|
|
|
|
cacheName = c.CacheNameConnect
|
|
|
|
}
|
|
|
|
|
|
|
|
raw, md, err := c.Cache.Get(ctx, cacheName, &req)
|
2020-09-30 21:38:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return out, md, err
|
|
|
|
}
|
|
|
|
|
|
|
|
value, ok := raw.(*structs.IndexedCheckServiceNodes)
|
|
|
|
if !ok {
|
|
|
|
panic("wrong response type for cachetype.HealthServicesName")
|
|
|
|
}
|
2021-02-03 17:23:10 +00:00
|
|
|
|
2021-02-08 16:53:18 +00:00
|
|
|
return filterTags(filterNodeMeta(value, req), req), md, nil
|
2021-02-03 17:23:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func filterTags(out *structs.IndexedCheckServiceNodes, req structs.ServiceSpecificRequest) structs.IndexedCheckServiceNodes {
|
|
|
|
if len(req.ServiceTags) == 0 || len(out.Nodes) == 0 {
|
|
|
|
return *out
|
|
|
|
}
|
|
|
|
tags := make([]string, 0, len(req.ServiceTags))
|
|
|
|
for _, r := range req.ServiceTags {
|
|
|
|
// DNS has the bad habit to setting [""] for ServiceTags
|
|
|
|
if r != "" {
|
|
|
|
tags = append(tags, strings.ToLower(r))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// No need to filter
|
|
|
|
if len(tags) == 0 {
|
|
|
|
return *out
|
|
|
|
}
|
|
|
|
results := make(structs.CheckServiceNodes, 0, len(out.Nodes))
|
|
|
|
for _, service := range out.Nodes {
|
|
|
|
svc := service.Service
|
|
|
|
if !serviceTagsFilter(svc, tags) {
|
|
|
|
results = append(results, service)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
out.Nodes = results
|
|
|
|
return *out
|
|
|
|
}
|
|
|
|
|
|
|
|
// serviceTagsFilter return true if service does not contains all the given tags
|
|
|
|
func serviceTagsFilter(sn *structs.NodeService, tags []string) bool {
|
|
|
|
for _, tag := range tags {
|
|
|
|
if serviceTagFilter(sn, tag) {
|
|
|
|
// If any one of the expected tags was not found, filter the service
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If all tags were found, don't filter the service
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// serviceTagFilter returns true (should filter) if the given service node
|
|
|
|
// doesn't contain the given tag.
|
|
|
|
func serviceTagFilter(sn *structs.NodeService, tag string) bool {
|
|
|
|
// Look for the lower cased version of the tag.
|
|
|
|
for _, t := range sn.Tags {
|
|
|
|
if strings.ToLower(t) == tag {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we didn't hit the tag above then we should filter.
|
|
|
|
return true
|
2020-09-30 21:38:13 +00:00
|
|
|
}
|
2021-02-08 16:53:18 +00:00
|
|
|
|
|
|
|
func filterNodeMeta(out *structs.IndexedCheckServiceNodes, req structs.ServiceSpecificRequest) *structs.IndexedCheckServiceNodes {
|
|
|
|
if len(req.NodeMetaFilters) == 0 || len(out.Nodes) == 0 {
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
results := make(structs.CheckServiceNodes, 0, len(out.Nodes))
|
|
|
|
for _, service := range out.Nodes {
|
|
|
|
serviceNode := service.Node
|
|
|
|
if structs.SatisfiesMetaFilters(serviceNode.Meta, req.NodeMetaFilters) {
|
|
|
|
results = append(results, service)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
out.Nodes = results
|
|
|
|
return out
|
|
|
|
}
|