github.com/percona/percona-xtradb-cluster-operator@v1.14.0/pkg/pxc/queries/queries.go (about) 1 package queries 2 3 import ( 4 "context" 5 "database/sql" 6 "fmt" 7 "strings" 8 9 "github.com/pkg/errors" 10 11 "github.com/go-sql-driver/mysql" 12 corev1 "k8s.io/api/core/v1" 13 "k8s.io/apimachinery/pkg/types" 14 "sigs.k8s.io/controller-runtime/pkg/client" 15 ) 16 17 type ReplicationStatus int8 18 19 const ( 20 ReplicationStatusActive ReplicationStatus = iota 21 ReplicationStatusError 22 ReplicationStatusNotInitiated 23 ) 24 25 const ( 26 WriterHostgroup = "writer_hostgroup" 27 ReaderHostgroup = "reader_hostgroup" 28 ) 29 30 // value of writer group is hardcoded in ProxySQL config inside docker image 31 // https://github.com/percona/percona-docker/blob/pxc-operator-1.3.0/proxysql/dockerdir/etc/proxysql-admin.cnf#L23 32 const writerID = 11 33 34 type Database struct { 35 db *sql.DB 36 } 37 38 type ReplicationConfig struct { 39 Source ReplicationChannelSource 40 SourceRetryCount uint 41 SourceConnectRetry uint 42 SSL bool 43 SSLSkipVerify bool 44 CA string 45 } 46 47 type ReplicationChannelSource struct { 48 Name string 49 Host string 50 Port int 51 Weight int 52 } 53 54 var ErrNotFound = errors.New("not found") 55 56 func New(client client.Client, namespace, secretName, user, host string, port int32, timeout int32) (Database, error) { 57 secretObj := corev1.Secret{} 58 err := client.Get(context.TODO(), 59 types.NamespacedName{ 60 Namespace: namespace, 61 Name: secretName, 62 }, 63 &secretObj, 64 ) 65 if err != nil { 66 return Database{}, err 67 } 68 69 timeoutStr := fmt.Sprintf("%ds", timeout) 70 config := mysql.NewConfig() 71 config.User = user 72 config.Passwd = string(secretObj.Data[user]) 73 config.Net = "tcp" 74 config.DBName = "mysql" 75 config.Addr = fmt.Sprintf("%s:%d", host, port) 76 config.Params = map[string]string{ 77 "interpolateParams": "true", 78 "timeout": timeoutStr, 79 "readTimeout": timeoutStr, 80 "writeTimeout": timeoutStr, 81 "tls": "preferred", 82 } 83 84 db, err := sql.Open("mysql", config.FormatDSN()) 85 if err != nil { 86 return Database{}, err 87 } 88 89 err = db.Ping() 90 if err != nil { 91 return Database{}, err 92 } 93 94 return Database{ 95 db: db, 96 }, nil 97 } 98 99 func (p *Database) CurrentReplicationChannels() ([]string, error) { 100 rows, err := p.db.Query(`SELECT DISTINCT(Channel_name) from replication_asynchronous_connection_failover`) 101 if err != nil { 102 if errors.Is(err, sql.ErrNoRows) { 103 return nil, nil 104 } 105 return nil, errors.Wrap(err, "select current replication channels") 106 } 107 108 defer rows.Close() 109 110 result := make([]string, 0) 111 for rows.Next() { 112 src := "" 113 err = rows.Scan(&src) 114 if err != nil { 115 return nil, errors.Wrap(err, "scan channel name") 116 } 117 result = append(result, src) 118 } 119 return result, nil 120 } 121 122 func (p *Database) ChangeChannelPassword(channel, password string) error { 123 tx, err := p.db.Begin() 124 if err != nil { 125 return errors.Wrap(err, "start transaction for updating channel password") 126 } 127 _, err = tx.Exec(`STOP REPLICA IO_THREAD FOR CHANNEL ?`, channel) 128 if err != nil { 129 errT := tx.Rollback() 130 if errT != nil { 131 return errors.Wrapf(err, "rollback STOP REPLICA IO_THREAD FOR CHANNEL %s", channel) 132 } 133 return errors.Wrapf(err, "stop replication IO thread for channel %s", channel) 134 } 135 _, err = tx.Exec(`CHANGE REPLICATION SOURCE TO SOURCE_PASSWORD=? FOR CHANNEL ?`, password, channel) 136 if err != nil { 137 errT := tx.Rollback() 138 if errT != nil { 139 return errors.Wrapf(err, "rollback CHANGE SOURCE_PASSWORD FOR CHANNEL %s", channel) 140 } 141 return errors.Wrapf(err, "change master password for channel %s", channel) 142 } 143 _, err = tx.Exec(`START REPLICA IO_THREAD FOR CHANNEL ?`, channel) 144 if err != nil { 145 errT := tx.Rollback() 146 if errT != nil { 147 return errors.Wrapf(err, "rollback START REPLICA IO_THREAD FOR CHANNEL %s", channel) 148 } 149 return errors.Wrapf(err, "start io thread for channel %s", channel) 150 } 151 return tx.Commit() 152 } 153 154 func (p *Database) ShowReplicaStatus(ctx context.Context, channel string) (map[string]string, error) { 155 rows, err := p.db.Query(`SHOW REPLICA STATUS FOR CHANNEL ?`, channel) 156 if err != nil { 157 return nil, err 158 } 159 defer rows.Close() 160 columns, err := rows.Columns() 161 if err != nil { 162 return nil, err 163 } 164 ok := rows.Next() 165 if !ok { 166 return make(map[string]string), nil 167 } 168 169 values := make([]any, 0, len(columns)) 170 for range columns { 171 values = append(values, new([]byte)) 172 } 173 status := make(map[string]string, len(columns)) 174 175 if err := rows.Scan(values...); err != nil { 176 return nil, err 177 } 178 179 for i, name := range columns { 180 ptr, ok := values[i].(*[]byte) 181 if !ok { 182 return nil, errors.Errorf("failed to convert %T to *[]byte: %s", values[i], name) 183 } 184 status[name] = string(*ptr) 185 } 186 187 return status, nil 188 } 189 190 func (p *Database) ReplicationStatus(ctx context.Context, channel string) (ReplicationStatus, error) { 191 statusMap, err := p.ShowReplicaStatus(ctx, channel) 192 if err != nil { 193 if strings.HasSuffix(err.Error(), "does not exist.") || errors.Is(err, sql.ErrNoRows) { 194 return ReplicationStatusNotInitiated, nil 195 } 196 return ReplicationStatusError, errors.Wrap(err, "get current replica status") 197 } 198 199 IORunning := statusMap["Replica_IO_Running"] 200 SQLRunning := statusMap["Replica_SQL_Running"] 201 LastErrNo := statusMap["Last_Errno"] 202 if IORunning == "Yes" && SQLRunning == "Yes" { 203 return ReplicationStatusActive, nil 204 } 205 206 if IORunning == "No" && SQLRunning == "No" && LastErrNo == "0" { 207 return ReplicationStatusNotInitiated, nil 208 } 209 210 return ReplicationStatusError, nil 211 } 212 213 func (p *Database) StopAllReplication() error { 214 _, err := p.db.Exec("STOP REPLICA") 215 return errors.Wrap(err, "failed to stop replication") 216 } 217 218 func (p *Database) AddReplicationSource(name, host string, port, weight int) error { 219 _, err := p.db.Exec("SELECT asynchronous_connection_failover_add_source(?, ?, ?, null, ?)", name, host, port, weight) 220 return errors.Wrap(err, "add replication source") 221 } 222 223 func (p *Database) ReplicationChannelSources(channelName string) ([]ReplicationChannelSource, error) { 224 rows, err := p.db.Query(` 225 SELECT host, 226 port, 227 weight 228 FROM replication_asynchronous_connection_failover 229 WHERE channel_name = ? 230 `, channelName) 231 if err != nil { 232 if errors.Is(err, sql.ErrNoRows) { 233 return nil, ErrNotFound 234 } 235 return nil, errors.Wrap(err, "get replication channels") 236 } 237 defer rows.Close() 238 result := make([]ReplicationChannelSource, 0) 239 for rows.Next() { 240 r := ReplicationChannelSource{} 241 err = rows.Scan(&r.Host, &r.Port, &r.Weight) 242 if err != nil { 243 return nil, errors.Wrap(err, "read replication channel info") 244 } 245 result = append(result, r) 246 } 247 return result, nil 248 } 249 250 func (p *Database) StopReplication(name string) error { 251 _, err := p.db.Exec("STOP REPLICA FOR CHANNEL ?", name) 252 return errors.Wrap(err, "stop replication for channel "+name) 253 } 254 255 func (p *Database) EnableReadonly() error { 256 _, err := p.db.Exec("SET GLOBAL READ_ONLY=1") 257 return errors.Wrap(err, "set global read_only param to 1") 258 } 259 260 func (p *Database) DisableReadonly() error { 261 _, err := p.db.Exec("SET GLOBAL READ_ONLY=0") 262 return errors.Wrap(err, "set global read_only param to 0") 263 } 264 265 func (p *Database) IsReadonly() (bool, error) { 266 readonly := 0 267 err := p.db.QueryRow("select @@read_only").Scan(&readonly) 268 return readonly == 1, errors.Wrap(err, "select global read_only param") 269 } 270 271 func (p *Database) StartReplication(replicaPass string, config ReplicationConfig, shouldGetMasterKey bool) error { 272 var ca string 273 var ssl int 274 if config.SSL { 275 ssl = 1 276 ca = config.CA 277 } 278 279 var sslVerify int 280 if !config.SSLSkipVerify { 281 sslVerify = 1 282 } 283 284 _, err := p.db.Exec(` 285 CHANGE REPLICATION SOURCE TO 286 SOURCE_USER='replication', 287 SOURCE_PASSWORD=?, 288 SOURCE_HOST=?, 289 SOURCE_PORT=?, 290 SOURCE_CONNECTION_AUTO_FAILOVER=1, 291 SOURCE_AUTO_POSITION=1, 292 SOURCE_RETRY_COUNT=?, 293 SOURCE_CONNECT_RETRY=?, 294 SOURCE_SSL=?, 295 SOURCE_SSL_CA=?, 296 SOURCE_SSL_VERIFY_SERVER_CERT=? 297 FOR CHANNEL ? 298 `, replicaPass, config.Source.Host, config.Source.Port, config.SourceRetryCount, config.SourceConnectRetry, ssl, ca, sslVerify, config.Source.Name) 299 if err != nil { 300 return errors.Wrapf(err, "change source for channel %s", config.Source.Name) 301 } 302 303 if shouldGetMasterKey { 304 _, err = p.db.Exec(`CHANGE MASTER TO GET_MASTER_PUBLIC_KEY=1 FOR CHANNEL ?`, config.Source.Name) 305 if err != nil { 306 return errors.Wrapf(err, "change master to GET_MASTER_PUBLIC_KEY for channel %s", config.Source.Name) 307 } 308 } 309 310 _, err = p.db.Exec(`START REPLICA FOR CHANNEL ?`, config.Source.Name) 311 return errors.Wrapf(err, "start replica for source %s", config.Source.Name) 312 } 313 314 func (p *Database) DeleteReplicationSource(name, host string, port int) error { 315 _, err := p.db.Exec("SELECT asynchronous_connection_failover_delete_source(?, ?, ?, null)", name, host, port) 316 return errors.Wrap(err, "delete replication source "+name) 317 } 318 319 func (p *Database) ProxySQLInstanceStatus(host string) ([]string, error) { 320 rows, err := p.db.Query("SELECT status FROM mysql_servers WHERE hostname LIKE ?;", host+"%") 321 if err != nil { 322 if errors.Is(err, sql.ErrNoRows) { 323 return nil, ErrNotFound 324 } 325 return nil, err 326 } 327 328 statuses := []string{} 329 for rows.Next() { 330 var status string 331 332 err := rows.Scan(&status) 333 if err != nil { 334 return nil, err 335 } 336 337 statuses = append(statuses, status) 338 } 339 340 return statuses, nil 341 } 342 343 func (p *Database) PresentInHostgroups(host string) (bool, error) { 344 hostgroups := []string{WriterHostgroup, ReaderHostgroup} 345 query := fmt.Sprintf(`SELECT COUNT(*) FROM mysql_servers 346 INNER JOIN mysql_galera_hostgroups ON hostgroup_id IN (%s) 347 WHERE hostname LIKE ? GROUP BY hostname`, strings.Join(hostgroups, ",")) 348 var count int 349 err := p.db.QueryRow(query, host+"%").Scan(&count) 350 if err != nil { 351 if errors.Is(err, sql.ErrNoRows) { 352 return false, ErrNotFound 353 } 354 return false, err 355 } 356 if count != len(hostgroups) { 357 return false, nil 358 } 359 return true, nil 360 } 361 362 func (p *Database) PrimaryHost() (string, error) { 363 var host string 364 err := p.db.QueryRow("SELECT hostname FROM runtime_mysql_servers WHERE hostgroup_id = ?", writerID).Scan(&host) 365 if err != nil { 366 if errors.Is(err, sql.ErrNoRows) { 367 return "", ErrNotFound 368 } 369 return "", err 370 } 371 372 return host, nil 373 } 374 375 func (p *Database) Hostname() (string, error) { 376 var hostname string 377 err := p.db.QueryRow("SELECT @@hostname hostname").Scan(&hostname) 378 if err != nil { 379 if errors.Is(err, sql.ErrNoRows) { 380 return "", ErrNotFound 381 } 382 return "", err 383 } 384 385 return hostname, nil 386 } 387 388 func (p *Database) WsrepLocalStateComment() (string, error) { 389 var variable_name string 390 var value string 391 392 err := p.db.QueryRow("SHOW GLOBAL STATUS LIKE 'wsrep_local_state_comment'").Scan(&variable_name, &value) 393 if err != nil { 394 if err == sql.ErrNoRows { 395 return "", fmt.Errorf("variable was not found") 396 } 397 return "", err 398 } 399 400 return value, nil 401 } 402 403 func (p *Database) Version() (string, error) { 404 var version string 405 406 err := p.db.QueryRow("select @@VERSION;").Scan(&version) 407 if err != nil { 408 if err == sql.ErrNoRows { 409 return "", fmt.Errorf("variable was not found") 410 } 411 return "", err 412 } 413 414 return version, nil 415 } 416 417 func (p *Database) ReadVariable(variable string) (string, error) { 418 var varName string 419 var value string 420 421 err := p.db.QueryRow("SHOW VARIABLES LIKE ?", variable).Scan(&varName, &value) 422 if err != nil { 423 if err == sql.ErrNoRows { 424 return "", fmt.Errorf("variable was not found") 425 } 426 return "", err 427 } 428 429 return value, nil 430 } 431 432 func (p *Database) Close() error { 433 return p.db.Close() 434 }