github.com/percona/percona-xtradb-cluster-operator@v1.14.0/cmd/pitr/pxc/pxc.go (about) 1 package pxc 2 3 import ( 4 "context" 5 "database/sql" 6 "log" 7 "os/exec" 8 "sort" 9 "strconv" 10 "strings" 11 12 "github.com/go-sql-driver/mysql" 13 "github.com/pkg/errors" 14 ) 15 16 const UsingPassErrorMessage = `mysqlbinlog: [Warning] Using a password on the command line interface can be insecure.` 17 18 // PXC is a type for working with pxc 19 type PXC struct { 20 db *sql.DB // handle for work with database 21 host string // host for connection 22 } 23 24 // NewManager return new manager for work with pxc 25 func NewPXC(addr string, user, pass string) (*PXC, error) { 26 var pxc PXC 27 28 config := mysql.NewConfig() 29 config.User = user 30 config.Passwd = pass 31 config.Net = "tcp" 32 config.Addr = addr + ":33062" 33 config.Params = map[string]string{"interpolateParams": "true"} 34 35 mysqlDB, err := sql.Open("mysql", config.FormatDSN()) 36 if err != nil { 37 return nil, errors.Wrap(err, "cannot connect to host") 38 } 39 40 pxc.db = mysqlDB 41 pxc.host = addr 42 43 return &pxc, nil 44 } 45 46 // Close is for closing db connection 47 func (p *PXC) Close() error { 48 return p.db.Close() 49 } 50 51 // GetHost returns pxc host 52 func (p *PXC) GetHost() string { 53 return p.host 54 } 55 56 // GetGTIDSet return GTID set by binary log file name 57 func (p *PXC) GetGTIDSet(ctx context.Context, binlogName string) (string, error) { 58 //select name from mysql.func where name='get_gtid_set_by_binlog' 59 var existFunc string 60 nameRow := p.db.QueryRowContext(ctx, "select name from mysql.func where name='get_gtid_set_by_binlog'") 61 err := nameRow.Scan(&existFunc) 62 if err != nil && err != sql.ErrNoRows { 63 return "", errors.Wrap(err, "get udf name") 64 } 65 if len(existFunc) == 0 { 66 _, err = p.db.ExecContext(ctx, "CREATE FUNCTION get_gtid_set_by_binlog RETURNS STRING SONAME 'binlog_utils_udf.so'") 67 if err != nil { 68 return "", errors.Wrap(err, "create function") 69 } 70 } 71 var binlogSet string 72 row := p.db.QueryRowContext(ctx, "SELECT get_gtid_set_by_binlog(?)", binlogName) 73 err = row.Scan(&binlogSet) 74 if err != nil && !strings.Contains(err.Error(), "Binary log does not exist") { 75 return "", errors.Wrap(err, "scan set") 76 } 77 78 return binlogSet, nil 79 } 80 81 type Binlog struct { 82 Name string 83 Size int64 84 Encrypted string 85 GTIDSet GTIDSet 86 } 87 88 type GTIDSet struct { 89 gtidSet string 90 } 91 92 func NewGTIDSet(gtidSet string) GTIDSet { 93 return GTIDSet{gtidSet: gtidSet} 94 } 95 96 func (s *GTIDSet) IsEmpty() bool { 97 return len(s.gtidSet) == 0 98 } 99 100 func (s *GTIDSet) Raw() string { 101 return s.gtidSet 102 } 103 104 func (s *GTIDSet) List() []string { 105 if len(s.gtidSet) == 0 { 106 return nil 107 } 108 list := strings.Split(s.gtidSet, ",") 109 sort.Strings(list) 110 return list 111 } 112 113 // GetBinLogList return binary log files list 114 func (p *PXC) GetBinLogList(ctx context.Context) ([]Binlog, error) { 115 rows, err := p.db.QueryContext(ctx, "SHOW BINARY LOGS") 116 if err != nil { 117 return nil, errors.Wrap(err, "show binary logs") 118 } 119 120 var binlogs []Binlog 121 for rows.Next() { 122 var b Binlog 123 if err := rows.Scan(&b.Name, &b.Size, &b.Encrypted); err != nil { 124 return nil, errors.Wrap(err, "scan binlogs") 125 } 126 binlogs = append(binlogs, b) 127 } 128 129 _, err = p.db.ExecContext(ctx, "FLUSH BINARY LOGS") 130 if err != nil { 131 return nil, errors.Wrap(err, "flush binary logs") 132 } 133 134 return binlogs, nil 135 } 136 137 // GetBinLogList return binary log files list 138 func (p *PXC) GetBinLogNamesList(ctx context.Context) ([]string, error) { 139 rows, err := p.db.QueryContext(ctx, "SHOW BINARY LOGS") 140 if err != nil { 141 return nil, errors.Wrap(err, "show binary logs") 142 } 143 defer rows.Close() 144 145 var binlogs []string 146 for rows.Next() { 147 var b Binlog 148 if err := rows.Scan(&b.Name, &b.Size, &b.Encrypted); err != nil { 149 return nil, errors.Wrap(err, "scan binlogs") 150 } 151 binlogs = append(binlogs, b.Name) 152 } 153 154 return binlogs, nil 155 } 156 157 func (p *PXC) GTIDSubset(ctx context.Context, set1, set2 string) (bool, error) { 158 row := p.db.QueryRowContext(ctx, "SELECT GTID_SUBSET(?,?)", set1, set2) 159 var result int 160 if err := row.Scan(&result); err != nil { 161 return false, errors.Wrap(err, "scan result") 162 } 163 164 return result == 1, nil 165 } 166 167 // GetBinLogFirstTimestamp return binary log file first timestamp 168 func (p *PXC) GetBinLogFirstTimestamp(ctx context.Context, binlog string) (string, error) { 169 var existFunc string 170 nameRow := p.db.QueryRowContext(ctx, "select name from mysql.func where name='get_first_record_timestamp_by_binlog'") 171 err := nameRow.Scan(&existFunc) 172 if err != nil && err != sql.ErrNoRows { 173 return "", errors.Wrap(err, "get udf name") 174 } 175 if len(existFunc) == 0 { 176 _, err = p.db.ExecContext(ctx, "CREATE FUNCTION get_first_record_timestamp_by_binlog RETURNS INTEGER SONAME 'binlog_utils_udf.so'") 177 if err != nil { 178 return "", errors.Wrap(err, "create function") 179 } 180 } 181 var timestamp string 182 row := p.db.QueryRowContext(ctx, "SELECT get_first_record_timestamp_by_binlog(?) DIV 1000000", binlog) 183 184 err = row.Scan(×tamp) 185 if err != nil { 186 return "", errors.Wrap(err, "scan binlog timestamp") 187 } 188 189 return timestamp, nil 190 } 191 192 // GetBinLogLastTimestamp return binary log file last timestamp 193 func (p *PXC) GetBinLogLastTimestamp(ctx context.Context, binlog string) (string, error) { 194 var existFunc string 195 nameRow := p.db.QueryRowContext(ctx, "select name from mysql.func where name='get_last_record_timestamp_by_binlog'") 196 err := nameRow.Scan(&existFunc) 197 if err != nil && err != sql.ErrNoRows { 198 return "", errors.Wrap(err, "get udf name") 199 } 200 if len(existFunc) == 0 { 201 _, err = p.db.ExecContext(ctx, "CREATE FUNCTION get_last_record_timestamp_by_binlog RETURNS INTEGER SONAME 'binlog_utils_udf.so'") 202 if err != nil { 203 return "", errors.Wrap(err, "create function") 204 } 205 } 206 var timestamp string 207 row := p.db.QueryRowContext(ctx, "SELECT get_last_record_timestamp_by_binlog(?) DIV 1000000", binlog) 208 209 err = row.Scan(×tamp) 210 if err != nil { 211 return "", errors.Wrap(err, "scan binlog timestamp") 212 } 213 214 return timestamp, nil 215 } 216 217 func (p *PXC) SubtractGTIDSet(ctx context.Context, set, subSet string) (string, error) { 218 var result string 219 row := p.db.QueryRowContext(ctx, "SELECT GTID_SUBTRACT(?,?)", set, subSet) 220 err := row.Scan(&result) 221 if err != nil { 222 return "", errors.Wrap(err, "scan gtid subtract result") 223 } 224 225 return result, nil 226 } 227 228 func getNodesByServiceName(ctx context.Context, pxcServiceName string) ([]string, error) { 229 cmd := exec.CommandContext(ctx, "peer-list", "-on-start=/usr/bin/get-pxc-state", "-service="+pxcServiceName) 230 out, err := cmd.CombinedOutput() 231 if err != nil { 232 return nil, errors.Wrap(err, "get peer-list output") 233 } 234 return strings.Split(string(out), "node:"), nil 235 } 236 237 func GetPXCFirstHost(ctx context.Context, pxcServiceName string) (string, error) { 238 nodes, err := getNodesByServiceName(ctx, pxcServiceName) 239 if err != nil { 240 return "", errors.Wrap(err, "get nodes by service name") 241 } 242 sort.Strings(nodes) 243 lastHost := "" 244 for _, node := range nodes { 245 if strings.Contains(node, "wsrep_ready:ON:wsrep_connected:ON:wsrep_local_state_comment:Synced:wsrep_cluster_status:Primary") { 246 nodeArr := strings.Split(node, ":") 247 lastHost = nodeArr[0] 248 break 249 } 250 } 251 if len(lastHost) == 0 { 252 return "", errors.New("can't find host") 253 } 254 255 return lastHost, nil 256 } 257 258 func GetPXCOldestBinlogHost(ctx context.Context, pxcServiceName, user, pass string) (string, error) { 259 nodes, err := getNodesByServiceName(ctx, pxcServiceName) 260 if err != nil { 261 return "", errors.Wrap(err, "get nodes by service name") 262 } 263 264 var oldestHost string 265 var oldestTS int64 266 for _, node := range nodes { 267 if strings.Contains(node, "wsrep_ready:ON:wsrep_connected:ON:wsrep_local_state_comment:Synced:wsrep_cluster_status:Primary") { 268 nodeArr := strings.Split(node, ":") 269 binlogTime, err := getBinlogTime(ctx, nodeArr[0], user, pass) 270 if err != nil { 271 log.Printf("ERROR: get binlog time %v", err) 272 continue 273 } 274 if len(oldestHost) == 0 || oldestTS > 0 && binlogTime < oldestTS { 275 oldestHost = nodeArr[0] 276 oldestTS = binlogTime 277 } 278 279 } 280 } 281 282 if len(oldestHost) == 0 { 283 return "", errors.New("can't find host") 284 } 285 286 return oldestHost, nil 287 } 288 289 func getBinlogTime(ctx context.Context, host, user, pass string) (int64, error) { 290 db, err := NewPXC(host, user, pass) 291 if err != nil { 292 return 0, errors.Errorf("creating connection for host %s: %v", host, err) 293 } 294 defer db.Close() 295 list, err := db.GetBinLogNamesList(ctx) 296 if err != nil { 297 return 0, errors.Errorf("get binlog list for host %s: %v", host, err) 298 } 299 if len(list) == 0 { 300 return 0, errors.Errorf("get binlog list for host %s: no binlogs found", host) 301 } 302 var binlogTime int64 303 for _, binlogName := range list { 304 binlogTime, err = getBinlogTimeByName(ctx, db, binlogName) 305 if err != nil { 306 log.Printf("ERROR: get binlog timestamp for binlog %s host %s: %v", binlogName, host, err) 307 continue 308 } 309 if binlogTime > 0 { 310 break 311 } 312 } 313 if binlogTime == 0 { 314 return 0, errors.Errorf("get binlog oldest timestamp for host %s: no binlogs timestamp found", host) 315 } 316 317 return binlogTime, nil 318 } 319 320 func getBinlogTimeByName(ctx context.Context, db *PXC, binlogName string) (int64, error) { 321 ts, err := db.GetBinLogFirstTimestamp(ctx, binlogName) 322 if err != nil { 323 return 0, errors.Wrap(err, "get binlog first timestamp") 324 } 325 binlogTime, err := strconv.ParseInt(ts, 10, 64) 326 if err != nil { 327 return 0, errors.Wrap(err, "parse timestamp") 328 } 329 330 return binlogTime, nil 331 } 332 333 func (p *PXC) DropCollectorFunctions(ctx context.Context) error { 334 _, err := p.db.ExecContext(ctx, "DROP FUNCTION IF EXISTS get_first_record_timestamp_by_binlog") 335 if err != nil { 336 return errors.Wrap(err, "drop get_first_record_timestamp_by_binlog function") 337 } 338 _, err = p.db.ExecContext(ctx, "DROP FUNCTION IF EXISTS get_binlog_by_gtid_set") 339 if err != nil { 340 return errors.Wrap(err, "drop get_binlog_by_gtid_set function") 341 } 342 343 _, err = p.db.ExecContext(ctx, "DROP FUNCTION IF EXISTS get_gtid_set_by_binlog") 344 if err != nil { 345 return errors.Wrap(err, "drop get_gtid_set_by_binlog function") 346 } 347 348 return nil 349 }