SQL: Migrate to use SDK contracts (#36635)

* convert SQLs to use sdk contracts

* make draft

* postgres

* intermedia

* get datasourceinfo filled at the beginning of the service

* move the interval into package because of cyclict  import and fix all postgres tests

* fix mysql test

* fix mssql

* fix the test for pr https://github.com/grafana/grafana/issues/35839

* fix some issue about intervalv2 package

* update sql test

* wire migration for SQLs

* add sqls to the background process

* make it register instead of register and start

* revert formatting

* fix tests

* fix linter

* remove integration test

* Postgres test fix

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
This commit is contained in:
ying-jeanne
2021-09-07 09:35:37 +02:00
committed by GitHub
co-authored by Marcus Efraimsson
parent 51aeb717a4
commit c989f0fbbe
40 changed files with 1632 additions and 1495 deletions
+10 -11
View File
@@ -6,8 +6,8 @@ import (
"strings"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/components/gtime"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
)
@@ -26,8 +26,7 @@ func newPostgresMacroEngine(timescaledb bool) sqleng.SQLMacroEngine {
}
}
func (m *postgresMacroEngine) Interpolate(query plugins.DataSubQuery, timeRange plugins.DataTimeRange,
sql string) (string, error) {
func (m *postgresMacroEngine) Interpolate(query *backend.DataQuery, timeRange backend.TimeRange, sql string) (string, error) {
// TODO: Handle error
rExp, _ := regexp.Compile(sExpr)
var macroError error
@@ -67,7 +66,7 @@ func (m *postgresMacroEngine) Interpolate(query plugins.DataSubQuery, timeRange
}
//nolint: gocyclo
func (m *postgresMacroEngine) evaluateMacro(timeRange plugins.DataTimeRange, query plugins.DataSubQuery, name string, args []string) (string, error) {
func (m *postgresMacroEngine) evaluateMacro(timeRange backend.TimeRange, query *backend.DataQuery, name string, args []string) (string, error) {
switch name {
case "__time":
if len(args) == 0 {
@@ -84,11 +83,11 @@ func (m *postgresMacroEngine) evaluateMacro(timeRange plugins.DataTimeRange, que
return "", fmt.Errorf("missing time column argument for macro %v", name)
}
return fmt.Sprintf("%s BETWEEN '%s' AND '%s'", args[0], timeRange.GetFromAsTimeUTC().Format(time.RFC3339Nano), timeRange.GetToAsTimeUTC().Format(time.RFC3339Nano)), nil
return fmt.Sprintf("%s BETWEEN '%s' AND '%s'", args[0], timeRange.From.UTC().Format(time.RFC3339Nano), timeRange.To.UTC().Format(time.RFC3339Nano)), nil
case "__timeFrom":
return fmt.Sprintf("'%s'", timeRange.GetFromAsTimeUTC().Format(time.RFC3339Nano)), nil
return fmt.Sprintf("'%s'", timeRange.From.UTC().Format(time.RFC3339Nano)), nil
case "__timeTo":
return fmt.Sprintf("'%s'", timeRange.GetToAsTimeUTC().Format(time.RFC3339Nano)), nil
return fmt.Sprintf("'%s'", timeRange.To.UTC().Format(time.RFC3339Nano)), nil
case "__timeGroup":
if len(args) < 2 {
return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name)
@@ -123,16 +122,16 @@ func (m *postgresMacroEngine) evaluateMacro(timeRange plugins.DataTimeRange, que
if len(args) == 0 {
return "", fmt.Errorf("missing time column argument for macro %v", name)
}
return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], timeRange.GetFromAsSecondsEpoch(), args[0], timeRange.GetToAsSecondsEpoch()), nil
return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], timeRange.From.UTC().Unix(), args[0], timeRange.To.UTC().Unix()), nil
case "__unixEpochNanoFilter":
if len(args) == 0 {
return "", fmt.Errorf("missing time column argument for macro %v", name)
}
return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], timeRange.GetFromAsTimeUTC().UnixNano(), args[0], timeRange.GetToAsTimeUTC().UnixNano()), nil
return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], timeRange.From.UTC().UnixNano(), args[0], timeRange.To.UTC().UnixNano()), nil
case "__unixEpochNanoFrom":
return fmt.Sprintf("%d", timeRange.GetFromAsTimeUTC().UnixNano()), nil
return fmt.Sprintf("%d", timeRange.From.UTC().UnixNano()), nil
case "__unixEpochNanoTo":
return fmt.Sprintf("%d", timeRange.GetToAsTimeUTC().UnixNano()), nil
return fmt.Sprintf("%d", timeRange.To.UTC().UnixNano()), nil
case "__unixEpochGroup":
if len(args) < 2 {
return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name)
+24 -23
View File
@@ -2,13 +2,11 @@ package postgres
import (
"fmt"
"strconv"
"sync"
"testing"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/stretchr/testify/require"
)
@@ -17,12 +15,12 @@ func TestMacroEngine(t *testing.T) {
engine := newPostgresMacroEngine(timescaledbEnabled)
timescaledbEnabled = true
engineTS := newPostgresMacroEngine(timescaledbEnabled)
query := plugins.DataSubQuery{}
query := &backend.DataQuery{}
t.Run("Given a time range between 2018-04-12 00:00 and 2018-04-12 00:05", func(t *testing.T) {
from := time.Date(2018, 4, 12, 18, 0, 0, 0, time.UTC)
to := from.Add(5 * time.Minute)
timeRange := plugins.DataTimeRange{From: "5m", To: "now", Now: to}
timeRange := backend.TimeRange{From: from, To: to}
t.Run("interpolate __time function", func(t *testing.T) {
sql, err := engine.Interpolate(query, timeRange, "select $__time(time_column)")
@@ -151,9 +149,10 @@ func TestMacroEngine(t *testing.T) {
t.Run("Given a time range between 1960-02-01 07:00 and 1965-02-03 08:00", func(t *testing.T) {
from := time.Date(1960, 2, 1, 7, 0, 0, 0, time.UTC)
to := time.Date(1965, 2, 3, 8, 0, 0, 0, time.UTC)
timeRange := plugins.NewDataTimeRange(
strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10),
strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10))
timeRange := backend.TimeRange{
From: from,
To: to,
}
t.Run("interpolate __timeFilter function", func(t *testing.T) {
sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
@@ -177,9 +176,10 @@ func TestMacroEngine(t *testing.T) {
t.Run("Given a time range between 1960-02-01 07:00 and 1980-02-03 08:00", func(t *testing.T) {
from := time.Date(1960, 2, 1, 7, 0, 0, 0, time.UTC)
to := time.Date(1980, 2, 3, 8, 0, 0, 0, time.UTC)
timeRange := plugins.NewDataTimeRange(
strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10),
strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10))
timeRange := backend.TimeRange{
From: from,
To: to,
}
t.Run("interpolate __timeFilter function", func(t *testing.T) {
sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)")
@@ -203,9 +203,10 @@ func TestMacroEngine(t *testing.T) {
t.Run("Given a time range between 1960-02-01 07:00:00.5 and 1980-02-03 08:00:00.5", func(t *testing.T) {
from := time.Date(1960, 2, 1, 7, 0, 0, 500e6, time.UTC)
to := time.Date(1980, 2, 3, 8, 0, 0, 500e6, time.UTC)
timeRange := plugins.NewDataTimeRange(
strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10))
timeRange := backend.TimeRange{
From: from,
To: to,
}
require.Equal(t, "1960-02-01T07:00:00.5Z", from.Format(time.RFC3339Nano))
require.Equal(t, "1980-02-03T08:00:00.5Z", to.Format(time.RFC3339Nano))
@@ -219,27 +220,27 @@ func TestMacroEngine(t *testing.T) {
func TestMacroEngineConcurrency(t *testing.T) {
engine := newPostgresMacroEngine(false)
query1 := plugins.DataSubQuery{
Model: simplejson.New(),
query1 := backend.DataQuery{
JSON: []byte{},
}
query2 := plugins.DataSubQuery{
Model: simplejson.New(),
query2 := backend.DataQuery{
JSON: []byte{},
}
from := time.Date(2018, 4, 12, 18, 0, 0, 0, time.UTC)
to := from.Add(5 * time.Minute)
timeRange := plugins.DataTimeRange{From: "5m", To: "now", Now: to}
timeRange := backend.TimeRange{From: from, To: to}
var wg sync.WaitGroup
wg.Add(2)
go func(query plugins.DataSubQuery) {
go func(query backend.DataQuery) {
defer wg.Done()
_, err := engine.Interpolate(query, timeRange, "SELECT $__timeGroup(time_column,'5m')")
_, err := engine.Interpolate(&query, timeRange, "SELECT $__timeGroup(time_column,'5m')")
require.NoError(t, err)
}(query1)
go func(query plugins.DataSubQuery) {
_, err := engine.Interpolate(query, timeRange, "SELECT $__timeGroup(time_column,'5m')")
go func(query backend.DataQuery) {
_, err := engine.Interpolate(&query, timeRange, "SELECT $__timeGroup(time_column,'5m')")
require.NoError(t, err)
defer wg.Done()
}(query2)
+99 -47
View File
@@ -1,72 +1,124 @@
package postgres
import (
"context"
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana-plugin-sdk-go/data/sqlutil"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util/errutil"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
)
func ProvideService(cfg *setting.Cfg) *PostgresService {
logger := log.New("tsdb.postgres")
return &PostgresService{
var logger = log.New("tsdb.postgres")
func ProvideService(cfg *setting.Cfg, manager backendplugin.Manager) (*Service, error) {
s := &Service{
Cfg: cfg,
logger: logger,
tlsManager: newTLSManager(logger, cfg.DataPath),
}
s.im = datasource.NewInstanceManager(s.newInstanceSettings())
factory := coreplugin.New(backend.ServeOpts{
QueryDataHandler: s,
})
if err := manager.Register("postgres", factory); err != nil {
logger.Error("Failed to register plugin", "error", err)
}
return s, nil
}
type PostgresService struct {
type Service struct {
Cfg *setting.Cfg
logger log.Logger
tlsManager tlsSettingsProvider
im instancemgmt.InstanceManager
}
//nolint: staticcheck // plugins.DataPlugin deprecated
func (s *PostgresService) NewExecutor(datasource *models.DataSource) (plugins.DataPlugin, error) {
s.logger.Debug("Creating Postgres query endpoint")
cnnstr, err := s.generateConnectionString(datasource)
func (s *Service) getDSInfo(pluginCtx backend.PluginContext) (*sqleng.DataSourceHandler, error) {
i, err := s.im.Get(pluginCtx)
if err != nil {
return nil, err
}
instance := i.(*sqleng.DataSourceHandler)
return instance, nil
}
if s.Cfg.Env == setting.Dev {
s.logger.Debug("getEngine", "connection", cnnstr)
}
config := sqleng.DataPluginConfiguration{
DriverName: "postgres",
ConnectionString: cnnstr,
Datasource: datasource,
MetricColumnTypes: []string{"UNKNOWN", "TEXT", "VARCHAR", "CHAR"},
}
queryResultTransformer := postgresQueryResultTransformer{
log: s.logger,
}
timescaledb := datasource.JsonData.Get("timescaledb").MustBool(false)
plugin, err := sqleng.NewDataPlugin(config, &queryResultTransformer, newPostgresMacroEngine(timescaledb),
s.logger)
func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
dsInfo, err := s.getDSInfo(req.PluginContext)
if err != nil {
s.logger.Error("Failed connecting to Postgres", "err", err)
return nil, err
}
return dsInfo.QueryData(ctx, req)
}
s.logger.Debug("Successfully connected to Postgres")
return plugin, nil
func (s *Service) newInstanceSettings() datasource.InstanceFactoryFunc {
return func(settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
logger.Debug("Creating Postgres query endpoint")
jsonData := sqleng.JsonData{
MaxOpenConns: 0,
MaxIdleConns: 2,
ConnMaxLifetime: 14400,
Timescaledb: false,
ConfigurationMethod: "file-path",
}
err := json.Unmarshal(settings.JSONData, &jsonData)
if err != nil {
return nil, fmt.Errorf("error reading settings: %w", err)
}
dsInfo := sqleng.DataSourceInfo{
JsonData: jsonData,
URL: settings.URL,
User: settings.User,
Database: settings.Database,
ID: settings.ID,
Updated: settings.Updated,
UID: settings.UID,
DecryptedSecureJSONData: settings.DecryptedSecureJSONData,
}
cnnstr, err := s.generateConnectionString(dsInfo)
if err != nil {
return nil, err
}
if s.Cfg.Env == setting.Dev {
logger.Debug("getEngine", "connection", cnnstr)
}
config := sqleng.DataPluginConfiguration{
DriverName: "postgres",
ConnectionString: cnnstr,
DSInfo: dsInfo,
MetricColumnTypes: []string{"UNKNOWN", "TEXT", "VARCHAR", "CHAR"},
}
queryResultTransformer := postgresQueryResultTransformer{
log: logger,
}
handler, err := sqleng.NewQueryDataHandler(config, &queryResultTransformer, newPostgresMacroEngine(dsInfo.JsonData.Timescaledb),
logger)
if err != nil {
logger.Error("Failed connecting to Postgres", "err", err)
return nil, err
}
logger.Debug("Successfully connected to Postgres")
return handler, nil
}
}
// escape single quotes and backslashes in Postgres connection string parameters.
@@ -74,14 +126,14 @@ func escape(input string) string {
return strings.ReplaceAll(strings.ReplaceAll(input, `\`, `\\`), "'", `\'`)
}
func (s *PostgresService) generateConnectionString(datasource *models.DataSource) (string, error) {
func (s *Service) generateConnectionString(dsInfo sqleng.DataSourceInfo) (string, error) {
var host string
var port int
if strings.HasPrefix(datasource.Url, "/") {
host = datasource.Url
s.logger.Debug("Generating connection string with Unix socket specifier", "socket", host)
if strings.HasPrefix(dsInfo.URL, "/") {
host = dsInfo.URL
logger.Debug("Generating connection string with Unix socket specifier", "socket", host)
} else {
sp := strings.SplitN(datasource.Url, ":", 2)
sp := strings.SplitN(dsInfo.URL, ":", 2)
host = sp[0]
if len(sp) > 1 {
var err error
@@ -90,19 +142,19 @@ func (s *PostgresService) generateConnectionString(datasource *models.DataSource
return "", errutil.Wrapf(err, "invalid port in host specifier %q", sp[1])
}
s.logger.Debug("Generating connection string with network host/port pair", "host", host, "port", port)
logger.Debug("Generating connection string with network host/port pair", "host", host, "port", port)
} else {
s.logger.Debug("Generating connection string with network host", "host", host)
logger.Debug("Generating connection string with network host", "host", host)
}
}
connStr := fmt.Sprintf("user='%s' password='%s' host='%s' dbname='%s'",
escape(datasource.User), escape(datasource.DecryptedPassword()), escape(host), escape(datasource.Database))
escape(dsInfo.User), escape(dsInfo.DecryptedSecureJSONData["password"]), escape(host), escape(dsInfo.Database))
if port > 0 {
connStr += fmt.Sprintf(" port=%d", port)
}
tlsSettings, err := s.tlsManager.getTLSSettings(datasource)
tlsSettings, err := s.tlsManager.getTLSSettings(dsInfo)
if err != nil {
return "", err
}
@@ -111,19 +163,19 @@ func (s *PostgresService) generateConnectionString(datasource *models.DataSource
// Attach root certificate if provided
if tlsSettings.RootCertFile != "" {
s.logger.Debug("Setting server root certificate", "tlsRootCert", tlsSettings.RootCertFile)
logger.Debug("Setting server root certificate", "tlsRootCert", tlsSettings.RootCertFile)
connStr += fmt.Sprintf(" sslrootcert='%s'", escape(tlsSettings.RootCertFile))
}
// Attach client certificate and key if both are provided
if tlsSettings.CertFile != "" && tlsSettings.CertKeyFile != "" {
s.logger.Debug("Setting TLS/SSL client auth", "tlsCert", tlsSettings.CertFile, "tlsKey", tlsSettings.CertKeyFile)
logger.Debug("Setting TLS/SSL client auth", "tlsCert", tlsSettings.CertFile, "tlsKey", tlsSettings.CertKeyFile)
connStr += fmt.Sprintf(" sslcert='%s' sslkey='%s'", escape(tlsSettings.CertFile), escape(tlsSettings.CertKeyFile))
} else if tlsSettings.CertFile != "" || tlsSettings.CertKeyFile != "" {
return "", fmt.Errorf("TLS/SSL client certificate and key must both be specified")
}
s.logger.Debug("Generated Postgres connection string successfully")
logger.Debug("Generated Postgres connection string successfully")
return connStr, nil
}
File diff suppressed because it is too large Load Diff
+44 -47
View File
@@ -8,17 +8,26 @@ import (
"strconv"
"strings"
"sync"
"time"
"github.com/grafana/grafana/pkg/infra/fs"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
)
var validateCertFunc = validateCertFilePaths
var writeCertFileFunc = writeCertFile
type certFileType int
const (
rootCert = iota
clientCert
clientKey
)
type tlsSettingsProvider interface {
getTLSSettings(datasource *models.DataSource) (tlsSettings, error)
getTLSSettings(dsInfo sqleng.DataSourceInfo) (tlsSettings, error)
}
type datasourceCacheManager struct {
@@ -48,46 +57,37 @@ type tlsSettings struct {
CertKeyFile string
}
func (m *tlsManager) getTLSSettings(datasource *models.DataSource) (tlsSettings, error) {
tlsMode := strings.TrimSpace(strings.ToLower(datasource.JsonData.Get("sslmode").MustString("verify-full")))
isTLSDisabled := tlsMode == "disable"
func (m *tlsManager) getTLSSettings(dsInfo sqleng.DataSourceInfo) (tlsSettings, error) {
tlsconfig := tlsSettings{
Mode: dsInfo.JsonData.Mode,
}
settings := tlsSettings{}
settings.Mode = tlsMode
isTLSDisabled := (tlsconfig.Mode == "disable")
if isTLSDisabled {
m.logger.Debug("Postgres TLS/SSL is disabled")
return settings, nil
return tlsconfig, nil
}
m.logger.Debug("Postgres TLS/SSL is enabled", "tlsMode", tlsMode)
m.logger.Debug("Postgres TLS/SSL is enabled", "tlsMode", tlsconfig.Mode)
settings.ConfigurationMethod = strings.TrimSpace(
strings.ToLower(datasource.JsonData.Get("tlsConfigurationMethod").MustString("file-path")))
tlsconfig.ConfigurationMethod = dsInfo.JsonData.ConfigurationMethod
tlsconfig.RootCertFile = dsInfo.JsonData.RootCertFile
tlsconfig.CertFile = dsInfo.JsonData.CertFile
tlsconfig.CertKeyFile = dsInfo.JsonData.CertKeyFile
if settings.ConfigurationMethod == "file-content" {
if err := m.writeCertFiles(datasource, &settings); err != nil {
return settings, err
if tlsconfig.ConfigurationMethod == "file-content" {
if err := m.writeCertFiles(dsInfo, &tlsconfig); err != nil {
return tlsconfig, err
}
} else {
settings.RootCertFile = datasource.JsonData.Get("sslRootCertFile").MustString("")
settings.CertFile = datasource.JsonData.Get("sslCertFile").MustString("")
settings.CertKeyFile = datasource.JsonData.Get("sslKeyFile").MustString("")
if err := validateCertFunc(settings.RootCertFile, settings.CertFile, settings.CertKeyFile); err != nil {
return settings, err
if err := validateCertFunc(tlsconfig.RootCertFile, tlsconfig.CertFile, tlsconfig.CertKeyFile); err != nil {
return tlsconfig, err
}
}
return settings, nil
return tlsconfig, nil
}
type certFileType int
const (
rootCert = iota
clientCert
clientKey
)
func (t certFileType) String() string {
switch t {
case rootCert:
@@ -118,8 +118,7 @@ func getFileName(dataDir string, fileType certFileType) string {
}
// writeCertFile writes a certificate file.
func writeCertFile(
ds *models.DataSource, logger log.Logger, fileContent string, generatedFilePath string) error {
func writeCertFile(logger log.Logger, fileContent string, generatedFilePath string) error {
fileContent = strings.TrimSpace(fileContent)
if fileContent != "" {
logger.Debug("Writing cert file", "path", generatedFilePath)
@@ -146,30 +145,28 @@ func writeCertFile(
return nil
}
func (m *tlsManager) writeCertFiles(ds *models.DataSource, settings *tlsSettings) error {
func (m *tlsManager) writeCertFiles(dsInfo sqleng.DataSourceInfo, tlsconfig *tlsSettings) error {
m.logger.Debug("Writing TLS certificate files to disk")
decrypted := ds.DecryptedValues()
tlsRootCert := decrypted["tlsCACert"]
tlsClientCert := decrypted["tlsClientCert"]
tlsClientKey := decrypted["tlsClientKey"]
tlsRootCert := dsInfo.DecryptedSecureJSONData["tlsCACert"]
tlsClientCert := dsInfo.DecryptedSecureJSONData["tlsClientCert"]
tlsClientKey := dsInfo.DecryptedSecureJSONData["tlsClientKey"]
if tlsRootCert == "" && tlsClientCert == "" && tlsClientKey == "" {
m.logger.Debug("No TLS/SSL certificates provided")
}
// Calculate all files path
workDir := filepath.Join(m.dataPath, "tls", ds.Uid+"generatedTLSCerts")
settings.RootCertFile = getFileName(workDir, rootCert)
settings.CertFile = getFileName(workDir, clientCert)
settings.CertKeyFile = getFileName(workDir, clientKey)
workDir := filepath.Join(m.dataPath, "tls", dsInfo.UID+"generatedTLSCerts")
tlsconfig.RootCertFile = getFileName(workDir, rootCert)
tlsconfig.CertFile = getFileName(workDir, clientCert)
tlsconfig.CertKeyFile = getFileName(workDir, clientKey)
// Find datasource in the cache, if found, skip writing files
cacheKey := strconv.Itoa(int(ds.Id))
cacheKey := strconv.Itoa(int(dsInfo.ID))
m.dsCacheInstance.locker.RLock(cacheKey)
item, ok := m.dsCacheInstance.cache.Load(cacheKey)
m.dsCacheInstance.locker.RUnlock(cacheKey)
if ok {
if item.(int) == ds.Version {
if !item.(time.Time).Before(dsInfo.Updated) {
return nil
}
}
@@ -179,7 +176,7 @@ func (m *tlsManager) writeCertFiles(ds *models.DataSource, settings *tlsSettings
item, ok = m.dsCacheInstance.cache.Load(cacheKey)
if ok {
if item.(int) == ds.Version {
if !item.(time.Time).Before(dsInfo.Updated) {
return nil
}
}
@@ -195,18 +192,18 @@ func (m *tlsManager) writeCertFiles(ds *models.DataSource, settings *tlsSettings
}
}
if err = writeCertFileFunc(ds, m.logger, tlsRootCert, settings.RootCertFile); err != nil {
if err = writeCertFileFunc(m.logger, tlsRootCert, tlsconfig.RootCertFile); err != nil {
return err
}
if err = writeCertFileFunc(ds, m.logger, tlsClientCert, settings.CertFile); err != nil {
if err = writeCertFileFunc(m.logger, tlsClientCert, tlsconfig.CertFile); err != nil {
return err
}
if err = writeCertFileFunc(ds, m.logger, tlsClientKey, settings.CertKeyFile); err != nil {
if err = writeCertFileFunc(m.logger, tlsClientKey, tlsconfig.CertKeyFile); err != nil {
return err
}
// Update datasource cache
m.dsCacheInstance.cache.Store(cacheKey, ds.Version)
m.dsCacheInstance.cache.Store(cacheKey, dsInfo.Updated)
return nil
}
+71 -69
View File
@@ -7,12 +7,11 @@ import (
"strings"
"sync"
"testing"
"time"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -30,16 +29,17 @@ func TestDataSourceCacheManager(t *testing.T) {
dsCacheInstance: datasourceCacheManager{locker: newLocker()},
dataPath: cfg.DataPath,
}
jsonData := simplejson.NewFromAny(map[string]interface{}{
"sslmode": "verify-full",
"tlsConfigurationMethod": "file-content",
})
secureJSONData := securejsondata.GetEncryptedJsonData(map[string]string{
jsonData := sqleng.JsonData{
Mode: "verify-full",
ConfigurationMethod: "file-content",
}
secureJSONData := map[string]string{
"tlsClientCert": "I am client certification",
"tlsClientKey": "I am client key",
"tlsCACert": "I am CA certification",
})
}
updateTime := time.Now().Add(-5 * time.Minute)
mockValidateCertFilePaths()
t.Cleanup(resetValidateCertFilePaths)
@@ -49,13 +49,13 @@ func TestDataSourceCacheManager(t *testing.T) {
wg.Add(10)
for id := int64(1); id <= 10; id++ {
go func(id int64) {
ds := &models.DataSource{
Id: id,
Version: 1,
Database: "database",
JsonData: jsonData,
SecureJsonData: secureJSONData,
Uid: "testData",
ds := sqleng.DataSourceInfo{
ID: id,
Updated: updateTime,
Database: "database",
JsonData: jsonData,
DecryptedSecureJSONData: secureJSONData,
UID: "testData",
}
s := tlsSettings{}
err := mng.writeCertFiles(ds, &s)
@@ -67,9 +67,9 @@ func TestDataSourceCacheManager(t *testing.T) {
t.Run("check cache creation is succeed", func(t *testing.T) {
for id := int64(1); id <= 10; id++ {
version, ok := mng.dsCacheInstance.cache.Load(strconv.Itoa(int(id)))
updated, ok := mng.dsCacheInstance.cache.Load(strconv.Itoa(int(id)))
require.True(t, ok)
require.Equal(t, int(1), version)
require.Equal(t, updateTime, updated)
}
})
})
@@ -82,13 +82,13 @@ func TestDataSourceCacheManager(t *testing.T) {
wg1.Add(5)
for id := int64(1); id <= 5; id++ {
go func(id int64) {
ds := &models.DataSource{
Id: 1,
Version: 2,
Database: "database",
JsonData: jsonData,
SecureJsonData: secureJSONData,
Uid: "testData",
ds := sqleng.DataSourceInfo{
ID: 1,
Updated: updateTime,
Database: "database",
JsonData: jsonData,
DecryptedSecureJSONData: secureJSONData,
UID: "testData",
}
s := tlsSettings{}
err := mng.writeCertFiles(ds, &s)
@@ -97,25 +97,25 @@ func TestDataSourceCacheManager(t *testing.T) {
}(id)
}
wg1.Wait()
assert.Equal(t, writeCertFileCallNum, 3)
assert.Equal(t, writeCertFileCallNum, 0)
})
t.Run("cache is updated with the last datasource version", func(t *testing.T) {
dsV2 := &models.DataSource{
Id: 1,
Version: 2,
Database: "database",
JsonData: jsonData,
SecureJsonData: secureJSONData,
Uid: "testData",
dsV2 := sqleng.DataSourceInfo{
ID: 1,
Updated: updateTime.Add(time.Minute),
Database: "database",
JsonData: jsonData,
DecryptedSecureJSONData: secureJSONData,
UID: "testData",
}
dsV3 := &models.DataSource{
Id: 1,
Version: 3,
Database: "database",
JsonData: jsonData,
SecureJsonData: secureJSONData,
Uid: "testData",
dsV3 := sqleng.DataSourceInfo{
ID: 1,
Updated: updateTime.Add(2 * time.Minute),
Database: "database",
JsonData: jsonData,
DecryptedSecureJSONData: secureJSONData,
UID: "testData",
}
s := tlsSettings{}
err := mng.writeCertFiles(dsV2, &s)
@@ -124,7 +124,7 @@ func TestDataSourceCacheManager(t *testing.T) {
require.NoError(t, err)
version, ok := mng.dsCacheInstance.cache.Load("1")
require.True(t, ok)
require.Equal(t, int(3), version)
require.Equal(t, updateTime.Add(2*time.Minute), version)
})
})
}
@@ -173,36 +173,39 @@ func TestGetTLSSettings(t *testing.T) {
mockValidateCertFilePaths()
t.Cleanup(resetValidateCertFilePaths)
updatedTime := time.Now()
testCases := []struct {
desc string
expErr string
jsonData map[string]interface{}
jsonData sqleng.JsonData
secureJSONData map[string]string
uid string
tlsSettings tlsSettings
version int
updated time.Time
}{
{
desc: "Custom TLS authentication disabled",
version: 1,
jsonData: map[string]interface{}{
"sslmode": "disable",
"sslRootCertFile": "i/am/coding/ca.crt",
"sslCertFile": "i/am/coding/client.crt",
"sslKeyFile": "i/am/coding/client.key",
"tlsConfigurationMethod": "file-path",
updated: updatedTime,
jsonData: sqleng.JsonData{
Mode: "disable",
RootCertFile: "i/am/coding/ca.crt",
CertFile: "i/am/coding/client.crt",
CertKeyFile: "i/am/coding/client.key",
ConfigurationMethod: "file-path",
},
tlsSettings: tlsSettings{Mode: "disable"},
},
{
desc: "Custom TLS authentication with file path",
version: 2,
jsonData: map[string]interface{}{
"sslmode": "verify-full",
"sslRootCertFile": "i/am/coding/ca.crt",
"sslCertFile": "i/am/coding/client.crt",
"sslKeyFile": "i/am/coding/client.key",
"tlsConfigurationMethod": "file-path",
updated: updatedTime.Add(time.Minute),
jsonData: sqleng.JsonData{
Mode: "verify-full",
ConfigurationMethod: "file-path",
RootCertFile: "i/am/coding/ca.crt",
CertFile: "i/am/coding/client.crt",
CertKeyFile: "i/am/coding/client.key",
},
tlsSettings: tlsSettings{
Mode: "verify-full",
@@ -214,11 +217,11 @@ func TestGetTLSSettings(t *testing.T) {
},
{
desc: "Custom TLS mode verify-full with certificate files content",
version: 3,
updated: updatedTime.Add(2 * time.Minute),
uid: "xxx",
jsonData: map[string]interface{}{
"sslmode": "verify-full",
"tlsConfigurationMethod": "file-content",
jsonData: sqleng.JsonData{
Mode: "verify-full",
ConfigurationMethod: "file-content",
},
secureJSONData: map[string]string{
"tlsCACert": "I am CA certification",
@@ -244,12 +247,11 @@ func TestGetTLSSettings(t *testing.T) {
dataPath: cfg.DataPath,
}
jsonData := simplejson.NewFromAny(tt.jsonData)
ds := &models.DataSource{
JsonData: jsonData,
SecureJsonData: securejsondata.GetEncryptedJsonData(tt.secureJSONData),
Uid: tt.uid,
Version: tt.version,
ds := sqleng.DataSourceInfo{
JsonData: tt.jsonData,
DecryptedSecureJSONData: tt.secureJSONData,
UID: tt.uid,
Updated: tt.updated,
}
settings, err = mng.getTLSSettings(ds)
@@ -278,7 +280,7 @@ func resetValidateCertFilePaths() {
func mockWriteCertFile() {
writeCertFileCallNum = 0
writeCertFileFunc = func(ds *models.DataSource, logger log.Logger, fileContent string, generatedFilePath string) error {
writeCertFileFunc = func(logger log.Logger, fileContent string, generatedFilePath string) error {
writeCertFileCallNum++
return nil
}