Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(724)

Side by Side Diff: go/database/database.go

Issue 813443002: Overhaul database package (Closed) Base URL: https://skia.googlesource.com/buildbot@master
Patch Set: Comments/readme cleanup Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 package database 1 package database
2 2
3 import ( 3 import (
4 "bufio"
4 "database/sql" 5 "database/sql"
5 "fmt" 6 "fmt"
7 "os"
6 "time" 8 "time"
7 9
10 "strings"
11
8 _ "github.com/go-sql-driver/mysql" 12 _ "github.com/go-sql-driver/mysql"
9 "github.com/golang/glog" 13 "github.com/golang/glog"
10 » _ "github.com/mattn/go-sqlite3" 14 » "skia.googlesource.com/buildbot.git/go/metadata"
11 "skia.googlesource.com/buildbot.git/go/util" 15 "skia.googlesource.com/buildbot.git/go/util"
12 ) 16 )
13 17
18 const (
19 // Template for database address given a hostname and port number.
20 DB_ADDR_TMPL = "tcp(%s:%s)"
21
22 // Template for DB connection strings.
23 DB_CONN_TMPL = "%s:%s@%s/%s?parseTime=true"
24
25 // Local test database.
26 TEST_DB_HOST = ""
27 TEST_USER_ROOT = "test_root"
28 TEST_PASSWORD = ""
29
30 // Name of the root user.
31 USER_ROOT = "root"
32
33 // Name of the readwrite user.
34 USER_RW = "readwrite"
35
36 // Key of the password for the readwrite user.
37 RW_METADATA_KEY = "readwrite"
38 )
39
40 // AddrFromHostPort takes a host and port and returns a database address.
41 func AddrFromHostPort(host, port string) string {
42 return fmt.Sprintf(DB_ADDR_TMPL, host, port)
43 }
44
14 // Config information to create a database connection. 45 // Config information to create a database connection.
15 type DatabaseConfig struct { 46 type DatabaseConfig struct {
16 MySQLString string 47 MySQLString string
17 SQLiteFilePath string
18 MigrationSteps []MigrationStep 48 MigrationSteps []MigrationStep
19 } 49 }
20 50
51 // NewDatabaseConfig constructs a DatabaseConfig from the given options.
52 func NewDatabaseConfig(user, password, host, database string, m []MigrationStep) *DatabaseConfig {
53 return &DatabaseConfig{
54 MySQLString: fmt.Sprintf(DB_CONN_TMPL, user, password, host, database),
55 MigrationSteps: m,
56 }
57 }
58
59 // LocalDatabaseConfig returns a DatabaseConfig appropriate for local use.
60 func LocalDatabaseConfig(database string, m []MigrationStep) *DatabaseConfig {
61 return NewDatabaseConfig(USER_RW, TEST_PASSWORD, TEST_DB_HOST, database, m)
62 }
63
64 // LocalDatabaseConfig returns a DatabaseConfig appropriate use in a local
65 // testing-only database.
66 func LocalTestDatabaseConfig(database string, m []MigrationStep) *DatabaseConfig {
67 return NewDatabaseConfig(USER_RW, TEST_PASSWORD, TEST_DB_HOST, database, m)
68 }
69
70 // LocalDatabaseConfig returns a DatabaseConfig appropriate use in a local
71 // testing-only database with root access.
72 func LocalTestRootDatabaseConfig(database string, m []MigrationStep) *DatabaseCo nfig {
73 return NewDatabaseConfig(TEST_USER_ROOT, TEST_PASSWORD, TEST_DB_HOST, da tabase, m)
74 }
75
76 // ProdDatabaseConfig returns a DatabaseConfig appropriate for running in
77 // production.
78 func ProdDatabaseConfig(host, database string, m []MigrationStep) *DatabaseConfi g {
79 // First, get the password from the metadata server.
80 // See https://developers.google.com/compute/docs/metadata#custom.
81 password, err := metadata.Get(RW_METADATA_KEY)
82 if err != nil {
83 glog.Fatalf("Failed to find metadata. Use 'local' flag when runn ing locally.")
84 }
85 return NewDatabaseConfig(USER_RW, password, host, database, m)
86 }
87
88 // ResolveCustomMySQLString determines whether a password was entered as part
89 // of the given string, prompts for the password if necessary, and then returns
90 // a DatabaseConfig.
91 func ResolveCustomMySQLString(mySQLConnStr string, m []MigrationStep) (*Database Config, error) {
92 useMySQLConnStr := mySQLConnStr
93 if strings.Contains(useMySQLConnStr, "%s") {
94 // Assume the missing part of the string is a password, prompt
95 // and replace.
96 reader := bufio.NewReader(os.Stdin)
97 fmt.Print("Enter password for MySQL: ")
98 password, err := reader.ReadString('\n')
99 if err != nil {
100 return nil, fmt.Errorf("Failed to get password: %v", err )
101 }
102 useMySQLConnStr = fmt.Sprintf(useMySQLConnStr, strings.TrimRight (password, "\n"))
103 }
104 return &DatabaseConfig{
105 MySQLString: useMySQLConnStr,
106 MigrationSteps: m,
107 }, nil
108 }
109
21 // Single step to migrated from one database version to the next and back. 110 // Single step to migrated from one database version to the next and back.
22 type MigrationStep struct { 111 type MigrationStep struct {
23 » MySQLUp []string 112 » MySQLUp []string
24 » MySQLDown []string 113 » MySQLDown []string
25 » SQLiteUp []string
26 » SQLiteDown []string
27 } 114 }
28 115
29 // Database handle to send queries to the underlying database. 116 // Database handle to send queries to the underlying database.
30 type VersionedDB struct { 117 type VersionedDB struct {
31 » // Database intance that is either backed by SQLite or MySQl. 118 » // Database intance that is backed by MySQL.
32 DB *sql.DB 119 DB *sql.DB
33 120
34 // Keeps track if we are connected to MySQL or SQLite
35 IsMySQL bool
36
37 // List of migration steps for this database. 121 // List of migration steps for this database.
38 migrationSteps []MigrationStep 122 migrationSteps []MigrationStep
39 } 123 }
40 124
41 // Init must be called once before DB is used. 125 // Init must be called once before DB is used.
42 // 126 //
43 // Since it used glog, make sure it is also called after flag.Parse is called. 127 // Since it used glog, make sure it is also called after flag.Parse is called.
44 func NewVersionedDB(conf *DatabaseConfig) *VersionedDB { 128 func NewVersionedDB(conf *DatabaseConfig) *VersionedDB {
45 // If there is a connection string then connect to the MySQL server. 129 // If there is a connection string then connect to the MySQL server.
46 // This is for testing only. In production we get the relevant informati on 130 // This is for testing only. In production we get the relevant informati on
47 // from the metadata server. 131 // from the metadata server.
48 var err error 132 var err error
49 var isMySQL = true
50 var DB *sql.DB = nil 133 var DB *sql.DB = nil
51 134
52 » if conf.MySQLString != "" { 135 » glog.Infoln("Opening SQL database.")
53 » » glog.Infoln("Opening SQL database.") 136 » if DB, err = sql.Open("mysql", conf.MySQLString); err == nil {
54 » » if DB, err = sql.Open("mysql", conf.MySQLString); err == nil { 137 » » glog.Infoln("Sending Ping.")
55 » » » glog.Infoln("Sending Ping.") 138 » » err = DB.Ping()
56 » » » err = DB.Ping() 139 » }
57 » » }
58 140
59 » » if err != nil { 141 » if err != nil {
60 » » » glog.Fatalln("Failed to open connection to SQL server:", err) 142 » » glog.Fatalln("Failed to open connection to SQL server:", err)
61 » » }
62 » } else {
63 » » // Open a local SQLite database instead.
64 » » glog.Infof("Opening local sqlite database at: %s", conf.SQLiteFi lePath)
65 » » // Fallback to sqlite for local use.
66 » » DB, err = sql.Open("sqlite3", conf.SQLiteFilePath)
67 » » if err != nil {
68 » » » glog.Fatalln("Failed to open:", err)
69 » » }
70 » » isMySQL = false
71 } 143 }
72 144
73 result := &VersionedDB{ 145 result := &VersionedDB{
74 DB: DB, 146 DB: DB,
75 IsMySQL: isMySQL,
76 migrationSteps: conf.MigrationSteps, 147 migrationSteps: conf.MigrationSteps,
77 } 148 }
78 149
79 // Make sure the migration table exists. 150 // Make sure the migration table exists.
80 if err := result.checkVersionTable(); err != nil { 151 if err := result.checkVersionTable(); err != nil {
81 // We are using panic() instead of Fataln() to be able to trap t his 152 // We are using panic() instead of Fataln() to be able to trap t his
82 // in tests and make sure it fails when no version table exists. 153 // in tests and make sure it fails when no version table exists.
83 glog.Errorln("Unable to create version table.") 154 glog.Errorln("Unable to create version table.")
84 panic("Attempt to create version table returned: " + err.Error() ) 155 panic("Attempt to create version table returned: " + err.Error() )
85 } 156 }
86 glog.Infoln("Version table OK.") 157 glog.Infoln("Version table OK.")
87 158
88 // Migrate to the latest version if we are using SQLite, so we don't hav e
89 // to run the *_migratdb command for a local database.
90 if !result.IsMySQL {
91 result.Migrate(result.MaxDBVersion())
92 }
93
94 // Ping the database occasionally to keep the connection fresh. 159 // Ping the database occasionally to keep the connection fresh.
95 go func() { 160 go func() {
96 c := time.Tick(1 * time.Minute) 161 c := time.Tick(1 * time.Minute)
97 for _ = range c { 162 for _ = range c {
98 if err := result.DB.Ping(); err != nil { 163 if err := result.DB.Ping(); err != nil {
99 glog.Warningln("Database failed to respond:", er r) 164 glog.Warningln("Database failed to respond:", er r)
100 } 165 }
101 glog.Infof("db: Successful ping") 166 glog.Infof("db: Successful ping")
102 } 167 }
103 }() 168 }()
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after
178 return version, err 243 return version, err
179 } 244 }
180 245
181 // Returns the highest version currently available. 246 // Returns the highest version currently available.
182 func (vdb *VersionedDB) MaxDBVersion() int { 247 func (vdb *VersionedDB) MaxDBVersion() int {
183 return len(vdb.migrationSteps) 248 return len(vdb.migrationSteps)
184 } 249 }
185 250
186 // Returns an error if the version table does not exist. 251 // Returns an error if the version table does not exist.
187 func (vdb *VersionedDB) checkVersionTable() error { 252 func (vdb *VersionedDB) checkVersionTable() error {
188 » // Check if the table exists in MySQL or SQLite. 253 » // Check if the table exists in MySQL.
189 stmt := "SHOW TABLES LIKE 'sk_db_version'" 254 stmt := "SHOW TABLES LIKE 'sk_db_version'"
190 if !vdb.IsMySQL {
191 stmt = "SELECT name FROM sqlite_master WHERE type='table' AND na me='sk_db_version';"
192 }
193 255
194 var temp string 256 var temp string
195 err := vdb.DB.QueryRow(stmt).Scan(&temp) 257 err := vdb.DB.QueryRow(stmt).Scan(&temp)
196 if err != nil { 258 if err != nil {
197 // See if we can create the version table. 259 // See if we can create the version table.
198 return vdb.ensureVersionTable() 260 return vdb.ensureVersionTable()
199 } 261 }
200 262
201 return nil 263 return nil
202 } 264 }
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
256 if inc < 0 { 318 if inc < 0 {
257 idx = currentVersion - 1 319 idx = currentVersion - 1
258 } 320 }
259 delta := util.AbsInt(targetVersion - currentVersion) 321 delta := util.AbsInt(targetVersion - currentVersion)
260 result := make([][]string, 0, delta) 322 result := make([][]string, 0, delta)
261 323
262 for i := 0; i < delta; i++ { 324 for i := 0; i < delta; i++ {
263 var temp []string 325 var temp []string
264 switch { 326 switch {
265 // using mysqlp 327 // using mysqlp
266 » » case (inc > 0) && vdb.IsMySQL: 328 » » case (inc > 0):
267 temp = vdb.migrationSteps[idx].MySQLUp 329 temp = vdb.migrationSteps[idx].MySQLUp
268 » » case (inc < 0) && vdb.IsMySQL: 330 » » case (inc < 0):
269 temp = vdb.migrationSteps[idx].MySQLDown 331 temp = vdb.migrationSteps[idx].MySQLDown
270 // using sqlite
271 case (inc > 0):
272 temp = vdb.migrationSteps[idx].SQLiteUp
273 case (inc < 0):
274 temp = vdb.migrationSteps[idx].SQLiteDown
275 } 332 }
276 result = append(result, temp) 333 result = append(result, temp)
277 idx += inc 334 idx += inc
278 } 335 }
279 return result 336 return result
280 } 337 }
OLDNEW
« go/buildbot/db.go ('K') | « go/buildbot/db_setup.go ('k') | go/database/setup_test_db » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698