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

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

Issue 813443002: Overhaul database package (Closed) Base URL: https://skia.googlesource.com/buildbot@master
Patch Set: Assume the MySQL password is "" when running locally Created 5 years, 11 months 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
« no previous file with comments | « go/buildbot/db_setup.go ('k') | go/database/setup_test_db » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 package database 1 package database
2 2
3 import ( 3 import (
4 "bufio"
4 "database/sql" 5 "database/sql"
6 "flag"
5 "fmt" 7 "fmt"
8 "os"
9 "strings"
6 "time" 10 "time"
7 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 DB connection strings.
20 DB_CONN_TMPL = "%s:%s@tcp(%s:%d)/%s?parseTime=true"
21
22 // Name of the root user.
23 USER_ROOT = "root"
24
25 // Name of the readwrite user.
26 USER_RW = "readwrite"
27
28 // Key of the password for the readwrite user.
29 RW_METADATA_KEY = "readwrite"
30
31 // Key of the password for the root user.
32 ROOT_METADATA_KEY = "root"
33 )
34
35 var (
36 // Flags
37 dbHost *string
38 dbPort *int
39 dbUser *string
40 dbName *string
41 )
42
43 // SetupFlags adds command-line flags for the database.
44 func SetupFlags(defaultHost string, defaultPort int, defaultUser, defaultDatabas e string) {
45 dbHost = flag.String("db_host", defaultHost, "Hostname of the MySQL data base server.")
46 dbPort = flag.Int("db_port", defaultPort, "Port number of the MySQL data base.")
47 dbUser = flag.String("db_user", defaultUser, "MySQL user name.")
48 dbName = flag.String("db_name", defaultDatabase, "Name of the MySQL data base.")
49 }
50
51 // checkFlags returns an error if the command-line flags have not been set.
52 func checkFlags() error {
53 if dbHost == nil || dbPort == nil || dbUser == nil || dbName == nil {
54 return fmt.Errorf(
55 "One or more of the required command-line flags was not set. " +
56 "Did you call forget to call database.SetupFlags ?")
57 }
58 return nil
59 }
60
61 // ConfigFromFlags obtains a DatabaseConfig based on parsed command-line flags.
62 // If local is true, the DB host is overridden.
63 func ConfigFromFlags(password string, local bool, m []MigrationStep) (*DatabaseC onfig, error) {
64 if err := checkFlags(); err != nil {
65 return nil, err
66 }
67 // Override the DB host in local mode.
68 useHost := *dbHost
69 if local {
70 useHost = "localhost"
71 }
72
73 usePassword := password
74 // Prompt for password if necessary.
75 if usePassword == "" && !local {
76 reader := bufio.NewReader(os.Stdin)
77 fmt.Printf("Enter password for MySQL user %s at %s:%d: ", *dbUse r, useHost, *dbPort)
78 var err error
79 usePassword, err = reader.ReadString('\n')
80 if err != nil {
81 return nil, fmt.Errorf("Failed to get password: %v", err )
82 }
83 usePassword = strings.Trim(usePassword, "\n")
84 }
85 return NewDatabaseConfig(*dbUser, usePassword, useHost, *dbPort, *dbName , m), nil
86 }
87
88 // ConfigFromFlagsAndMetadata obtains a DatabaseConfig based on a combination
89 // of parsed command-line flags and metadata when not running in local mode.
90 func ConfigFromFlagsAndMetadata(local bool, m []MigrationStep) (*DatabaseConfig, error) {
91 if err := checkFlags(); err != nil {
92 return nil, err
93 }
94 // If not in local mode, get the password from metadata.
95 password := ""
96 if !local {
97 key := ""
98 if *dbUser == USER_RW {
99 key = RW_METADATA_KEY
100 } else if *dbUser == USER_ROOT {
101 key = ROOT_METADATA_KEY
102 }
103 if key == "" {
104 return nil, fmt.Errorf("Unknown user %s; could not obtai n password from metadata.", *dbUser)
105 }
106 var err error
107 password, err = metadata.Get(key)
108 if err != nil {
109 return nil, fmt.Errorf("Failed to find metadata. Use 'lo cal' flag when running locally.")
110 }
111 }
112 return ConfigFromFlags(password, local, m)
113 }
114
14 // Config information to create a database connection. 115 // Config information to create a database connection.
15 type DatabaseConfig struct { 116 type DatabaseConfig struct {
16 MySQLString string 117 MySQLString string
17 SQLiteFilePath string
18 MigrationSteps []MigrationStep 118 MigrationSteps []MigrationStep
19 } 119 }
20 120
121 // NewDatabaseConfig constructs a DatabaseConfig from the given options.
122 func NewDatabaseConfig(user, password, host string, port int, database string, m []MigrationStep) *DatabaseConfig {
123 return &DatabaseConfig{
124 MySQLString: fmt.Sprintf(DB_CONN_TMPL, user, password, host, port, database),
125 MigrationSteps: m,
126 }
127 }
128
21 // Single step to migrated from one database version to the next and back. 129 // Single step to migrated from one database version to the next and back.
22 type MigrationStep struct { 130 type MigrationStep struct {
23 » MySQLUp []string 131 » MySQLUp []string
24 » MySQLDown []string 132 » MySQLDown []string
25 » SQLiteUp []string
26 » SQLiteDown []string
27 } 133 }
28 134
29 // Database handle to send queries to the underlying database. 135 // Database handle to send queries to the underlying database.
30 type VersionedDB struct { 136 type VersionedDB struct {
31 » // Database intance that is either backed by SQLite or MySQl. 137 » // Database intance that is backed by MySQL.
32 DB *sql.DB 138 DB *sql.DB
33 139
34 // Keeps track if we are connected to MySQL or SQLite
35 IsMySQL bool
36
37 // List of migration steps for this database. 140 // List of migration steps for this database.
38 migrationSteps []MigrationStep 141 migrationSteps []MigrationStep
39 } 142 }
40 143
41 // Init must be called once before DB is used. 144 // Init must be called once before DB is used.
42 // 145 //
43 // Since it used glog, make sure it is also called after flag.Parse is called. 146 // Since it used glog, make sure it is also called after flag.Parse is called.
44 func NewVersionedDB(conf *DatabaseConfig) *VersionedDB { 147 func NewVersionedDB(conf *DatabaseConfig) *VersionedDB {
45 // If there is a connection string then connect to the MySQL server. 148 // 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 149 // This is for testing only. In production we get the relevant informati on
47 // from the metadata server. 150 // from the metadata server.
48 var err error 151 var err error
49 var isMySQL = true
50 var DB *sql.DB = nil 152 var DB *sql.DB = nil
51 153
52 » if conf.MySQLString != "" { 154 » glog.Infoln("Opening SQL database.")
53 » » glog.Infoln("Opening SQL database.") 155 » if DB, err = sql.Open("mysql", conf.MySQLString); err == nil {
54 » » if DB, err = sql.Open("mysql", conf.MySQLString); err == nil { 156 » » glog.Infoln("Sending Ping.")
55 » » » glog.Infoln("Sending Ping.") 157 » » err = DB.Ping()
56 » » » err = DB.Ping() 158 » }
57 » » }
58 159
59 » » if err != nil { 160 » if err != nil {
60 » » » glog.Fatalln("Failed to open connection to SQL server:", err) 161 » » 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 } 162 }
72 163
73 result := &VersionedDB{ 164 result := &VersionedDB{
74 DB: DB, 165 DB: DB,
75 IsMySQL: isMySQL,
76 migrationSteps: conf.MigrationSteps, 166 migrationSteps: conf.MigrationSteps,
77 } 167 }
78 168
79 // Make sure the migration table exists. 169 // Make sure the migration table exists.
80 if err := result.checkVersionTable(); err != nil { 170 if err := result.checkVersionTable(); err != nil {
81 // We are using panic() instead of Fataln() to be able to trap t his 171 // 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. 172 // in tests and make sure it fails when no version table exists.
83 glog.Errorln("Unable to create version table.") 173 glog.Errorln("Unable to create version table.")
84 panic("Attempt to create version table returned: " + err.Error() ) 174 panic("Attempt to create version table returned: " + err.Error() )
85 } 175 }
86 glog.Infoln("Version table OK.") 176 glog.Infoln("Version table OK.")
87 177
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. 178 // Ping the database occasionally to keep the connection fresh.
95 go func() { 179 go func() {
96 c := time.Tick(1 * time.Minute) 180 c := time.Tick(1 * time.Minute)
97 for _ = range c { 181 for _ = range c {
98 if err := result.DB.Ping(); err != nil { 182 if err := result.DB.Ping(); err != nil {
99 glog.Warningln("Database failed to respond:", er r) 183 glog.Warningln("Database failed to respond:", er r)
100 } 184 }
101 glog.Infof("db: Successful ping") 185 glog.Infof("db: Successful ping")
102 } 186 }
103 }() 187 }()
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after
178 return version, err 262 return version, err
179 } 263 }
180 264
181 // Returns the highest version currently available. 265 // Returns the highest version currently available.
182 func (vdb *VersionedDB) MaxDBVersion() int { 266 func (vdb *VersionedDB) MaxDBVersion() int {
183 return len(vdb.migrationSteps) 267 return len(vdb.migrationSteps)
184 } 268 }
185 269
186 // Returns an error if the version table does not exist. 270 // Returns an error if the version table does not exist.
187 func (vdb *VersionedDB) checkVersionTable() error { 271 func (vdb *VersionedDB) checkVersionTable() error {
188 » // Check if the table exists in MySQL or SQLite. 272 » // Check if the table exists in MySQL.
189 stmt := "SHOW TABLES LIKE 'sk_db_version'" 273 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 274
194 var temp string 275 var temp string
195 err := vdb.DB.QueryRow(stmt).Scan(&temp) 276 err := vdb.DB.QueryRow(stmt).Scan(&temp)
196 if err != nil { 277 if err != nil {
197 // See if we can create the version table. 278 // See if we can create the version table.
198 return vdb.ensureVersionTable() 279 return vdb.ensureVersionTable()
199 } 280 }
200 281
201 return nil 282 return nil
202 } 283 }
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
256 if inc < 0 { 337 if inc < 0 {
257 idx = currentVersion - 1 338 idx = currentVersion - 1
258 } 339 }
259 delta := util.AbsInt(targetVersion - currentVersion) 340 delta := util.AbsInt(targetVersion - currentVersion)
260 result := make([][]string, 0, delta) 341 result := make([][]string, 0, delta)
261 342
262 for i := 0; i < delta; i++ { 343 for i := 0; i < delta; i++ {
263 var temp []string 344 var temp []string
264 switch { 345 switch {
265 // using mysqlp 346 // using mysqlp
266 » » case (inc > 0) && vdb.IsMySQL: 347 » » case (inc > 0):
267 temp = vdb.migrationSteps[idx].MySQLUp 348 temp = vdb.migrationSteps[idx].MySQLUp
268 » » case (inc < 0) && vdb.IsMySQL: 349 » » case (inc < 0):
269 temp = vdb.migrationSteps[idx].MySQLDown 350 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 } 351 }
276 result = append(result, temp) 352 result = append(result, temp)
277 idx += inc 353 idx += inc
278 } 354 }
279 return result 355 return result
280 } 356 }
OLDNEW
« no previous file with comments | « 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