@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
@ -27,151 +28,164 @@ import (
_ "github.com/grafana/grafana/pkg/tsdb/mssql"
_ "github.com/grafana/grafana/pkg/tsdb/mssql"
)
)
type DatabaseConfig struct {
Type , Host , Name , User , Pwd , Path , SslMode string
CaCertPath string
ClientKeyPath string
ClientCertPath string
ServerCertName string
MaxOpenConn int
MaxIdleConn int
ConnMaxLifetime int
}
var (
var (
x * xorm . Engine
x * xorm . Engine
dialect migrator . Dialect
dialect migrator . Dialect
HasEngine bool
sqlog log . Logger = log . New ( "sqlstore" )
DbCfg DatabaseConfig
UseSQLite3 bool
sqlog log . Logger = log . New ( "sqlstore" )
)
)
func EnsureAdminUser ( ) {
func init ( ) {
statsQuery := m . GetSystemStatsQuery { }
registry . Register ( & registry . Descriptor {
Name : "SqlStore" ,
if err := bus . Dispatch ( & statsQuery ) ; err != nil {
Instance : & SqlStore { } ,
log . Fatal ( 3 , "Could not determine if admin user exists: %v" , err )
InitPriority : registry . High ,
return
} )
}
}
if statsQuery . Result . Users > 0 {
return
}
cmd := m . CreateUserCommand { }
cmd . Login = setting . AdminUser
cmd . Email = setting . AdminUser + "@localhost"
cmd . Password = setting . AdminPassword
cmd . IsAdmin = true
if err := bus . Dispatch ( & cmd ) ; err != nil {
type SqlStore struct {
log . Error ( 3 , "Failed to create default admin user" , err )
Cfg * setting . Cfg ` inject:"" `
return
}
log . Info ( "Created default admin user: %v" , setting . AdminUser )
dbCfg DatabaseConfig
engine * xorm . Engine
log log . Logger
skipEnsureAdmin bool
}
}
func NewEngine ( ) * xorm . Engine {
func ( ss * SqlStore ) Init ( ) error {
x , err := getEngine ( )
ss . log = log . New ( "sqlstore" )
ss . readConfig ( )
if err != nil {
engine , err := ss . getEngine ( )
sqlog . Crit ( "Fail to connect to database" , "error" , err )
os . Exit ( 1 )
}
err = SetEngine ( x )
if err != nil {
if err != nil {
sqlog . Error ( "Fail to initialize orm engine" , "error" , err )
return fmt . Errorf ( "Fail to connect to database: %v" , err )
os . Exit ( 1 )
}
}
return x
ss . engine = engine
}
func SetEngine ( engine * xorm . Engine ) ( err error ) {
// temporarily still set global var
x = engine
x = engine
dialect = migrator . NewDialect ( x )
dialect = migrator . NewDialect ( x )
migrator := migrator . NewMigrator ( x )
migrator := migrator . NewMigrator ( x )
migrations . AddMigrations ( migrator )
migrations . AddMigrations ( migrator )
if err := migrator . Start ( ) ; err != nil {
if err := migrator . Start ( ) ; err != nil {
return fmt . Errorf ( "Sqlstore:: Migration failed err: %v\n " , err )
return fmt . Errorf ( "Migration failed err: %v" , err )
}
}
// Init repo instances
// Init repo instances
annotations . SetRepository ( & SqlAnnotationRepo { } )
annotations . SetRepository ( & SqlAnnotationRepo { } )
// ensure admin user
if ss . skipEnsureAdmin {
return nil
}
return ss . ensureAdminUser ( )
}
func ( ss * SqlStore ) ensureAdminUser ( ) error {
statsQuery := m . GetSystemStatsQuery { }
if err := bus . Dispatch ( & statsQuery ) ; err != nil {
fmt . Errorf ( "Could not determine if admin user exists: %v" , err )
}
if statsQuery . Result . Users > 0 {
return nil
}
cmd := m . CreateUserCommand { }
cmd . Login = setting . AdminUser
cmd . Email = setting . AdminUser + "@localhost"
cmd . Password = setting . AdminPassword
cmd . IsAdmin = true
if err := bus . Dispatch ( & cmd ) ; err != nil {
return fmt . Errorf ( "Failed to create admin user: %v" , err )
}
ss . log . Info ( "Created default admin user: %v" , setting . AdminUser )
return nil
return nil
}
}
func getEngine ( ) ( * xorm . Engine , error ) {
func ( ss * SqlStore ) buildConnectionString ( ) ( string , error ) {
LoadConfig ( )
cnnstr := ss . dbCfg . ConnectionString
cnnstr := ""
// special case used by integration tests
switch DbCfg . Type {
if cnnstr != "" {
return cnnstr , nil
}
switch ss . dbCfg . Type {
case migrator . MYSQL :
case migrator . MYSQL :
protocol := "tcp"
protocol := "tcp"
if strings . HasPrefix ( DbCfg . Host , "/" ) {
if strings . HasPrefix ( ss . d bCfg. Host , "/" ) {
protocol = "unix"
protocol = "unix"
}
}
cnnstr = fmt . Sprintf ( "%s:%s@%s(%s)/%s?collation=utf8mb4_unicode_ci&allowNativePasswords=true" ,
cnnstr = fmt . Sprintf ( "%s:%s@%s(%s)/%s?collation=utf8mb4_unicode_ci&allowNativePasswords=true" ,
url . QueryEscape ( DbCfg . User ) , url . QueryEscape ( DbCfg . Pwd ) , protocol , DbCfg . Host , url . PathEscape ( DbCfg . Name ) )
ss . dbCfg . User , ss . d bCfg. Pwd , protocol , ss . dbCfg . Host , ss . d bCfg. Name )
if DbCfg . SslMode == "true" || DbCfg . SslMode == "skip-verify" {
if ss . d bCfg. SslMode == "true" || ss . d bCfg. SslMode == "skip-verify" {
tlsCert , err := makeCert ( "custom" , DbCfg )
tlsCert , err := makeCert ( "custom" , ss . d bCfg)
if err != nil {
if err != nil {
return nil , err
return "" , err
}
}
mysql . RegisterTLSConfig ( "custom" , tlsCert )
mysql . RegisterTLSConfig ( "custom" , tlsCert )
cnnstr += "&tls=custom"
cnnstr += "&tls=custom"
}
}
case migrator . POSTGRES :
case migrator . POSTGRES :
var host , port = "127.0.0.1" , "5432"
var host , port = "127.0.0.1" , "5432"
fields := strings . Split ( DbCfg . Host , ":" )
fields := strings . Split ( ss . d bCfg. Host , ":" )
if len ( fields ) > 0 && len ( strings . TrimSpace ( fields [ 0 ] ) ) > 0 {
if len ( fields ) > 0 && len ( strings . TrimSpace ( fields [ 0 ] ) ) > 0 {
host = fields [ 0 ]
host = fields [ 0 ]
}
}
if len ( fields ) > 1 && len ( strings . TrimSpace ( fields [ 1 ] ) ) > 0 {
if len ( fields ) > 1 && len ( strings . TrimSpace ( fields [ 1 ] ) ) > 0 {
port = fields [ 1 ]
port = fields [ 1 ]
}
}
cnnstr = fmt . Sprintf ( "user='%s' password='%s' host='%s' port='%s' dbname='%s' sslmode='%s' sslcert='%s' sslkey='%s' sslrootcert='%s'" ,
if ss . dbCfg . Pwd == "" {
strings . Replace ( DbCfg . User , ` ' ` , ` \' ` , - 1 ) ,
ss . dbCfg . Pwd = "''"
strings . Replace ( DbCfg . Pwd , ` ' ` , ` \' ` , - 1 ) ,
}
strings . Replace ( host , ` ' ` , ` \' ` , - 1 ) ,
if ss . dbCfg . User == "" {
strings . Replace ( port , ` ' ` , ` \' ` , - 1 ) ,
ss . dbCfg . User = "''"
strings . Replace ( DbCfg . Name , ` ' ` , ` \' ` , - 1 ) ,
}
strings . Replace ( DbCfg . SslMode , ` ' ` , ` \' ` , - 1 ) ,
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 , host , port , ss . dbCfg . Name , ss . dbCfg . SslMode , ss . dbCfg . ClientCertPath , ss . dbCfg . ClientKeyPath , ss . dbCfg . CaCertPath )
strings . Replace ( DbCfg . ClientCertPath , ` ' ` , ` \' ` , - 1 ) ,
strings . Replace ( DbCfg . ClientKeyPath , ` ' ` , ` \' ` , - 1 ) ,
strings . Replace ( DbCfg . CaCertPath , ` ' ` , ` \' ` , - 1 ) ,
)
case migrator . SQLITE :
case migrator . SQLITE :
if ! filepath . IsAbs ( DbCfg . Path ) {
// special case for tests
DbCfg . Path = filepath . Join ( setting . DataPath , DbCfg . Path )
if ! filepath . IsAbs ( ss . dbCfg . Path ) {
ss . dbCfg . Path = filepath . Join ( setting . DataPath , ss . dbCfg . Path )
}
}
os . MkdirAll ( path . Dir ( D bCfg. Path ) , os . ModePerm )
os . MkdirAll ( path . Dir ( ss . d bCfg. Path ) , os . ModePerm )
cnnstr = "file:" + D bCfg. Path + "?cache=shared&mode=rwc"
cnnstr = "file:" + ss . d bCfg. Path + "?cache=shared&mode=rwc"
default :
default :
return nil , fmt . Errorf ( "Unknown database type: %s" , D bCfg. Type )
return "" , fmt . Errorf ( "Unknown database type: %s" , ss . d bCfg. Type )
}
}
sqlog . Info ( "Initializing DB" , "dbtype" , DbCfg . Type )
return cnnstr , nil
engine , err := xorm . NewEngine ( DbCfg . Type , cnnstr )
}
func ( ss * SqlStore ) getEngine ( ) ( * xorm . Engine , error ) {
connectionString , err := ss . buildConnectionString ( )
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
engine . SetMaxOpenConns ( DbCfg . MaxOpenConn )
sqlog . Info ( "Connecting to DB" , "dbtype" , ss . dbCfg . Type )
engine . SetMaxIdleConns ( DbCfg . MaxIdleConn )
engine , err := xorm . NewEngine ( ss . dbCfg . Type , connectionString )
engine . SetConnMaxLifetime ( time . Second * time . Duration ( DbCfg . ConnMaxLifetime ) )
if err != nil {
debugSql := setting . Raw . Section ( "database" ) . Key ( "log_queries" ) . MustBool ( false )
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 {
if ! debugSql {
engine . SetLogger ( & xorm . DiscardLogger { } )
engine . SetLogger ( & xorm . DiscardLogger { } )
} else {
} else {
@ -183,95 +197,90 @@ func getEngine() (*xorm.Engine, error) {
return engine , nil
return engine , nil
}
}
func Lo adConfig( ) {
func ( ss * SqlStore ) re adConfig( ) {
sec := settin g . Raw . Section ( "database" )
sec := ss . Cf g . Raw . Section ( "database" )
cfgURL := sec . Key ( "url" ) . String ( )
cfgURL := sec . Key ( "url" ) . String ( )
if len ( cfgURL ) != 0 {
if len ( cfgURL ) != 0 {
dbURL , _ := url . Parse ( cfgURL )
dbURL , _ := url . Parse ( cfgURL )
D bCfg. Type = dbURL . Scheme
ss . d bCfg. Type = dbURL . Scheme
D bCfg. Host = dbURL . Host
ss . d bCfg. Host = dbURL . Host
pathSplit := strings . Split ( dbURL . Path , "/" )
pathSplit := strings . Split ( dbURL . Path , "/" )
if len ( pathSplit ) > 1 {
if len ( pathSplit ) > 1 {
D bCfg. Name = pathSplit [ 1 ]
ss . d bCfg. Name = pathSplit [ 1 ]
}
}
userInfo := dbURL . User
userInfo := dbURL . User
if userInfo != nil {
if userInfo != nil {
D bCfg. User = userInfo . Username ( )
ss . d bCfg. User = userInfo . Username ( )
D bCfg. Pwd , _ = userInfo . Password ( )
ss . d bCfg. Pwd , _ = userInfo . Password ( )
}
}
} else {
} else {
DbCfg . Type = sec . Key ( "type" ) . String ( )
ss . dbCfg . Type = sec . Key ( "type" ) . String ( )
DbCfg . Host = sec . Key ( "host" ) . String ( )
ss . dbCfg . Host = sec . Key ( "host" ) . String ( )
DbCfg . Name = sec . Key ( "name" ) . String ( )
ss . dbCfg . Name = sec . Key ( "name" ) . String ( )
DbCfg . User = sec . Key ( "user" ) . String ( )
ss . dbCfg . User = sec . Key ( "user" ) . String ( )
if len ( DbCfg . Pwd ) == 0 {
ss . dbCfg . ConnectionString = sec . Key ( "connection_string" ) . String ( )
DbCfg . Pwd = sec . Key ( "password" ) . String ( )
ss . dbCfg . Pwd = sec . Key ( "password" ) . String ( )
}
}
DbCfg . MaxOpenConn = sec . Key ( "max_open_conn" ) . MustInt ( 0 )
DbCfg . MaxIdleConn = sec . Key ( "max_idle_conn" ) . MustInt ( 0 )
DbCfg . ConnMaxLifetime = sec . Key ( "conn_max_lifetime" ) . MustInt ( 14400 )
if DbCfg . Type == "sqlite3" {
UseSQLite3 = true
// only allow one connection as sqlite3 has multi threading issues that cause table locks
// DbCfg.MaxIdleConn = 1
// DbCfg.MaxOpenConn = 1
}
}
DbCfg . SslMode = sec . Key ( "ssl_mode" ) . String ( )
DbCfg . CaCertPath = sec . Key ( "ca_cert_path" ) . String ( )
ss . dbCfg . MaxOpenConn = sec . Key ( "max_open_conn" ) . MustInt ( 0 )
DbCfg . ClientKeyPath = sec . Key ( "client_key_path" ) . String ( )
ss . dbCfg . MaxIdleConn = sec . Key ( "max_idle_conn" ) . MustInt ( 2 )
DbCfg . ClientCertPath = sec . Key ( "client_cert_path" ) . String ( )
ss . dbCfg . ConnMaxLifetime = sec . Key ( "conn_max_lifetime" ) . MustInt ( 14400 )
DbCfg . ServerCertName = sec . Key ( "server_cert_name" ) . String ( )
DbCfg . Path = sec . Key ( "path" ) . MustString ( "data/grafana.db" )
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" )
}
}
func InitTestDB ( t * testing . T ) * xorm . Engine {
func InitTestDB ( t * testing . T ) * SqlStore {
selectedDb := migrator . SQLITE
sqlstore := & SqlStore { }
// selectedDb := migrator.MYSQL
sqlstore . skipEnsureAdmin = true
// selectedDb := migrator.POSTGRES
var x * xorm . Engine
dbType := migrator . SQLITE
var err error
// environment variable present for test db?
// environment variable present for test db?
if db , present := os . LookupEnv ( "GRAFANA_TEST_DB" ) ; present {
if db , present := os . LookupEnv ( "GRAFANA_TEST_DB" ) ; present {
selecte dD b = db
dbType = db
}
}
switch strings . ToLower ( selectedDb ) {
// set test db config
case migrator . MYSQL :
sqlstore . Cfg = setting . NewCfg ( )
x , err = xorm . NewEngine ( sqlutil . TestDB_Mysql . DriverName , sqlutil . TestDB_Mysql . ConnStr )
sec , _ := sqlstore . Cfg . Raw . NewSection ( "database" )
case migrator . POSTGRES :
sec . NewKey ( "type" , dbType )
x , err = xorm . NewEngine ( sqlutil . TestDB_Postgres . DriverName , sqlutil . TestDB_Postgres . ConnStr )
switch dbType {
case "mysql" :
sec . NewKey ( "connection_string" , sqlutil . TestDB_Mysql . ConnStr )
case "postgres" :
sec . NewKey ( "connection_string" , sqlutil . TestDB_Postgres . ConnStr )
default :
default :
x , err = xorm . NewEngine ( sqlutil . TestDB_Sqlite3 . DriverName , sqlutil . TestDB_Sqlite3 . ConnStr )
s ec . NewKey ( "connection_string" , sqlutil . TestDB_Sqlite3 . ConnStr )
}
}
x . DatabaseTZ = time . UTC
// need to get engine to clean db before we init
x . TZLocation = time . UTC
engine , err := xorm . NewEngine ( dbType , sec . Key ( "connection_string" ) . String ( ) )
if err != nil {
if err != nil {
t . Fatalf ( "Failed to init test database: %v" , err )
t . Fatalf ( "Failed to init test database: %v" , err )
}
}
dialect = migrator . NewDialect ( x )
dialect = migrator . NewDialect ( engine )
if err := dialect . CleanDB ( ) ; err != nil {
err = dialect . CleanDB ( )
if err != nil {
t . Fatalf ( "Failed to clean test db %v" , err )
t . Fatalf ( "Failed to clean test db %v" , err )
}
}
if err := SetEngine ( x ) ; err != nil {
if err := sqlstore . Init ( ) ; err != nil {
t . Fatal ( err )
t . Fatalf ( "Failed to init test database: %v" , err )
}
}
// x.ShowSQL()
//// sqlstore.engine.DatabaseTZ = time.UTC
//// sqlstore.engine.TZLocation = time.UTC
return x
return sqlstore
}
}
func IsTestDbMySql ( ) bool {
func IsTestDbMySql ( ) bool {
@ -289,3 +298,15 @@ func IsTestDbPostgres() bool {
return false
return false
}
}
type DatabaseConfig struct {
Type , Host , Name , User , Pwd , Path , SslMode string
CaCertPath string
ClientKeyPath string
ClientCertPath string
ServerCertName string
ConnectionString string
MaxOpenConn int
MaxIdleConn int
ConnMaxLifetime int
}