github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/server/sqlite/sqlite.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 sqlite implements the fleetspeak datastore interface using an sqlite 16 // database. It is meant for testing for small single-server deployments. In 17 // particular, having multiple servers using the same sqlite datastore is not 18 // supported. 19 package sqlite 20 21 import ( 22 "database/sql" 23 "sync" 24 25 log "github.com/golang/glog" 26 27 // We access the driver through sql.Open, but need to bring in the 28 // dependency. 29 _ "github.com/mattn/go-sqlite3" 30 ) 31 32 // Datastore wraps an sqlite database and implements db.Store. 33 type Datastore struct { 34 db *sql.DB 35 36 // We serialize access to the database to eliminate database locked 37 // errors when using this in a multithreaded context. 38 l sync.Mutex 39 40 looper *messageLooper 41 } 42 43 // MakeDatastore opens the given sqlite database file and creates any missing 44 // tables. 45 func MakeDatastore(fileName string) (*Datastore, error) { 46 log.Infof("Opening sql database: %v", fileName) 47 db, err := sql.Open("sqlite3", fileName) 48 if err != nil { 49 return nil, err 50 } 51 err = initDB(db) 52 if err != nil { 53 return nil, err 54 } 55 err = initSchema(db) 56 if err != nil { 57 return nil, err 58 } 59 return &Datastore{db: db}, nil 60 } 61 62 // Close closes the underlying database resources. 63 func (d *Datastore) Close() error { 64 return d.db.Close() 65 } 66 67 // runInTx runs f, passing it a transaction. The transaction will be committed 68 // if f returns an error, otherwise rolled back. 69 func (d *Datastore) runInTx(f func(*sql.Tx) error) error { 70 tx, err := d.db.Begin() 71 if err != nil { 72 return err 73 } 74 err = f(tx) 75 if err != nil { 76 tx.Rollback() 77 return err 78 } 79 return tx.Commit() 80 } 81 82 // IsNotFound implements db.Store. 83 func (d *Datastore) IsNotFound(err error) bool { 84 return err == sql.ErrNoRows 85 } 86 87 func initDB(db *sql.DB) error { 88 if _, err := db.Exec("PRAGMA foreign_keys = ON"); err != nil { 89 return err 90 } 91 return nil 92 } 93 94 func initSchema(db *sql.DB) error { 95 for _, s := range []string{ 96 `PRAGMA journal_mode = WAL`, 97 `CREATE TABLE IF NOT EXISTS clients( 98 client_id TEXT(16) PRIMARY KEY, 99 client_key BLOB, 100 blacklisted BOOLEAN NOT NULL, 101 last_contact_time INT8 NOT NULL, 102 last_contact_address TEXT(64), 103 last_contact_streaming_to TEXT(128), 104 last_clock_seconds INT8, 105 last_clock_nanos INT4)`, 106 `CREATE TABLE IF NOT EXISTS client_labels( 107 client_id TEXT(16) NOT NULL, 108 service_name TEXT(128) NOT NULL, 109 label TEXT(128) NOT NULL, 110 PRIMARY KEY (client_id, service_name, label), 111 FOREIGN KEY (client_id) REFERENCES clients(client_id)) 112 WITHOUT ROWID`, 113 `CREATE TABLE IF NOT EXISTS client_contacts( 114 client_contact_id INTEGER NOT NULL, 115 client_id TEXT(16) NOT NULL, 116 time INT8 NOT NULL, 117 sent_nonce TEXT(16) NOT NULL, 118 received_nonce TEXT(16) NOT NULL, 119 address TEXT(64), 120 -- We want auto-increment functionality, and get it because an INTEGER primary key 121 -- becomes the ROWID. 122 PRIMARY KEY (client_contact_id), 123 FOREIGN KEY (client_id) REFERENCES clients(client_id))`, 124 `CREATE TABLE IF NOT EXISTS client_resource_usage_records( 125 client_id TEXT(16) NOT NULL, 126 scope TEXT(128) NOT NULL, 127 pid INT8, 128 process_start_time INT8, 129 client_timestamp INT8, 130 server_timestamp INT8, 131 process_terminated BOOLEAN NOT NULL, 132 mean_user_cpu_rate REAL, 133 max_user_cpu_rate REAL, 134 mean_system_cpu_rate REAL, 135 max_system_cpu_rate REAL, 136 mean_resident_memory_mib INT4, 137 max_resident_memory_mib INT4, 138 mean_num_fds INT4, 139 max_num_fds INT4, 140 FOREIGN KEY (client_id) REFERENCES clients(client_id))`, 141 `CREATE TABLE IF NOT EXISTS messages( 142 message_id TEXT(64) NOT NULL, 143 source_client_id TEXT(16) NOT NULL, 144 source_service_name TEXT(128) NOT NULL, 145 source_message_id TEXT(32) NOT NULL, 146 destination_client_id TEXT(16), 147 destination_service_name TEXT(128) NOT NULL, 148 message_type TEXT(128), 149 creation_time_seconds INT8 NOT NULL, 150 creation_time_nanos INT4 NOT NULL, 151 processed_time_seconds INT8, 152 processed_time_nanos INT4, 153 validation_info BLOB, 154 failed INT1, 155 failed_reason TEXT, 156 annotations BLOB, 157 PRIMARY KEY (message_id))`, 158 `CREATE TABLE IF NOT EXISTS pending_messages( 159 message_id TEXT(64) NOT NULL, 160 retry_count INT4 NOT NULL, 161 scheduled_time INT8 NOT NULL, 162 data_type_url TEXT, 163 data_value BLOB, 164 PRIMARY KEY (message_id), 165 FOREIGN KEY (message_id) REFERENCES messages(message_id))`, 166 `CREATE TABLE IF NOT EXISTS client_contact_messages( 167 client_contact_id INTEGER NOT NULL, 168 message_id TEXT(64) NOT NULL, 169 PRIMARY KEY (client_contact_id, message_id), 170 FOREIGN KEY (client_contact_id) REFERENCES client_contacts(client_contact_id), 171 FOREIGN KEY (message_id) REFERENCES messages(message_id)) 172 WITHOUT ROWID`, 173 `CREATE TABLE IF NOT EXISTS broadcasts( 174 broadcast_id TEXT(32) NOT NULL, 175 source_service_name TEXT(128) NOT NULL, 176 message_type TEXT(128) NOT NULL, 177 expiration_time_seconds INT8, 178 expiration_time_nanos INT4, 179 data_type_url TEXT, 180 data_value BLOB, 181 sent UINT8, 182 allocated UINT8, 183 message_limit UINT8, 184 PRIMARY KEY (broadcast_id))`, 185 `CREATE TABLE IF NOT EXISTS broadcast_labels( 186 broadcast_id TEXT(32) NOT NULL, 187 service_name TEXT(128) NOT NULL, 188 label TEXT(128) NOT NULL, 189 PRIMARY KEY (broadcast_id, service_name, label), 190 FOREIGN KEY (broadcast_id) REFERENCES broadcasts(broadcast_id)) 191 WITHOUT ROWID`, 192 `CREATE TABLE IF NOT EXISTS broadcast_allocations( 193 broadcast_id TEXT(32) NOT NULL, 194 allocation_id TEXT(16) NOT NULL, 195 sent UINT8, 196 message_limit UINT8, 197 expiration_time_seconds INT8, 198 expiration_time_nanos INT4, 199 PRIMARY KEY (broadcast_id, allocation_id), 200 FOREIGN KEY (broadcast_id) REFERENCES broadcasts(broadcast_id)) 201 WITHOUT ROWID`, 202 `CREATE TABLE IF NOT EXISTS broadcast_sent( 203 broadcast_id TEXT(32) NOT NULL, 204 client_id TEXT(16) NOT NULL, 205 PRIMARY KEY (client_id, broadcast_id), 206 FOREIGN KEY (broadcast_id) REFERENCES broadcasts(broadcast_id), 207 FOREIGN KEY (client_id) REFERENCES clients(client_id)) 208 WITHOUT ROWID`, 209 `CREATE TABLE IF NOT EXISTS files( 210 service TEXT(128) NOT NULL, 211 name TEST(256) NOT NULL, 212 modified_time_nanos INT8 NOT NULL, 213 data BLOB, 214 PRIMARY KEY (service, name)) 215 `, 216 } { 217 if _, err := db.Exec(s); err != nil { 218 log.Errorf("Error [%v] creating table: \n%v", err, s) 219 return err 220 } 221 } 222 223 return nil 224 }