Files
grafana/pkg/models/datasource_cache.go
Marcus Efraimsson a6b2e1865c Datasource: Improve default timeout settings for HTTP client provider (#36621)
Make sure that default timeout settings are based on configuration
parameters. This now applies for core data sources using old TSDB
contracts and new SDK contracts. Before it was only applied for old TSDB
contracts.
Also moves global setting variables to non-global (setting.Cfg).
2021-07-15 14:30:06 +02:00

270 lines
7.1 KiB
Go

package models
import (
"crypto/tls"
"fmt"
"net/http"
"strconv"
"sync"
"time"
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/httpclient"
)
func (ds *DataSource) getTimeout() time.Duration {
timeout := 0
if ds.JsonData != nil {
timeout = ds.JsonData.Get("timeout").MustInt()
if timeout <= 0 {
if timeoutStr := ds.JsonData.Get("timeout").MustString(); timeoutStr != "" {
if t, err := strconv.Atoi(timeoutStr); err == nil {
timeout = t
}
}
}
}
if timeout <= 0 {
return sdkhttpclient.DefaultTimeoutOptions.Timeout
}
return time.Duration(timeout) * time.Second
}
type proxyTransportCache struct {
cache map[int64]cachedRoundTripper
sync.Mutex
}
type cachedRoundTripper struct {
updated time.Time
roundTripper http.RoundTripper
}
var ptc = proxyTransportCache{
cache: make(map[int64]cachedRoundTripper),
}
func (ds *DataSource) GetHTTPClient(provider httpclient.Provider) (*http.Client, error) {
transport, err := ds.GetHTTPTransport(provider)
if err != nil {
return nil, err
}
return &http.Client{
Timeout: ds.getTimeout(),
Transport: transport,
}, nil
}
func (ds *DataSource) GetHTTPTransport(provider httpclient.Provider, customMiddlewares ...sdkhttpclient.Middleware) (http.RoundTripper, error) {
ptc.Lock()
defer ptc.Unlock()
if t, present := ptc.cache[ds.Id]; present && ds.Updated.Equal(t.updated) {
return t.roundTripper, nil
}
opts := ds.HTTPClientOptions()
opts.Middlewares = customMiddlewares
rt, err := provider.GetTransport(opts)
if err != nil {
return nil, err
}
ptc.cache[ds.Id] = cachedRoundTripper{
roundTripper: rt,
updated: ds.Updated,
}
return rt, nil
}
func (ds *DataSource) HTTPClientOptions() sdkhttpclient.Options {
tlsOptions := ds.TLSOptions()
timeouts := &sdkhttpclient.TimeoutOptions{
Timeout: ds.getTimeout(),
DialTimeout: sdkhttpclient.DefaultTimeoutOptions.DialTimeout,
KeepAlive: sdkhttpclient.DefaultTimeoutOptions.KeepAlive,
TLSHandshakeTimeout: sdkhttpclient.DefaultTimeoutOptions.TLSHandshakeTimeout,
ExpectContinueTimeout: sdkhttpclient.DefaultTimeoutOptions.ExpectContinueTimeout,
MaxConnsPerHost: sdkhttpclient.DefaultTimeoutOptions.MaxConnsPerHost,
MaxIdleConns: sdkhttpclient.DefaultTimeoutOptions.MaxIdleConns,
MaxIdleConnsPerHost: sdkhttpclient.DefaultTimeoutOptions.MaxIdleConnsPerHost,
IdleConnTimeout: sdkhttpclient.DefaultTimeoutOptions.IdleConnTimeout,
}
opts := sdkhttpclient.Options{
Timeouts: timeouts,
Headers: getCustomHeaders(ds.JsonData, ds.DecryptedValues()),
Labels: map[string]string{
"datasource_name": ds.Name,
"datasource_uid": ds.Uid,
},
TLS: &tlsOptions,
}
if ds.JsonData != nil {
opts.CustomOptions = ds.JsonData.MustMap()
}
if ds.BasicAuth {
opts.BasicAuth = &sdkhttpclient.BasicAuthOptions{
User: ds.BasicAuthUser,
Password: ds.DecryptedBasicAuthPassword(),
}
} else if ds.User != "" {
opts.BasicAuth = &sdkhttpclient.BasicAuthOptions{
User: ds.User,
Password: ds.DecryptedPassword(),
}
}
if ds.JsonData != nil && ds.JsonData.Get("sigV4Auth").MustBool(false) {
opts.SigV4 = &sdkhttpclient.SigV4Config{
Service: awsServiceNamespace(ds.Type),
Region: ds.JsonData.Get("sigV4Region").MustString(),
AssumeRoleARN: ds.JsonData.Get("sigV4AssumeRoleArn").MustString(),
AuthType: ds.JsonData.Get("sigV4AuthType").MustString(),
ExternalID: ds.JsonData.Get("sigV4ExternalId").MustString(),
Profile: ds.JsonData.Get("sigV4Profile").MustString(),
}
if val, exists := ds.DecryptedValue("sigV4AccessKey"); exists {
opts.SigV4.AccessKey = val
}
if val, exists := ds.DecryptedValue("sigV4SecretKey"); exists {
opts.SigV4.SecretKey = val
}
}
return opts
}
func (ds *DataSource) TLSOptions() sdkhttpclient.TLSOptions {
var tlsSkipVerify, tlsClientAuth, tlsAuthWithCACert bool
var serverName string
if ds.JsonData != nil {
tlsClientAuth = ds.JsonData.Get("tlsAuth").MustBool(false)
tlsAuthWithCACert = ds.JsonData.Get("tlsAuthWithCACert").MustBool(false)
tlsSkipVerify = ds.JsonData.Get("tlsSkipVerify").MustBool(false)
serverName = ds.JsonData.Get("serverName").MustString()
}
opts := sdkhttpclient.TLSOptions{
InsecureSkipVerify: tlsSkipVerify,
ServerName: serverName,
}
if tlsClientAuth || tlsAuthWithCACert {
if tlsAuthWithCACert {
if val, exists := ds.DecryptedValue("tlsCACert"); exists && len(val) > 0 {
opts.CACertificate = val
}
}
if tlsClientAuth {
if val, exists := ds.DecryptedValue("tlsClientCert"); exists && len(val) > 0 {
opts.ClientCertificate = val
}
if val, exists := ds.DecryptedValue("tlsClientKey"); exists && len(val) > 0 {
opts.ClientKey = val
}
}
}
return opts
}
func (ds *DataSource) GetTLSConfig(httpClientProvider httpclient.Provider) (*tls.Config, error) {
return httpClientProvider.GetTLSConfig(ds.HTTPClientOptions())
}
// getCustomHeaders returns a map with all the to be set headers
// The map key represents the HeaderName and the value represents this header's value
func getCustomHeaders(jsonData *simplejson.Json, decryptedValues map[string]string) map[string]string {
headers := make(map[string]string)
if jsonData == nil {
return headers
}
index := 1
for {
headerNameSuffix := fmt.Sprintf("httpHeaderName%d", index)
headerValueSuffix := fmt.Sprintf("httpHeaderValue%d", index)
key := jsonData.Get(headerNameSuffix).MustString()
if key == "" {
// No (more) header values are available
break
}
if val, ok := decryptedValues[headerValueSuffix]; ok {
headers[key] = val
}
index++
}
return headers
}
type cachedDecryptedJSON struct {
updated time.Time
json map[string]string
}
type secureJSONDecryptionCache struct {
cache map[int64]cachedDecryptedJSON
sync.Mutex
}
var dsDecryptionCache = secureJSONDecryptionCache{
cache: make(map[int64]cachedDecryptedJSON),
}
// DecryptedValues returns cached decrypted values from secureJsonData.
func (ds *DataSource) DecryptedValues() map[string]string {
dsDecryptionCache.Lock()
defer dsDecryptionCache.Unlock()
if item, present := dsDecryptionCache.cache[ds.Id]; present && ds.Updated.Equal(item.updated) {
return item.json
}
json := ds.SecureJsonData.Decrypt()
dsDecryptionCache.cache[ds.Id] = cachedDecryptedJSON{
updated: ds.Updated,
json: json,
}
return json
}
// DecryptedValue returns cached decrypted value from cached secureJsonData.
func (ds *DataSource) DecryptedValue(key string) (string, bool) {
value, exists := ds.DecryptedValues()[key]
return value, exists
}
// ClearDSDecryptionCache clears the datasource decryption cache.
func ClearDSDecryptionCache() {
dsDecryptionCache.Lock()
defer dsDecryptionCache.Unlock()
dsDecryptionCache.cache = make(map[int64]cachedDecryptedJSON)
}
func awsServiceNamespace(dsType string) string {
switch dsType {
case DS_ES, DS_ES_OPEN_DISTRO:
return "es"
case DS_PROMETHEUS:
return "aps"
default:
panic(fmt.Sprintf("Unsupported datasource %q", dsType))
}
}