VictoriaMetrics/lib/promscrape/discovery/consul/service_node.go

163 lines
4.5 KiB
Go
Raw Normal View History

package consul
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
)
// getServiceNodesLabels returns labels for Consul service nodes with given cfg.
func getServiceNodesLabels(cfg *apiConfig) []*promutils.Labels {
sns := cfg.consulWatcher.getServiceNodesSnapshot()
var ms []*promutils.Labels
for svc, sn := range sns {
for i := range sn {
ms = sn[i].appendTargetLabels(ms, svc, cfg.tagSeparator)
}
}
return ms
}
// ServiceNode is Consul service node.
//
// See https://www.consul.io/api/health.html#list-nodes-for-service
type ServiceNode struct {
Service Service
Node Node
Checks []Check
}
// Service is Consul service.
//
// See https://www.consul.io/api/health.html#list-nodes-for-service
type Service struct {
ID string
Service string
Address string
Namespace string
Partition string
Port int
Tags []string
Meta map[string]string
}
// Node is Consul node.
//
// See https://www.consul.io/api/health.html#list-nodes-for-service
type Node struct {
Address string
Datacenter string
Node string
Meta map[string]string
TaggedAddresses map[string]string
}
// Check is Consul check.
//
// See https://www.consul.io/api/health.html#list-nodes-for-service
type Check struct {
CheckID string
Status string
}
func parseServiceNodes(data []byte) ([]ServiceNode, error) {
var sns []ServiceNode
if err := json.Unmarshal(data, &sns); err != nil {
return nil, fmt.Errorf("cannot unmarshal ServiceNodes from %q: %w", data, err)
}
return sns, nil
}
func (sn *ServiceNode) appendTargetLabels(ms []*promutils.Labels, serviceName, tagSeparator string) []*promutils.Labels {
var addr string
if sn.Service.Address != "" {
addr = discoveryutils.JoinHostPort(sn.Service.Address, sn.Service.Port)
} else {
addr = discoveryutils.JoinHostPort(sn.Node.Address, sn.Service.Port)
}
m := promutils.NewLabels(16)
m.Add("__address__", addr)
m.Add("__meta_consul_address", sn.Node.Address)
m.Add("__meta_consul_dc", sn.Node.Datacenter)
m.Add("__meta_consul_health", aggregatedStatus(sn.Checks))
m.Add("__meta_consul_namespace", sn.Service.Namespace)
m.Add("__meta_consul_partition", sn.Service.Partition)
m.Add("__meta_consul_node", sn.Node.Node)
m.Add("__meta_consul_service", serviceName)
m.Add("__meta_consul_service_address", sn.Service.Address)
m.Add("__meta_consul_service_id", sn.Service.ID)
m.Add("__meta_consul_service_port", strconv.Itoa(sn.Service.Port))
// We surround the separated list with the separator as well. This way regular expressions
// in relabeling rules don't have to consider tag positions.
m.Add("__meta_consul_tags", tagSeparator+strings.Join(sn.Service.Tags, tagSeparator)+tagSeparator)
// Expose individual tags via __meta_consul_tag_* labels, so users could move all the tags
// into the discovered scrape target with the following relabeling rule in the way similar to kubernetes_sd_configs:
//
// - action: labelmap
// regex: __meta_consul_tag_(.+)
//
// This solves https://stackoverflow.com/questions/44339461/relabeling-in-prometheus
for _, tag := range sn.Service.Tags {
k := tag
v := ""
if n := strings.IndexByte(tag, '='); n >= 0 {
k = tag[:n]
v = tag[n+1:]
}
m.Add(discoveryutils.SanitizeLabelName("__meta_consul_tag_"+k), v)
m.Add(discoveryutils.SanitizeLabelName("__meta_consul_tagpresent_"+k), "true")
}
for k, v := range sn.Node.Meta {
m.Add(discoveryutils.SanitizeLabelName("__meta_consul_metadata_"+k), v)
}
for k, v := range sn.Service.Meta {
m.Add(discoveryutils.SanitizeLabelName("__meta_consul_service_metadata_"+k), v)
}
for k, v := range sn.Node.TaggedAddresses {
m.Add(discoveryutils.SanitizeLabelName("__meta_consul_tagged_address_"+k), v)
}
ms = append(ms, m)
return ms
}
func aggregatedStatus(checks []Check) string {
// The code has been copy-pasted from HealthChecks.AggregatedStatus in Consul
var passing, warning, critical, maintenance bool
for _, check := range checks {
id := check.CheckID
if id == "_node_maintenance" || strings.HasPrefix(id, "_service_maintenance:") {
maintenance = true
continue
}
switch check.Status {
case "passing":
passing = true
case "warning":
warning = true
case "critical":
critical = true
default:
return ""
}
}
switch {
case maintenance:
return "maintenance"
case critical:
return "critical"
case warning:
return "warning"
case passing:
return "passing"
default:
return "passing"
}
}