mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
c6dee6c52d
* adds consul watch api, it must reduce load on consul service with blocking wait requests, changed discoveryClient api with fetchResponseMeta callback. * small fix * fix after master merge * adds watch client at discovery utils * fixes consul watcher, changes namings, fixes data race * small typo fix * sanity fix * fix naming and service node update
162 lines
4.3 KiB
Go
162 lines
4.3 KiB
Go
package consul
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
|
"github.com/VictoriaMetrics/fasthttp"
|
|
)
|
|
|
|
// apiConfig contains config for API server
|
|
// with consulWatch service.
|
|
type apiConfig struct {
|
|
tagSeparator string
|
|
consulWatcher *consulWatch
|
|
}
|
|
|
|
var configMap = discoveryutils.NewConfigMap()
|
|
|
|
func getAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
|
|
v, err := configMap.Get(sdc, func() (interface{}, error) { return newAPIConfig(sdc, baseDir) })
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return v.(*apiConfig), nil
|
|
}
|
|
|
|
func newAPIConfig(sdc *SDConfig, baseDir string) (*apiConfig, error) {
|
|
token, err := getToken(sdc.Token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var ba *promauth.BasicAuthConfig
|
|
if len(sdc.Username) > 0 {
|
|
ba = &promauth.BasicAuthConfig{
|
|
Username: sdc.Username,
|
|
Password: sdc.Password,
|
|
}
|
|
token = ""
|
|
}
|
|
ac, err := promauth.NewConfig(baseDir, ba, token, "", sdc.TLSConfig)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot parse auth config: %w", err)
|
|
}
|
|
apiServer := sdc.Server
|
|
if apiServer == "" {
|
|
apiServer = "localhost:8500"
|
|
}
|
|
if !strings.Contains(apiServer, "://") {
|
|
scheme := sdc.Scheme
|
|
if scheme == "" {
|
|
scheme = "http"
|
|
}
|
|
apiServer = scheme + "://" + apiServer
|
|
}
|
|
client, err := discoveryutils.NewClient(apiServer, ac)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot create HTTP client for %q: %w", apiServer, err)
|
|
}
|
|
tagSeparator := ","
|
|
if sdc.TagSeparator != nil {
|
|
tagSeparator = *sdc.TagSeparator
|
|
}
|
|
dc, err := getDatacenter(client, sdc.Datacenter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cw, err := newConsulWatch(client, sdc, dc)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot start consul watcher: %w", err)
|
|
}
|
|
cfg := &apiConfig{
|
|
tagSeparator: tagSeparator,
|
|
consulWatcher: cw,
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
func getToken(token *string) (string, error) {
|
|
if token != nil {
|
|
return *token, nil
|
|
}
|
|
if tokenFile := os.Getenv("CONSUL_HTTP_TOKEN_FILE"); tokenFile != "" {
|
|
data, err := ioutil.ReadFile(tokenFile)
|
|
if err != nil {
|
|
return "", fmt.Errorf("cannot read consul token file %q; probably, `token` arg is missing in `consul_sd_config`? error: %w", tokenFile, err)
|
|
}
|
|
return string(data), nil
|
|
}
|
|
t := os.Getenv("CONSUL_HTTP_TOKEN")
|
|
// Allow empty token - it shouls work if authorization is disabled in Consul
|
|
return t, nil
|
|
}
|
|
|
|
func getDatacenter(client *discoveryutils.Client, dc string) (string, error) {
|
|
if dc != "" {
|
|
return dc, nil
|
|
}
|
|
// See https://www.consul.io/api/agent.html#read-configuration
|
|
data, err := client.GetAPIResponse("/v1/agent/self")
|
|
if err != nil {
|
|
return "", fmt.Errorf("cannot query consul agent info: %w", err)
|
|
}
|
|
a, err := parseAgent(data)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return a.Config.Datacenter, nil
|
|
}
|
|
|
|
// returns ServiceNodesState and version index.
|
|
func getServiceState(client *discoveryutils.Client, svc, baseArgs string, index uint64) ([]ServiceNode, uint64, error) {
|
|
path := fmt.Sprintf("/v1/health/service/%s%s", svc, baseArgs)
|
|
|
|
data, newIndex, err := getBlockingAPIResponse(client, path, index)
|
|
if err != nil {
|
|
return nil, index, err
|
|
}
|
|
sns, err := parseServiceNodes(data)
|
|
if err != nil {
|
|
return nil, index, err
|
|
}
|
|
return sns, newIndex, nil
|
|
}
|
|
|
|
// returns consul api response with new index version of object.
|
|
// https://www.consul.io/api-docs/features/blocking
|
|
func getBlockingAPIResponse(client *discoveryutils.Client, path string, index uint64) ([]byte, uint64, error) {
|
|
path += "&index=" + strconv.FormatUint(index, 10)
|
|
path = path + fmt.Sprintf("&wait=%s", watchTime)
|
|
getMeta := func(resp *fasthttp.Response) {
|
|
if ind := resp.Header.Peek("X-Consul-Index"); len(ind) > 0 {
|
|
newIndex, err := strconv.ParseUint(string(ind), 10, 64)
|
|
if err != nil {
|
|
logger.Errorf("failed to parse consul index: %v", err)
|
|
return
|
|
}
|
|
// reset index
|
|
// https://www.consul.io/api-docs/features/blocking#implementation-details
|
|
if newIndex < 1 {
|
|
index = 1
|
|
return
|
|
}
|
|
if index > newIndex {
|
|
index = 0
|
|
return
|
|
}
|
|
index = newIndex
|
|
}
|
|
}
|
|
data, err := client.GetBlockingAPIResponse(path, getMeta)
|
|
if err != nil {
|
|
return nil, index, fmt.Errorf("failed query consul api path=%q, err=%w", path, err)
|
|
}
|
|
return data, index, nil
|
|
}
|