github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/server/mysql/mysql.go (about) 1 // Copyright 2017 Google Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package mysql implements the fleetspeak datastore interface using a mysql 16 // database. 17 // 18 // NOTE: Currently this is a fairly direct port of the sqlite datastore and not 19 // at all performant. TODO: Optimize and load test this. 20 package mysql 21 22 import ( 23 "context" 24 "database/sql" 25 "time" 26 27 "github.com/go-sql-driver/mysql" 28 log "github.com/golang/glog" 29 ) 30 31 const maxRetries = 50 32 33 const mysql_ER_LOCK_WAIT_TIMEOUT = 1205 34 const mysql_ER_LOCK_DEADLOCK = 1213 35 const mysql_ER_TOO_MANY_CONCURRENT_TRXS = 1637 36 37 // Datastore wraps a mysql backed sql.DB and implements db.Store. 38 type Datastore struct { 39 db *sql.DB 40 looper *messageLooper 41 } 42 43 // MakeDatastore creates any missing tables and returns a Datastore. The db 44 // parameter must be connected to a mysql database, e.g. using the mymysql 45 // driver. 46 func MakeDatastore(db *sql.DB) (*Datastore, error) { 47 err := initDB(db) 48 if err != nil { 49 return nil, err 50 } 51 err = initSchema(db) 52 if err != nil { 53 return nil, err 54 } 55 return &Datastore{db: db}, nil 56 } 57 58 // Close closes the underlying database resources. 59 func (d *Datastore) Close() error { 60 return d.db.Close() 61 } 62 63 // runOnce runs f, passing it a transaction. The transaction will be committed 64 // if f returns nil, otherwise rolled back. 65 func (d *Datastore) runOnce(ctx context.Context, readOnly bool, f func(*sql.Tx) error) error { 66 // TODO: Pass along the readOnly flag, once some mysql driver supports it. 67 tx, err := d.db.BeginTx(ctx, nil) 68 if err != nil { 69 return err 70 } 71 err = f(tx) 72 if err != nil { 73 tx.Rollback() 74 return err 75 } 76 return tx.Commit() 77 } 78 79 // runInTx runs f in a transaction. If the transaction returns a retriable MySQL 80 // error, the transaction will be retried (and f will be run again). 81 func (d *Datastore) runInTx(ctx context.Context, readOnly bool, f func(*sql.Tx) error) error { 82 var err error 83 for i := range maxRetries { 84 err = d.runOnce(ctx, readOnly, f) 85 e, ok := err.(*mysql.MySQLError) 86 if !ok { 87 return err 88 } 89 switch e.Number { 90 case mysql_ER_LOCK_WAIT_TIMEOUT, mysql_ER_LOCK_DEADLOCK, mysql_ER_TOO_MANY_CONCURRENT_TRXS: 91 t := time.NewTimer(time.Duration(i*100) * time.Millisecond) 92 select { 93 case <-t.C: 94 continue 95 case <-ctx.Done(): 96 t.Stop() 97 return err 98 } 99 default: 100 return err 101 } 102 } 103 return err 104 } 105 106 // IsNotFound implements db.Store. 107 func (d *Datastore) IsNotFound(err error) bool { 108 return err == sql.ErrNoRows 109 } 110 111 func initDB(db *sql.DB) error { 112 db.SetMaxIdleConns(100) 113 db.SetMaxOpenConns(100) 114 return nil 115 } 116 117 func initSchema(db *sql.DB) error { 118 for _, s := range []string{ 119 `CREATE TABLE IF NOT EXISTS clients( 120 client_id BINARY(8) PRIMARY KEY, 121 client_key BLOB, 122 blacklisted BOOL NOT NULL, 123 last_contact_time BIGINT NOT NULL, 124 last_contact_address TEXT(64), 125 last_contact_streaming_to TEXT(128), 126 last_clock_seconds BIGINT UNSIGNED, 127 last_clock_nanos INT UNSIGNED)`, 128 `CREATE TABLE IF NOT EXISTS client_labels( 129 client_id BINARY(8) NOT NULL, 130 service_name VARCHAR(128) NOT NULL, 131 label VARCHAR(128) NOT NULL, 132 PRIMARY KEY (client_id, service_name, label), 133 CONSTRAINT client_labels_fk_1 134 FOREIGN KEY (client_id) 135 REFERENCES clients(client_id) 136 ON DELETE CASCADE)`, 137 `CREATE TABLE IF NOT EXISTS client_contacts( 138 client_contact_id INTEGER NOT NULL AUTO_INCREMENT, 139 client_id BINARY(8) NOT NULL, 140 time BIGINT NOT NULL, 141 sent_nonce BINARY(8) NOT NULL, 142 received_nonce BINARY(8) NOT NULL, 143 address VARCHAR(64), 144 PRIMARY KEY (client_contact_id), 145 CONSTRAINT client_contacts_fk_1 146 FOREIGN KEY (client_id) 147 REFERENCES clients(client_id) 148 ON DELETE CASCADE)`, 149 `CREATE TABLE IF NOT EXISTS client_resource_usage_records( 150 client_id BINARY(8) NOT NULL, 151 scope VARCHAR(128) NOT NULL, 152 pid BIGINT, 153 process_start_time BIGINT, 154 client_timestamp BIGINT, 155 server_timestamp BIGINT, 156 process_terminated BOOL NOT NULL, 157 mean_user_cpu_rate REAL, 158 max_user_cpu_rate REAL, 159 mean_system_cpu_rate REAL, 160 max_system_cpu_rate REAL, 161 mean_resident_memory_mib INT4, 162 max_resident_memory_mib INT4, 163 mean_num_fds INT, 164 max_num_fds INT, 165 CONSTRAINT client_resource_usage_records_fk_1 166 FOREIGN KEY (client_id) 167 REFERENCES clients(client_id) 168 ON DELETE CASCADE)`, 169 `CREATE TABLE IF NOT EXISTS messages( 170 message_id BINARY(32) NOT NULL, 171 source_client_id BINARY(8), 172 source_service_name VARCHAR(128) NOT NULL, 173 source_message_id VARBINARY(16) NULL, 174 destination_client_id BINARY(8), 175 destination_service_name VARCHAR(128) NOT NULL, 176 message_type VARCHAR(128), 177 creation_time_seconds BIGINT NOT NULL, 178 creation_time_nanos INT NOT NULL, 179 processed_time_seconds BIGINT, 180 processed_time_nanos INT, 181 validation_info BLOB, 182 failed INT1, 183 failed_reason TEXT, 184 annotations BLOB, 185 PRIMARY KEY (message_id))`, 186 `CREATE TABLE IF NOT EXISTS pending_messages( 187 for_server BOOL NOT NULL, 188 message_id BINARY(32) NOT NULL, 189 retry_count INT NOT NULL, 190 scheduled_time BIGINT NOT NULL, 191 data_type_url TEXT, 192 data_value MEDIUMBLOB, 193 PRIMARY KEY (for_server, message_id), 194 CONSTRAINT pending_messages_fk_1 195 FOREIGN KEY (message_id) 196 REFERENCES messages(message_id) 197 ON DELETE CASCADE)`, 198 `CREATE TABLE IF NOT EXISTS client_contact_messages( 199 client_contact_id INTEGER NOT NULL, 200 message_id BINARY(32) NOT NULL, 201 PRIMARY KEY (client_contact_id, message_id), 202 CONSTRAINT client_contact_messages_fk_1 203 FOREIGN KEY (client_contact_id) 204 REFERENCES client_contacts(client_contact_id) 205 ON DELETE CASCADE, 206 CONSTRAINT client_contact_messages_fk_2 207 FOREIGN KEY (message_id) 208 REFERENCES messages(message_id) 209 ON DELETE CASCADE)`, 210 `CREATE TABLE IF NOT EXISTS broadcasts( 211 broadcast_id BINARY(8) NOT NULL, 212 source_service_name VARCHAR(128) NOT NULL, 213 message_type VARCHAR(128) NOT NULL, 214 expiration_time_seconds BIGINT, 215 expiration_time_nanos INT, 216 data_type_url TEXT, 217 data_value MEDIUMBLOB, 218 sent BIGINT UNSIGNED, 219 allocated BIGINT UNSIGNED, 220 message_limit BIGINT UNSIGNED, 221 PRIMARY KEY (broadcast_id))`, 222 `CREATE TABLE IF NOT EXISTS broadcast_labels( 223 broadcast_id BINARY(8) NOT NULL, 224 service_name VARCHAR(128) NOT NULL, 225 label VARCHAR(128) NOT NULL, 226 PRIMARY KEY (broadcast_id, service_name, label), 227 CONSTRAINT broadcast_labels_fk_1 228 FOREIGN KEY (broadcast_id) 229 REFERENCES broadcasts(broadcast_id) 230 ON DELETE CASCADE)`, 231 `CREATE TABLE IF NOT EXISTS broadcast_allocations( 232 broadcast_id BINARY(8) NOT NULL, 233 allocation_id BINARY(8) NOT NULL, 234 sent BIGINT UNSIGNED, 235 message_limit BIGINT UNSIGNED, 236 expiration_time_seconds BIGINT, 237 expiration_time_nanos INT, 238 PRIMARY KEY (broadcast_id, allocation_id), 239 CONSTRAINT broadcast_allocations_fk_1 240 FOREIGN KEY (broadcast_id) 241 REFERENCES broadcasts(broadcast_id) 242 ON DELETE CASCADE)`, 243 `CREATE TABLE IF NOT EXISTS broadcast_sent( 244 broadcast_id BINARY(8) NOT NULL, 245 client_id BINARY(8) NOT NULL, 246 PRIMARY KEY (client_id, broadcast_id), 247 CONSTRAINT broadcast_sent_fk_1 248 FOREIGN KEY (broadcast_id) 249 REFERENCES broadcasts(broadcast_id) 250 ON DELETE CASCADE, 251 CONSTRAINT broadcast_sent_fk_2 252 FOREIGN KEY (client_id) 253 REFERENCES clients(client_id) 254 ON DELETE CASCADE)`, 255 `CREATE TABLE IF NOT EXISTS files( 256 service VARCHAR(128) NOT NULL, 257 name VARCHAR(128) NOT NULL, 258 modified_time_nanos BIGINT NOT NULL, 259 data MEDIUMBLOB, 260 PRIMARY KEY (service, name)) 261 `, 262 } { 263 if _, err := db.Exec(s); err != nil { 264 log.Errorf("Error [%v] creating table: \n%v", err, s) 265 return err 266 } 267 } 268 269 return nil 270 }