OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 } |
OLD | NEW |