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  }