vitess.io/vitess@v0.16.2/go/vt/vtgr/db/db.go (about) 1 /* 2 Copyright 2014 Outbrain Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 /* 18 This file has been copied over from VTOrc package 19 */ 20 21 package db 22 23 import ( 24 "database/sql" 25 "fmt" 26 "strings" 27 "sync" 28 "time" 29 30 "vitess.io/vitess/go/vt/external/golib/sqlutils" 31 "vitess.io/vitess/go/vt/log" 32 "vitess.io/vitess/go/vt/vtgr/config" 33 ) 34 35 var ( 36 EmptyArgs []any 37 Db DB = (*vtorcDB)(nil) 38 ) 39 40 var mysqlURI string 41 var dbMutex sync.Mutex 42 43 type DB interface { 44 QueryOrchestrator(query string, argsArray []any, onRow func(sqlutils.RowMap) error) error 45 } 46 47 type vtorcDB struct { 48 } 49 50 var _ DB = (*vtorcDB)(nil) 51 52 func (m *vtorcDB) QueryOrchestrator(query string, argsArray []any, onRow func(sqlutils.RowMap) error) error { 53 return QueryOrchestrator(query, argsArray, onRow) 54 } 55 56 type DummySQLResult struct { 57 } 58 59 func (dummyRes DummySQLResult) LastInsertId() (int64, error) { 60 return 0, nil 61 } 62 63 func (dummyRes DummySQLResult) RowsAffected() (int64, error) { 64 return 1, nil 65 } 66 67 func getMySQLURI() string { 68 dbMutex.Lock() 69 defer dbMutex.Unlock() 70 if mysqlURI != "" { 71 return mysqlURI 72 } 73 mysqlURI := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?timeout=%ds&readTimeout=%ds&rejectReadOnly=%t&interpolateParams=true", 74 config.Config.MySQLOrchestratorUser, 75 config.Config.MySQLOrchestratorPassword, 76 config.Config.MySQLOrchestratorHost, 77 config.Config.MySQLOrchestratorPort, 78 config.Config.MySQLOrchestratorDatabase, 79 config.Config.MySQLConnectTimeoutSeconds, 80 config.Config.MySQLOrchestratorReadTimeoutSeconds, 81 config.Config.MySQLOrchestratorRejectReadOnly, 82 ) 83 if config.Config.MySQLOrchestratorUseMutualTLS { 84 mysqlURI, _ = SetupMySQLOrchestratorTLS(mysqlURI) 85 } 86 return mysqlURI 87 } 88 89 // OpenDiscovery returns a DB instance to access a topology instance. 90 // It has lower read timeout than OpenTopology and is intended to 91 // be used with low-latency discovery queries. 92 func OpenDiscovery(host string, port int) (*sql.DB, error) { 93 return openTopology(host, port, config.Config.MySQLDiscoveryReadTimeoutSeconds) 94 } 95 96 // OpenTopology returns a DB instance to access a topology instance. 97 func OpenTopology(host string, port int) (*sql.DB, error) { 98 return openTopology(host, port, config.Config.MySQLTopologyReadTimeoutSeconds) 99 } 100 101 func openTopology(host string, port int, readTimeout int) (db *sql.DB, err error) { 102 uri := fmt.Sprintf("%s:%s@tcp(%s:%d)/?timeout=%ds&readTimeout=%ds&interpolateParams=true", 103 config.Config.MySQLTopologyUser, 104 config.Config.MySQLTopologyPassword, 105 host, port, 106 config.Config.MySQLConnectTimeoutSeconds, 107 readTimeout, 108 ) 109 110 if config.Config.MySQLTopologyUseMutualTLS || 111 (config.Config.MySQLTopologyUseMixedTLS && requiresTLS(host, port, uri)) { 112 if uri, err = SetupMySQLTopologyTLS(uri); err != nil { 113 return nil, err 114 } 115 } 116 if db, _, err = sqlutils.GetDB(uri); err != nil { 117 return nil, err 118 } 119 if config.Config.MySQLConnectionLifetimeSeconds > 0 { 120 db.SetConnMaxLifetime(time.Duration(config.Config.MySQLConnectionLifetimeSeconds) * time.Second) 121 } 122 db.SetMaxOpenConns(config.MySQLTopologyMaxPoolConnections) 123 db.SetMaxIdleConns(config.MySQLTopologyMaxPoolConnections) 124 return db, err 125 } 126 127 func openOrchestratorMySQLGeneric() (db *sql.DB, fromCache bool, err error) { 128 uri := fmt.Sprintf("%s:%s@tcp(%s:%d)/?timeout=%ds&readTimeout=%ds&interpolateParams=true", 129 config.Config.MySQLOrchestratorUser, 130 config.Config.MySQLOrchestratorPassword, 131 config.Config.MySQLOrchestratorHost, 132 config.Config.MySQLOrchestratorPort, 133 config.Config.MySQLConnectTimeoutSeconds, 134 config.Config.MySQLOrchestratorReadTimeoutSeconds, 135 ) 136 if config.Config.MySQLOrchestratorUseMutualTLS { 137 uri, _ = SetupMySQLOrchestratorTLS(uri) 138 } 139 return sqlutils.GetDB(uri) 140 } 141 142 func IsSQLite() bool { 143 return config.Config.IsSQLite() 144 } 145 146 // OpenTopology returns the DB instance for the orchestrator backed database 147 func OpenOrchestrator() (db *sql.DB, err error) { 148 var fromCache bool 149 if IsSQLite() { 150 db, fromCache, err = sqlutils.GetSQLiteDB(config.Config.SQLite3DataFile) 151 if err == nil && !fromCache { 152 log.Infof("Connected to orchestrator backend: sqlite on %v", config.Config.SQLite3DataFile) 153 } 154 if db != nil { 155 db.SetMaxOpenConns(1) 156 db.SetMaxIdleConns(1) 157 } 158 } else { 159 if db, fromCache, err := openOrchestratorMySQLGeneric(); err != nil { 160 log.Errorf(err.Error()) 161 return db, err 162 } else if !fromCache { 163 // first time ever we talk to MySQL 164 query := fmt.Sprintf("create database if not exists %s", config.Config.MySQLOrchestratorDatabase) 165 if _, err := db.Exec(query); err != nil { 166 log.Errorf(err.Error()) 167 return db, err 168 } 169 } 170 db, fromCache, err = sqlutils.GetDB(getMySQLURI()) 171 if err == nil && !fromCache { 172 // do not show the password but do show what we connect to. 173 safeMySQLURI := fmt.Sprintf("%s:?@tcp(%s:%d)/%s?timeout=%ds", config.Config.MySQLOrchestratorUser, 174 config.Config.MySQLOrchestratorHost, config.Config.MySQLOrchestratorPort, config.Config.MySQLOrchestratorDatabase, config.Config.MySQLConnectTimeoutSeconds) 175 log.Infof("Connected to orchestrator backend: %v", safeMySQLURI) 176 if config.Config.MySQLOrchestratorMaxPoolConnections > 0 { 177 log.Infof("Orchestrator pool SetMaxOpenConns: %d", config.Config.MySQLOrchestratorMaxPoolConnections) 178 db.SetMaxOpenConns(config.Config.MySQLOrchestratorMaxPoolConnections) 179 } 180 if config.Config.MySQLConnectionLifetimeSeconds > 0 { 181 db.SetConnMaxLifetime(time.Duration(config.Config.MySQLConnectionLifetimeSeconds) * time.Second) 182 } 183 } 184 } 185 if err == nil && !fromCache { 186 if !config.Config.SkipOrchestratorDatabaseUpdate { 187 initOrchestratorDB(db) 188 } 189 // A low value here will trigger reconnects which could 190 // make the number of backend connections hit the tcp 191 // limit. That's bad. I could make this setting dynamic 192 // but then people need to know which value to use. For now 193 // allow up to 25% of MySQLOrchestratorMaxPoolConnections 194 // to be idle. That should provide a good number which 195 // does not keep the maximum number of connections open but 196 // at the same time does not trigger disconnections and 197 // reconnections too frequently. 198 maxIdleConns := int(config.Config.MySQLOrchestratorMaxPoolConnections * 25 / 100) 199 if maxIdleConns < 10 { 200 maxIdleConns = 10 201 } 202 log.Infof("Connecting to backend %s:%d: maxConnections: %d, maxIdleConns: %d", 203 config.Config.MySQLOrchestratorHost, 204 config.Config.MySQLOrchestratorPort, 205 config.Config.MySQLOrchestratorMaxPoolConnections, 206 maxIdleConns) 207 db.SetMaxIdleConns(maxIdleConns) 208 } 209 return db, err 210 } 211 212 func translateStatement(statement string) (string, error) { 213 if IsSQLite() { 214 statement = sqlutils.ToSqlite3Dialect(statement) 215 } 216 return statement, nil 217 } 218 219 // versionIsDeployed checks if given version has already been deployed 220 func versionIsDeployed(db *sql.DB) (result bool, err error) { 221 query := ` 222 select 223 count(*) as is_deployed 224 from 225 orchestrator_db_deployments 226 where 227 deployed_version = ? 228 ` 229 err = db.QueryRow(query, config.RuntimeCLIFlags.ConfiguredVersion).Scan(&result) 230 // err means the table 'orchestrator_db_deployments' does not even exist, in which case we proceed 231 // to deploy. 232 // If there's another error to this, like DB gone bad, then we're about to find out anyway. 233 return result, err 234 } 235 236 // registerOrchestratorDeployment updates the orchestrator_metadata table upon successful deployment 237 func registerOrchestratorDeployment(db *sql.DB) error { 238 query := ` 239 replace into orchestrator_db_deployments ( 240 deployed_version, deployed_timestamp 241 ) values ( 242 ?, NOW() 243 ) 244 ` 245 if _, err := execInternal(db, query, config.RuntimeCLIFlags.ConfiguredVersion); err != nil { 246 log.Fatalf("Unable to write to orchestrator_metadata: %+v", err) 247 } 248 log.Infof("Migrated database schema to version [%+v]", config.RuntimeCLIFlags.ConfiguredVersion) 249 return nil 250 } 251 252 // deployStatements will issue given sql queries that are not already known to be deployed. 253 // This iterates both lists (to-run and already-deployed) and also verifies no contraditions. 254 func deployStatements(db *sql.DB, queries []string) error { 255 tx, err := db.Begin() 256 if err != nil { 257 log.Fatal(err.Error()) 258 } 259 // Ugly workaround ahead. 260 // Origin of this workaround is the existence of some "timestamp NOT NULL," column definitions, 261 // where in NO_ZERO_IN_DATE,NO_ZERO_DATE sql_mode are invalid (since default is implicitly "0") 262 // This means installation of orchestrator fails on such configured servers, and in particular on 5.7 263 // where this setting is the dfault. 264 // For purpose of backwards compatability, what we do is force sql_mode to be more relaxed, create the schemas 265 // along with the "invalid" definition, and then go ahead and fix those definitions via following ALTER statements. 266 // My bad. 267 originalSQLMode := "" 268 if config.Config.IsMySQL() { 269 _ = tx.QueryRow(`select @@session.sql_mode`).Scan(&originalSQLMode) 270 if _, err := tx.Exec(`set @@session.sql_mode=REPLACE(@@session.sql_mode, 'NO_ZERO_DATE', '')`); err != nil { 271 log.Fatal(err.Error()) 272 } 273 if _, err := tx.Exec(`set @@session.sql_mode=REPLACE(@@session.sql_mode, 'NO_ZERO_IN_DATE', '')`); err != nil { 274 log.Fatal(err.Error()) 275 } 276 } 277 for _, query := range queries { 278 query, err := translateStatement(query) 279 if err != nil { 280 log.Fatalf("Cannot initiate orchestrator: %+v; query=%+v", err, query) 281 return err 282 } 283 if _, err := tx.Exec(query); err != nil { 284 if strings.Contains(err.Error(), "syntax error") { 285 log.Fatalf("Cannot initiate orchestrator: %+v; query=%+v", err, query) 286 return err 287 } 288 if !sqlutils.IsAlterTable(query) && !sqlutils.IsCreateIndex(query) && !sqlutils.IsDropIndex(query) { 289 log.Fatalf("Cannot initiate orchestrator: %+v; query=%+v", err, query) 290 return err 291 } 292 if !strings.Contains(err.Error(), "duplicate column name") && 293 !strings.Contains(err.Error(), "Duplicate column name") && 294 !strings.Contains(err.Error(), "check that column/key exists") && 295 !strings.Contains(err.Error(), "already exists") && 296 !strings.Contains(err.Error(), "Duplicate key name") { 297 log.Errorf("Error initiating orchestrator: %+v; query=%+v", err, query) 298 } 299 } 300 } 301 if config.Config.IsMySQL() { 302 if _, err := tx.Exec(`set session sql_mode=?`, originalSQLMode); err != nil { 303 log.Fatal(err.Error()) 304 } 305 } 306 if err := tx.Commit(); err != nil { 307 log.Fatal(err.Error()) 308 } 309 return nil 310 } 311 312 // initOrchestratorDB attempts to create/upgrade the orchestrator backend database. It is created once in the 313 // application's lifetime. 314 func initOrchestratorDB(db *sql.DB) error { 315 log.Info("Initializing orchestrator") 316 317 versionAlreadyDeployed, err := versionIsDeployed(db) 318 if versionAlreadyDeployed && config.RuntimeCLIFlags.ConfiguredVersion != "" && err == nil { 319 // Already deployed with this version 320 return nil 321 } 322 if config.Config.PanicIfDifferentDatabaseDeploy && config.RuntimeCLIFlags.ConfiguredVersion != "" && !versionAlreadyDeployed { 323 log.Fatalf("PanicIfDifferentDatabaseDeploy is set. Configured version %s is not the version found in the database", config.RuntimeCLIFlags.ConfiguredVersion) 324 } 325 log.Info("Migrating database schema") 326 deployStatements(db, generateSQLBase) 327 deployStatements(db, generateSQLPatches) 328 registerOrchestratorDeployment(db) 329 330 if IsSQLite() { 331 ExecOrchestrator(`PRAGMA journal_mode = WAL`) 332 ExecOrchestrator(`PRAGMA synchronous = NORMAL`) 333 } 334 335 return nil 336 } 337 338 // execInternal 339 func execInternal(db *sql.DB, query string, args ...any) (sql.Result, error) { 340 var err error 341 query, err = translateStatement(query) 342 if err != nil { 343 return nil, err 344 } 345 res, err := sqlutils.ExecNoPrepare(db, query, args...) 346 return res, err 347 } 348 349 // ExecOrchestrator will execute given query on the orchestrator backend database. 350 func ExecOrchestrator(query string, args ...any) (sql.Result, error) { 351 var err error 352 query, err = translateStatement(query) 353 if err != nil { 354 return nil, err 355 } 356 db, err := OpenOrchestrator() 357 if err != nil { 358 return nil, err 359 } 360 res, err := sqlutils.ExecNoPrepare(db, query, args...) 361 return res, err 362 } 363 364 // QueryOrchestrator 365 func QueryOrchestrator(query string, argsArray []any, onRow func(sqlutils.RowMap) error) error { 366 query, err := translateStatement(query) 367 if err != nil { 368 log.Fatalf("Cannot query orchestrator: %+v; query=%+v", err, query) 369 return err 370 } 371 db, err := OpenOrchestrator() 372 if err != nil { 373 return err 374 } 375 376 if err = sqlutils.QueryRowsMap(db, query, onRow, argsArray...); err != nil { 377 log.Warning(err.Error()) 378 } 379 380 return err 381 }