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

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

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

Powered by Google App Engine
This is Rietveld 408576698