github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/store/storetest/settings.go (about) 1 package storetest 2 3 import ( 4 "flag" 5 "fmt" 6 "net/url" 7 "os" 8 "path" 9 10 "database/sql" 11 12 "github.com/go-sql-driver/mysql" 13 _ "github.com/go-sql-driver/mysql" 14 _ "github.com/lib/pq" 15 "github.com/pkg/errors" 16 17 "github.com/masterhung0112/hk_server/v5/model" 18 ) 19 20 const ( 21 defaultMysqlDSN = "hkuser:mostest@tcp(localhost:3306)/hungknow_test?charset=utf8mb4,utf8\u0026readTimeout=30s\u0026writeTimeout=30s&multiStatements=true" 22 defaultPostgresqlDSN = "postgres://hkuser:mostest@localhost:5432/hungknow_test?sslmode=disable&connect_timeout=10" 23 defaultMysqlRootPWD = "mostest" 24 defaultMysqlReplicaDSN = "root:mostest@tcp(localhost:3307)/hungknow_test?charset=utf8mb4,utf8\u0026readTimeout=30s" 25 ) 26 27 func getEnv(name, defaultValue string) string { 28 if value := os.Getenv(name); value != "" { 29 return value 30 } 31 return defaultValue 32 } 33 34 func log(message string) { 35 verbose := false 36 if verboseFlag := flag.Lookup("test.v"); verboseFlag != nil { 37 verbose = verboseFlag.Value.String() != "" 38 } 39 if verboseFlag := flag.Lookup("v"); verboseFlag != nil { 40 verbose = verboseFlag.Value.String() != "" 41 } 42 43 if verbose { 44 fmt.Println(message) 45 } 46 } 47 48 // MySQLSettings returns the database settings to connect to the MySQL unittesting database. 49 // The database name is generated randomly and must be created before use. 50 func MySQLSettings(withReplica bool) *model.SqlSettings { 51 dsn := getEnv("TEST_DATABASE_MYSQL_DSN", defaultMysqlDSN) 52 cfg, err := mysql.ParseDSN(dsn) 53 if err != nil { 54 panic("failed to parse dsn " + dsn + ": " + err.Error()) 55 } 56 57 cfg.DBName = "db" + model.NewId() 58 59 mySQLSettings := databaseSettings("mysql", cfg.FormatDSN()) 60 61 if withReplica { 62 mySQLSettings.DataSourceReplicas = []string{getEnv("TEST_DATABASE_MYSQL_REPLICA_DSN", defaultMysqlReplicaDSN)} 63 } 64 65 return mySQLSettings 66 } 67 68 // PostgresSQLSettings returns the database settings to connect to the PostgreSQL unittesting database. 69 // The database name is generated randomly and must be created before use. 70 func PostgreSQLSettings() *model.SqlSettings { 71 dsn := getEnv("TEST_DATABASE_POSTGRESQL_DSN", defaultPostgresqlDSN) 72 dsnUrl, err := url.Parse(dsn) 73 if err != nil { 74 panic("failed to parse dsn " + dsn + ": " + err.Error()) 75 } 76 77 // Generate a random database name 78 dsnUrl.Path = "db" + model.NewId() 79 80 return databaseSettings("postgres", dsnUrl.String()) 81 } 82 83 func mySQLRootDSN(dsn string) string { 84 rootPwd := getEnv("TEST_DATABASE_MYSQL_ROOT_PASSWD", defaultMysqlRootPWD) 85 cfg, err := mysql.ParseDSN(dsn) 86 if err != nil { 87 panic("failed to parse dsn " + dsn + ": " + err.Error()) 88 } 89 90 cfg.User = "root" 91 cfg.Passwd = rootPwd 92 cfg.DBName = "mysql" 93 94 return cfg.FormatDSN() 95 } 96 97 func postgreSQLRootDSN(dsn string) string { 98 dsnUrl, err := url.Parse(dsn) 99 if err != nil { 100 panic("failed to parse dsn " + dsn + ": " + err.Error()) 101 } 102 103 // // Assume the unittesting database has the same password. 104 // password := "" 105 // if dsnUrl.User != nil { 106 // password, _ = dsnUrl.User.Password() 107 // } 108 109 // dsnUrl.User = url.UserPassword("", password) 110 dsnUrl.Path = "postgres" 111 112 return dsnUrl.String() 113 } 114 115 func mySQLDSNDatabase(dsn string) string { 116 cfg, err := mysql.ParseDSN(dsn) 117 if err != nil { 118 panic("failed to parse dsn " + dsn + ": " + err.Error()) 119 } 120 121 return cfg.DBName 122 } 123 124 func postgreSQLDSNDatabase(dsn string) string { 125 dsnUrl, err := url.Parse(dsn) 126 if err != nil { 127 panic("failed to parse dsn " + dsn + ": " + err.Error()) 128 } 129 130 return path.Base(dsnUrl.Path) 131 } 132 133 func databaseSettings(driver, dataSource string) *model.SqlSettings { 134 settings := &model.SqlSettings{ 135 DriverName: &driver, 136 DataSource: &dataSource, 137 DataSourceReplicas: []string{}, 138 DataSourceSearchReplicas: []string{}, 139 MaxIdleConns: new(int), 140 ConnMaxLifetimeMilliseconds: new(int), 141 ConnMaxIdleTimeMilliseconds: new(int), 142 MaxOpenConns: new(int), 143 Trace: model.NewBool(false), 144 AtRestEncryptKey: model.NewString(model.NewRandomString(32)), 145 QueryTimeout: new(int), 146 } 147 *settings.MaxIdleConns = 10 148 *settings.ConnMaxLifetimeMilliseconds = 3600000 149 *settings.ConnMaxIdleTimeMilliseconds = 300000 150 *settings.MaxOpenConns = 100 151 *settings.QueryTimeout = 60 152 153 return settings 154 } 155 156 // execAsRoot executes the given sql as root against the testing database 157 func execAsRoot(settings *model.SqlSettings, sqlCommand string) error { 158 var dsn string 159 var driver = *settings.DriverName 160 161 switch driver { 162 case model.DATABASE_DRIVER_MYSQL: 163 dsn = mySQLRootDSN(*settings.DataSource) 164 case model.DATABASE_DRIVER_POSTGRES: 165 dsn = postgreSQLRootDSN(*settings.DataSource) 166 default: 167 return fmt.Errorf("unsupported driver %s", driver) 168 } 169 170 db, err := sql.Open(driver, dsn) 171 if err != nil { 172 return errors.Wrapf(err, "failed to connect to %s database as root", driver) 173 } 174 defer db.Close() 175 if _, err = db.Exec(sqlCommand); err != nil { 176 return errors.Wrapf(err, "failed to execute `%s` against %s database as root", sqlCommand, driver) 177 } 178 179 return nil 180 } 181 182 func replaceMySQLDatabaseName(dsn, newDBName string) string { 183 cfg, err := mysql.ParseDSN(dsn) 184 if err != nil { 185 panic("failed to parse dsn " + dsn + ": " + err.Error()) 186 } 187 cfg.DBName = newDBName 188 return cfg.FormatDSN() 189 } 190 191 // MakeSqlSettings creates a randomly named database and returns the corresponding sql settings 192 func MakeSqlSettings(driver string, withReplica bool) *model.SqlSettings { 193 var settings *model.SqlSettings 194 var dbName string 195 196 switch driver { 197 case model.DATABASE_DRIVER_MYSQL: 198 settings = MySQLSettings(withReplica) 199 dbName = mySQLDSNDatabase(*settings.DataSource) 200 newDSRs := []string{} 201 for _, dataSource := range settings.DataSourceReplicas { 202 newDSRs = append(newDSRs, replaceMySQLDatabaseName(dataSource, dbName)) 203 } 204 settings.DataSourceReplicas = newDSRs 205 case model.DATABASE_DRIVER_POSTGRES: 206 settings = PostgreSQLSettings() 207 dbName = postgreSQLDSNDatabase(*settings.DataSource) 208 default: 209 panic("unsupported driver " + driver) 210 } 211 212 if err := execAsRoot(settings, "CREATE DATABASE "+dbName); err != nil { 213 panic("failed to create temporary database " + dbName + ": " + err.Error()) 214 } 215 216 switch driver { 217 case model.DATABASE_DRIVER_MYSQL: 218 if err := execAsRoot(settings, "GRANT ALL PRIVILEGES ON "+dbName+".* TO 'hkuser'"); err != nil { 219 panic("failed to grant hkuser permission to " + dbName + ":" + err.Error()) 220 } 221 case model.DATABASE_DRIVER_POSTGRES: 222 if err := execAsRoot(settings, "GRANT ALL PRIVILEGES ON DATABASE \""+dbName+"\" TO hkuser"); err != nil { 223 panic("failed to grant hkuser permission to " + dbName + ":" + err.Error()) 224 } 225 default: 226 panic("unsupported driver " + driver) 227 } 228 229 log("Created temporary " + driver + " database " + dbName) 230 231 return settings 232 } 233 234 func CleanupSqlSettings(settings *model.SqlSettings) { 235 var driver = *settings.DriverName 236 var dbName string 237 238 switch driver { 239 case model.DATABASE_DRIVER_MYSQL: 240 dbName = mySQLDSNDatabase(*settings.DataSource) 241 case model.DATABASE_DRIVER_POSTGRES: 242 dbName = postgreSQLDSNDatabase(*settings.DataSource) 243 default: 244 panic("unsupported driver " + driver) 245 } 246 247 if err := execAsRoot(settings, "DROP DATABASE "+dbName); err != nil { 248 panic("failed to drop temporary database " + dbName + ": " + err.Error()) 249 } 250 251 log("Dropped temporary database " + dbName) 252 }