Files
grafana/pkg/services/sqlstore/sqlstore.go
T

440 lines
12 KiB
Go
Raw Normal View History

2014-11-14 17:13:33 +01:00
package sqlstore
import (
"context"
2014-11-14 17:13:33 +01:00
"fmt"
2016-06-28 15:13:59 +02:00
"net/url"
2014-11-14 17:13:33 +01:00
"os"
"path"
"path/filepath"
2014-11-14 17:13:33 +01:00
"strings"
2018-03-16 00:08:25 +01:00
"time"
2014-11-14 17:13:33 +01:00
"github.com/go-sql-driver/mysql"
2015-02-05 10:37:13 +01:00
"github.com/grafana/grafana/pkg/bus"
2019-06-13 10:55:38 +02:00
"github.com/grafana/grafana/pkg/infra/localcache"
2019-05-13 14:45:54 +08:00
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
2018-05-18 11:10:10 +02:00
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
2015-02-05 10:37:13 +01:00
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
2015-02-05 10:37:13 +01:00
"github.com/grafana/grafana/pkg/setting"
2017-12-02 14:40:12 +03:00
_ "github.com/grafana/grafana/pkg/tsdb/mssql"
"github.com/grafana/grafana/pkg/util"
2019-10-09 08:58:45 +02:00
"github.com/grafana/grafana/pkg/util/errutil"
2018-06-07 12:54:36 -07:00
_ "github.com/lib/pq"
2020-04-01 20:57:21 +07:00
"xorm.io/xorm"
2014-11-14 17:13:33 +01:00
)
var (
x *xorm.Engine
dialect migrator.Dialect
2014-11-14 17:13:33 +01:00
2018-05-18 11:10:10 +02:00
sqlog log.Logger = log.New("sqlstore")
2014-11-14 17:13:33 +01:00
)
// ContextSessionKey is used as key to save values in `context.Context`
type ContextSessionKey struct{}
2018-05-18 11:10:10 +02:00
func init() {
// This change will make xorm use an empty default schema for postgres and
// by that mimic the functionality of how it was functioning before
// xorm's changes above.
xorm.DefaultPostgresSchema = ""
2018-05-18 11:10:10 +02:00
registry.Register(&registry.Descriptor{
Name: "SqlStore",
Instance: &SqlStore{},
InitPriority: registry.High,
})
}
2018-05-18 11:10:10 +02:00
type SqlStore struct {
2019-06-13 10:55:38 +02:00
Cfg *setting.Cfg `inject:""`
Bus bus.Bus `inject:""`
CacheService *localcache.CacheService `inject:""`
dbCfg DatabaseConfig
engine *xorm.Engine
log log.Logger
Dialect migrator.Dialect
skipEnsureDefaultOrgAndUser bool
2014-11-14 17:13:33 +01:00
}
2018-05-18 11:10:10 +02:00
func (ss *SqlStore) Init() error {
ss.log = log.New("sqlstore")
ss.readConfig()
2018-05-18 11:10:10 +02:00
engine, err := ss.getEngine()
if err != nil {
2018-05-18 11:10:10 +02:00
return fmt.Errorf("Fail to connect to database: %v", err)
2014-11-14 17:13:33 +01:00
}
2018-05-18 11:10:10 +02:00
ss.engine = engine
ss.Dialect = migrator.NewDialect(ss.engine)
2014-11-14 17:13:33 +01:00
2018-05-18 11:10:10 +02:00
// temporarily still set global var
x = engine
dialect = ss.Dialect
migrator := migrator.NewMigrator(x)
migrations.AddMigrations(migrator)
2018-07-01 16:01:43 +02:00
for _, descriptor := range registry.GetServices() {
sc, ok := descriptor.Instance.(registry.DatabaseMigrator)
if ok {
sc.AddMigration(migrator)
}
}
if err := migrator.Start(); err != nil {
2018-05-18 11:10:10 +02:00
return fmt.Errorf("Migration failed err: %v", err)
}
2017-04-17 18:56:39 +03:00
// Init repo instances
annotations.SetRepository(&SqlAnnotationRepo{})
2018-06-07 12:54:36 -07:00
ss.Bus.SetTransactionManager(ss)
2018-10-31 06:47:14 -07:00
// Register handlers
ss.addUserQueryAndCommandHandlers()
ss.addAlertNotificationUidByIdHandler()
2018-10-31 06:47:14 -07:00
err = ss.logOrgsNotice()
if err != nil {
return err
}
if ss.skipEnsureDefaultOrgAndUser {
2018-05-18 11:10:10 +02:00
return nil
}
return ss.ensureMainOrgAndAdminUser()
2018-05-18 11:10:10 +02:00
}
func (ss *SqlStore) logOrgsNotice() error {
type targetCount struct {
Count int64
}
return ss.WithDbSession(context.Background(), func(session *DBSession) error {
resp := make([]*targetCount, 0)
if err := session.SQL("select count(id) as Count from org").Find(&resp); err != nil {
return err
}
if resp[0].Count > 1 {
ss.log.Warn(`[Deprecation notice]`)
ss.log.Warn(`Fewer than 1% of Grafana installations use organizations, and we feel that most of those`)
ss.log.Warn(`users would have a better experience using Teams instead. As such, we are considering de-emphasizing`)
ss.log.Warn(`and eventually deprecating Organizations in a future Grafana release. If you would like to provide`)
ss.log.Warn(`feedback or describe your need, please do so in the issue linked below`)
ss.log.Warn(`https://github.com/grafana/grafana/issues/24588`)
}
return nil
})
}
func (ss *SqlStore) ensureMainOrgAndAdminUser() error {
2018-06-07 12:54:36 -07:00
err := ss.InTransaction(context.Background(), func(ctx context.Context) error {
systemUserCountQuery := models.GetSystemUserCountStatsQuery{}
err := bus.DispatchCtx(ctx, &systemUserCountQuery)
if err != nil {
return fmt.Errorf("Could not determine if admin user exists: %v", err)
}
2018-05-18 11:10:10 +02:00
if systemUserCountQuery.Result.Count > 0 {
return nil
}
2018-05-18 11:10:10 +02:00
// ensure admin user
if !ss.Cfg.DisableInitAdminCreation {
cmd := models.CreateUserCommand{}
cmd.Login = setting.AdminUser
cmd.Email = setting.AdminUser + "@localhost"
cmd.Password = setting.AdminPassword
cmd.IsAdmin = true
if err := bus.DispatchCtx(ctx, &cmd); err != nil {
return fmt.Errorf("Failed to create admin user: %v", err)
}
2018-05-18 11:10:10 +02:00
ss.log.Info("Created default admin", "user", setting.AdminUser)
return nil
}
2018-05-18 11:10:10 +02:00
// ensure default org if default admin user is disabled
if err := createDefaultOrg(ctx); err != nil {
return errutil.Wrap("Failed to create default organization", err)
}
2018-05-18 11:10:10 +02:00
ss.log.Info("Created default organization")
return nil
})
return err
2014-11-14 17:13:33 +01:00
}
func (ss *SqlStore) buildExtraConnectionString(sep rune) string {
if ss.dbCfg.UrlQueryParams == nil {
return ""
}
var sb strings.Builder
for key, values := range ss.dbCfg.UrlQueryParams {
for _, value := range values {
sb.WriteRune(sep)
sb.WriteString(key)
sb.WriteRune('=')
sb.WriteString(value)
}
}
return sb.String()
}
2018-05-18 11:10:10 +02:00
func (ss *SqlStore) buildConnectionString() (string, error) {
cnnstr := ss.dbCfg.ConnectionString
2018-05-18 11:10:10 +02:00
// special case used by integration tests
if cnnstr != "" {
return cnnstr, nil
}
switch ss.dbCfg.Type {
case migrator.MYSQL:
protocol := "tcp"
2018-05-18 11:10:10 +02:00
if strings.HasPrefix(ss.dbCfg.Host, "/") {
protocol = "unix"
}
cnnstr = fmt.Sprintf("%s:%s@%s(%s)/%s?collation=utf8mb4_unicode_ci&allowNativePasswords=true",
2018-05-18 11:10:10 +02:00
ss.dbCfg.User, ss.dbCfg.Pwd, protocol, ss.dbCfg.Host, ss.dbCfg.Name)
2018-05-18 11:10:10 +02:00
if ss.dbCfg.SslMode == "true" || ss.dbCfg.SslMode == "skip-verify" {
tlsCert, err := makeCert(ss.dbCfg)
2015-11-24 16:17:21 +00:00
if err != nil {
2018-05-18 11:10:10 +02:00
return "", err
2015-11-24 16:17:21 +00:00
}
2019-10-22 14:08:18 +02:00
if err := mysql.RegisterTLSConfig("custom", tlsCert); err != nil {
return "", err
}
2015-11-24 16:17:21 +00:00
cnnstr += "&tls=custom"
}
cnnstr += ss.buildExtraConnectionString('&')
case migrator.POSTGRES:
2019-10-09 08:58:45 +02:00
addr, err := util.SplitHostPortDefault(ss.dbCfg.Host, "127.0.0.1", "5432")
if err != nil {
return "", errutil.Wrapf(err, "Invalid host specifier '%s'", ss.dbCfg.Host)
}
2018-05-18 11:10:10 +02:00
if ss.dbCfg.Pwd == "" {
ss.dbCfg.Pwd = "''"
}
if ss.dbCfg.User == "" {
ss.dbCfg.User = "''"
}
2019-10-09 08:58:45 +02:00
cnnstr = fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s", ss.dbCfg.User, ss.dbCfg.Pwd, addr.Host, addr.Port, ss.dbCfg.Name, ss.dbCfg.SslMode, ss.dbCfg.ClientCertPath, ss.dbCfg.ClientKeyPath, ss.dbCfg.CaCertPath)
cnnstr += ss.buildExtraConnectionString(' ')
case migrator.SQLITE:
2018-05-18 11:10:10 +02:00
// special case for tests
if !filepath.IsAbs(ss.dbCfg.Path) {
2018-10-12 07:55:36 +02:00
ss.dbCfg.Path = filepath.Join(ss.Cfg.DataPath, ss.dbCfg.Path)
}
2019-10-22 14:08:18 +02:00
if err := os.MkdirAll(path.Dir(ss.dbCfg.Path), os.ModePerm); err != nil {
return "", err
}
2019-01-29 20:27:01 +01:00
cnnstr = fmt.Sprintf("file:%s?cache=%s&mode=rwc", ss.dbCfg.Path, ss.dbCfg.CacheMode)
cnnstr += ss.buildExtraConnectionString('&')
2014-11-14 17:13:33 +01:00
default:
2018-05-18 11:10:10 +02:00
return "", fmt.Errorf("Unknown database type: %s", ss.dbCfg.Type)
2014-11-14 17:13:33 +01:00
}
2018-05-18 11:10:10 +02:00
return cnnstr, nil
}
func (ss *SqlStore) getEngine() (*xorm.Engine, error) {
connectionString, err := ss.buildConnectionString()
if err != nil {
return nil, err
}
2018-05-18 11:10:10 +02:00
sqlog.Info("Connecting to DB", "dbtype", ss.dbCfg.Type)
engine, err := xorm.NewEngine(ss.dbCfg.Type, connectionString)
if err != nil {
return nil, err
}
engine.SetMaxOpenConns(ss.dbCfg.MaxOpenConn)
engine.SetMaxIdleConns(ss.dbCfg.MaxIdleConn)
engine.SetConnMaxLifetime(time.Second * time.Duration(ss.dbCfg.ConnMaxLifetime))
// configure sql logging
debugSql := ss.Cfg.Raw.Section("database").Key("log_queries").MustBool(false)
if !debugSql {
engine.SetLogger(&xorm.DiscardLogger{})
} else {
engine.SetLogger(NewXormLogger(log.LvlInfo, log.New("sqlstore.xorm")))
engine.ShowSQL(true)
engine.ShowExecTime(true)
}
return engine, nil
2014-11-14 17:13:33 +01:00
}
2018-05-18 11:10:10 +02:00
func (ss *SqlStore) readConfig() {
sec := ss.Cfg.Raw.Section("database")
2016-06-28 15:13:59 +02:00
cfgURL := sec.Key("url").String()
if len(cfgURL) != 0 {
dbURL, _ := url.Parse(cfgURL)
2018-05-18 11:10:10 +02:00
ss.dbCfg.Type = dbURL.Scheme
ss.dbCfg.Host = dbURL.Host
2016-06-28 15:13:59 +02:00
pathSplit := strings.Split(dbURL.Path, "/")
if len(pathSplit) > 1 {
2018-05-18 11:10:10 +02:00
ss.dbCfg.Name = pathSplit[1]
2016-06-28 15:13:59 +02:00
}
userInfo := dbURL.User
if userInfo != nil {
2018-05-18 11:10:10 +02:00
ss.dbCfg.User = userInfo.Username()
ss.dbCfg.Pwd, _ = userInfo.Password()
2016-06-28 15:13:59 +02:00
}
ss.dbCfg.UrlQueryParams = dbURL.Query()
2016-06-28 15:13:59 +02:00
} else {
2018-05-18 11:10:10 +02:00
ss.dbCfg.Type = sec.Key("type").String()
ss.dbCfg.Host = sec.Key("host").String()
ss.dbCfg.Name = sec.Key("name").String()
ss.dbCfg.User = sec.Key("user").String()
ss.dbCfg.ConnectionString = sec.Key("connection_string").String()
ss.dbCfg.Pwd = sec.Key("password").String()
}
2018-05-18 11:10:10 +02:00
ss.dbCfg.MaxOpenConn = sec.Key("max_open_conn").MustInt(0)
ss.dbCfg.MaxIdleConn = sec.Key("max_idle_conn").MustInt(2)
ss.dbCfg.ConnMaxLifetime = sec.Key("conn_max_lifetime").MustInt(14400)
ss.dbCfg.SslMode = sec.Key("ssl_mode").String()
ss.dbCfg.CaCertPath = sec.Key("ca_cert_path").String()
ss.dbCfg.ClientKeyPath = sec.Key("client_key_path").String()
ss.dbCfg.ClientCertPath = sec.Key("client_cert_path").String()
ss.dbCfg.ServerCertName = sec.Key("server_cert_name").String()
ss.dbCfg.Path = sec.Key("path").MustString("data/grafana.db")
2018-12-27 10:48:11 +01:00
ss.dbCfg.CacheMode = sec.Key("cache_mode").MustString("private")
}
2019-05-17 14:57:26 +03:00
// Interface of arguments for testing db
type ITestDB interface {
Helper()
Fatalf(format string, args ...interface{})
}
2020-04-29 21:37:21 +02:00
// InitTestDB initialize test DB.
2019-05-17 14:57:26 +03:00
func InitTestDB(t ITestDB) *SqlStore {
2018-06-14 19:07:33 +02:00
t.Helper()
2018-05-18 11:10:10 +02:00
sqlstore := &SqlStore{}
sqlstore.Bus = bus.New()
2019-06-13 10:55:38 +02:00
sqlstore.CacheService = localcache.New(5*time.Minute, 10*time.Minute)
sqlstore.skipEnsureDefaultOrgAndUser = true
2018-05-18 11:10:10 +02:00
dbType := migrator.SQLITE
// environment variable present for test db?
if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
2018-05-18 11:10:10 +02:00
dbType = db
}
2018-05-18 11:10:10 +02:00
// set test db config
sqlstore.Cfg = setting.NewCfg()
2019-10-22 14:08:18 +02:00
sec, err := sqlstore.Cfg.Raw.NewSection("database")
if err != nil {
t.Fatalf("Failed to create section: %s", err)
}
if _, err := sec.NewKey("type", dbType); err != nil {
t.Fatalf("Failed to create key: %s", err)
}
2018-05-18 11:10:10 +02:00
switch dbType {
case "mysql":
2019-10-22 14:08:18 +02:00
if _, err := sec.NewKey("connection_string", sqlutil.TestDB_Mysql.ConnStr); err != nil {
t.Fatalf("Failed to create key: %s", err)
}
2018-05-18 11:10:10 +02:00
case "postgres":
2019-10-22 14:08:18 +02:00
if _, err := sec.NewKey("connection_string", sqlutil.TestDB_Postgres.ConnStr); err != nil {
t.Fatalf("Failed to create key: %s", err)
}
default:
2019-10-22 14:08:18 +02:00
if _, err := sec.NewKey("connection_string", sqlutil.TestDB_Sqlite3.ConnStr); err != nil {
t.Fatalf("Failed to create key: %s", err)
}
}
2018-05-18 11:10:10 +02:00
// need to get engine to clean db before we init
engine, err := xorm.NewEngine(dbType, sec.Key("connection_string").String())
if err != nil {
t.Fatalf("Failed to init test database: %v", err)
}
sqlstore.Dialect = migrator.NewDialect(engine)
// temp global var until we get rid of global vars
dialect = sqlstore.Dialect
2018-05-18 11:10:10 +02:00
if err := dialect.CleanDB(); err != nil {
t.Fatalf("Failed to clean test db %v", err)
}
2018-05-18 11:10:10 +02:00
if err := sqlstore.Init(); err != nil {
t.Fatalf("Failed to init test database: %v", err)
}
2018-05-28 13:06:27 +02:00
sqlstore.engine.DatabaseTZ = time.UTC
sqlstore.engine.TZLocation = time.UTC
2018-05-18 11:10:10 +02:00
return sqlstore
}
func IsTestDbMySql() bool {
if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
return db == migrator.MYSQL
}
return false
}
func IsTestDbPostgres() bool {
if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
return db == migrator.POSTGRES
}
return false
}
2018-05-18 11:10:10 +02:00
type DatabaseConfig struct {
2018-12-27 10:48:11 +01:00
Type string
Host string
Name string
User string
Pwd string
Path string
SslMode string
CaCertPath string
ClientKeyPath string
ClientCertPath string
ServerCertName string
ConnectionString string
MaxOpenConn int
MaxIdleConn int
ConnMaxLifetime int
CacheMode string
2019-01-05 00:02:15 -05:00
UrlQueryParams map[string][]string
2018-05-18 11:10:10 +02:00
}