Commit 3fc5f455 by Marcus Efraimsson Committed by Arve Knudsen

CloudWatch: Fix high CPU load (#20579)

* Cache decrypted securejsondata
* Models: Add datasource cache tests
parent 29b46f7a
...@@ -463,6 +463,7 @@ func TestDSRouteRule(t *testing.T) { ...@@ -463,6 +463,7 @@ func TestDSRouteRule(t *testing.T) {
createAuthTest(m.DS_ES, AUTHTYPE_BASIC, AUTHCHECK_HEADER, true), createAuthTest(m.DS_ES, AUTHTYPE_BASIC, AUTHCHECK_HEADER, true),
} }
for _, test := range tests { for _, test := range tests {
m.ClearDSDecryptionCache()
runDatasourceAuthTest(test) runDatasourceAuthTest(test)
} }
}) })
......
...@@ -76,7 +76,7 @@ func (ds *DataSource) DecryptedPassword() string { ...@@ -76,7 +76,7 @@ func (ds *DataSource) DecryptedPassword() string {
// decryptedValue returns decrypted value from secureJsonData // decryptedValue returns decrypted value from secureJsonData
func (ds *DataSource) decryptedValue(field string, fallback string) string { func (ds *DataSource) decryptedValue(field string, fallback string) string {
if value, ok := ds.SecureJsonData.DecryptedValue(field); ok { if value, ok := ds.DecryptedValue(field); ok {
return value return value
} }
return fallback return fallback
......
...@@ -162,3 +162,49 @@ func (ds *DataSource) getCustomHeaders() map[string]string { ...@@ -162,3 +162,49 @@ func (ds *DataSource) getCustomHeaders() map[string]string {
return headers 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)
}
...@@ -11,15 +11,16 @@ import ( ...@@ -11,15 +11,16 @@ import (
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
//nolint:goconst //nolint:goconst
func TestDataSourceCache(t *testing.T) { func TestDataSourceProxyCache(t *testing.T) {
Convey("When caching a datasource proxy", t, func() { Convey("When caching a datasource proxy", t, func() {
clearCache() clearDSProxyCache()
ds := DataSource{ ds := DataSource{
Id: 1, Id: 1,
Url: "http://k8s:8001", Url: "http://k8s:8001",
...@@ -41,13 +42,13 @@ func TestDataSourceCache(t *testing.T) { ...@@ -41,13 +42,13 @@ func TestDataSourceCache(t *testing.T) {
Convey("Should have no TLS client certificate configured", func() { Convey("Should have no TLS client certificate configured", func() {
So(len(t1.transport.TLSClientConfig.Certificates), ShouldEqual, 0) So(len(t1.transport.TLSClientConfig.Certificates), ShouldEqual, 0)
}) })
Convey("Should have no user-supplied TLS CA onfigured", func() { Convey("Should have no user-supplied TLS CA configured", func() {
So(t1.transport.TLSClientConfig.RootCAs, ShouldBeNil) So(t1.transport.TLSClientConfig.RootCAs, ShouldBeNil)
}) })
}) })
Convey("When caching a datasource proxy then updating it", t, func() { Convey("When caching a datasource proxy then updating it", t, func() {
clearCache() clearDSProxyCache()
setting.SecretKey = "password" setting.SecretKey = "password"
json := simplejson.New() json := simplejson.New()
...@@ -89,7 +90,7 @@ func TestDataSourceCache(t *testing.T) { ...@@ -89,7 +90,7 @@ func TestDataSourceCache(t *testing.T) {
}) })
Convey("When caching a datasource proxy with TLS client authentication enabled", t, func() { Convey("When caching a datasource proxy with TLS client authentication enabled", t, func() {
clearCache() clearDSProxyCache()
setting.SecretKey = "password" setting.SecretKey = "password"
json := simplejson.New() json := simplejson.New()
...@@ -123,7 +124,7 @@ func TestDataSourceCache(t *testing.T) { ...@@ -123,7 +124,7 @@ func TestDataSourceCache(t *testing.T) {
}) })
Convey("When caching a datasource proxy with a user-supplied TLS CA", t, func() { Convey("When caching a datasource proxy with a user-supplied TLS CA", t, func() {
clearCache() clearDSProxyCache()
setting.SecretKey = "password" setting.SecretKey = "password"
json := simplejson.New() json := simplejson.New()
...@@ -152,7 +153,7 @@ func TestDataSourceCache(t *testing.T) { ...@@ -152,7 +153,7 @@ func TestDataSourceCache(t *testing.T) {
}) })
Convey("When caching a datasource proxy when user skips TLS verification", t, func() { Convey("When caching a datasource proxy when user skips TLS verification", t, func() {
clearCache() clearDSProxyCache()
json := simplejson.New() json := simplejson.New()
json.Set("tlsSkipVerify", true) json.Set("tlsSkipVerify", true)
...@@ -173,7 +174,7 @@ func TestDataSourceCache(t *testing.T) { ...@@ -173,7 +174,7 @@ func TestDataSourceCache(t *testing.T) {
}) })
Convey("When caching a datasource proxy with custom headers specified", t, func() { Convey("When caching a datasource proxy with custom headers specified", t, func() {
clearCache() clearDSProxyCache()
json := simplejson.NewFromAny(map[string]interface{}{ json := simplejson.NewFromAny(map[string]interface{}{
"httpHeaderName1": "Authorization", "httpHeaderName1": "Authorization",
...@@ -236,7 +237,64 @@ func TestDataSourceCache(t *testing.T) { ...@@ -236,7 +237,64 @@ func TestDataSourceCache(t *testing.T) {
}) })
} }
func clearCache() { func TestDataSourceDecryptionCache(t *testing.T) {
Convey("When datasource hasn't been updated, encrypted JSON should be fetched from cache", t, func() {
ClearDSDecryptionCache()
ds := DataSource{
Id: 1,
Type: DS_INFLUXDB_08,
JsonData: simplejson.New(),
User: "user",
SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{
"password": "password",
}),
}
// Populate cache
password, ok := ds.DecryptedValue("password")
So(password, ShouldEqual, "password")
So(ok, ShouldBeTrue)
ds.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{
"password": "",
})
password, ok = ds.DecryptedValue("password")
So(password, ShouldEqual, "password")
So(ok, ShouldBeTrue)
})
Convey("When datasource is updated, encrypted JSON should not be fetched from cache", t, func() {
ClearDSDecryptionCache()
ds := DataSource{
Id: 1,
Type: DS_INFLUXDB_08,
JsonData: simplejson.New(),
User: "user",
SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{
"password": "password",
}),
}
// Populate cache
password, ok := ds.DecryptedValue("password")
So(password, ShouldEqual, "password")
So(ok, ShouldBeTrue)
ds.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{
"password": "",
})
ds.Updated = time.Now()
password, ok = ds.DecryptedValue("password")
So(password, ShouldEqual, "")
So(ok, ShouldBeTrue)
})
}
func clearDSProxyCache() {
ptc.Lock() ptc.Lock()
defer ptc.Unlock() defer ptc.Unlock()
......
...@@ -149,16 +149,9 @@ func (e *CloudWatchExecutor) getDsInfo(region string) *DatasourceInfo { ...@@ -149,16 +149,9 @@ func (e *CloudWatchExecutor) getDsInfo(region string) *DatasourceInfo {
authType := e.DataSource.JsonData.Get("authType").MustString() authType := e.DataSource.JsonData.Get("authType").MustString()
assumeRoleArn := e.DataSource.JsonData.Get("assumeRoleArn").MustString() assumeRoleArn := e.DataSource.JsonData.Get("assumeRoleArn").MustString()
accessKey := "" decrypted := e.DataSource.DecryptedValues()
secretKey := "" accessKey := decrypted["accessKey"]
for key, value := range e.DataSource.SecureJsonData.Decrypt() { secretKey := decrypted["secretKey"]
if key == "accessKey" {
accessKey = value
}
if key == "secretKey" {
secretKey = value
}
}
datasourceInfo := &DatasourceInfo{ datasourceInfo := &DatasourceInfo{
Region: region, Region: region,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment