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  }