lab.nexedi.com/kirr/go123@v0.0.0-20240207185015-8299741fa871/xnet/lonet/registry_sqlite.go (about)

     1  // Copyright (C) 2018-2019  Nexedi SA and Contributors.
     2  //                          Kirill Smelkov <kirr@nexedi.com>
     3  //
     4  // This program is free software: you can Use, Study, Modify and Redistribute
     5  // it under the terms of the GNU General Public License version 3, or (at your
     6  // option) any later version, as published by the Free Software Foundation.
     7  //
     8  // You can also Link and Combine this program with other software covered by
     9  // the terms of any of the Free Software licenses or any of the Open Source
    10  // Initiative approved licenses and Convey the resulting work. Corresponding
    11  // source of such a combination shall include the source code for all other
    12  // software used.
    13  //
    14  // This program is distributed WITHOUT ANY WARRANTY; without even the implied
    15  // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    16  //
    17  // See COPYING file for full licensing terms.
    18  // See https://www.nexedi.com/licensing for rationale and options.
    19  
    20  package lonet
    21  // registry implemented as shared SQLite file.
    22  
    23  import (
    24  	"context"
    25  	"errors"
    26  	"fmt"
    27  
    28  	"crawshaw.io/sqlite"
    29  	"crawshaw.io/sqlite/sqlitex"
    30  
    31  	"lab.nexedi.com/kirr/go123/xerr"
    32  	"lab.nexedi.com/kirr/go123/xnet/virtnet"
    33  )
    34  
    35  // registry schema	(keep in sync wrt .setup())
    36  //
    37  // hosts:
    38  //	hostname	text !null PK
    39  //	osladdr		text !null
    40  //
    41  // meta:
    42  //	name		text !null PK
    43  //	value		text !null
    44  //
    45  // "schemaver"  text	- version of db schema.
    46  // "network"    text	- name of lonet network this registry serves.
    47  
    48  const schemaVer = "lonet.1"
    49  
    50  type sqliteRegistry struct {
    51  	dbpool *sqlitex.Pool
    52  
    53  	uri    string	// URI db was originally opened with
    54  }
    55  
    56  // openRegistrySQLite opens SQLite registry located at dburi.
    57  //
    58  // the registry is setup/verified to be serving specified lonet network.
    59  func openRegistrySQLite(ctx context.Context, dburi, network string) (_ *sqliteRegistry, err error) {
    60  	r := &sqliteRegistry{uri: dburi}
    61  	defer r.regerr(&err, "open")
    62  
    63  	dbpool, err := sqlitex.Open(dburi, 0, /* poolSize= */16)	// XXX pool size ok?
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	r.dbpool = dbpool
    69  
    70  	// initialize/check db
    71  	err = r.setup(ctx, network)
    72  	if err != nil {
    73  		r.Close()
    74  		return nil, err
    75  	}
    76  
    77  	return r, nil
    78  }
    79  
    80  // Close implements registry.
    81  func (r *sqliteRegistry) Close() (err error) {
    82  	defer r.regerr(&err, "close")
    83  	return r.dbpool.Close()
    84  }
    85  
    86  // withConn runs f on a dbpool connection.
    87  //
    88  // connection is first allocated from dbpool and put back after call to f.
    89  func (r *sqliteRegistry) withConn(ctx context.Context, f func(*sqlite.Conn) error) error {
    90  	conn := r.dbpool.Get(ctx)
    91  	if conn == nil {
    92  		// either ctx cancel or dbpool close
    93  		if ctx.Err() != nil {
    94  			return ctx.Err()
    95  		}
    96  		return virtnet.ErrRegistryDown // db closed
    97  	}
    98  	defer r.dbpool.Put(conn)
    99  	return f(conn)
   100  }
   101  
   102  var errNoRows   = errors.New("query: empty result")
   103  var errManyRows = errors.New("query: multiple results")
   104  
   105  // query1 is like sqlitex.Exec but checks that exactly 1 row is returned.
   106  //
   107  // if query results in no rows - errNoRows is returned.
   108  // if query results in more than 1 row - errManyRows is returned.
   109  func query1(conn *sqlite.Conn, query string, resultf func(stmt *sqlite.Stmt), argv ...interface{}) error {
   110  	nrow := 0
   111  	err := sqlitex.Exec(conn, query, func(stmt *sqlite.Stmt) error {
   112  		if nrow == 1 {
   113  			return errManyRows
   114  		}
   115  		nrow++
   116  		resultf(stmt)
   117  		return nil
   118  	}, argv...)
   119  	if err != nil {
   120  		return err
   121  	}
   122  	if nrow == 0 {
   123  		return errNoRows
   124  	}
   125  	return nil
   126  }
   127  
   128  // setup initializes/checks registry database to be of expected schema and configuration.
   129  func (r *sqliteRegistry) setup(ctx context.Context, network string) (err error) {
   130  	defer xerr.Contextf(&err, "setup %q", network)
   131  
   132  	return r.withConn(ctx, func(conn *sqlite.Conn) (err error) {
   133  		// NOTE: keep in sync wrt top-level text.
   134  		err = sqlitex.ExecScript(conn, `
   135  			CREATE TABLE IF NOT EXISTS hosts (
   136  				hostname	TEXT NON NULL PRIMARY KEY,
   137  				osladdr		TEXT NON NULL
   138  			);
   139  
   140  			CREATE TABLE IF NOT EXISTS meta (
   141  				name		TEXT NON NULL PRIMARY KEY,
   142  				value		TEXT NON NULL
   143  			);
   144  		`)
   145  		if err != nil {
   146  			return err
   147  		}
   148  
   149  		// do whole checks/init under transaction, so that there is
   150  		// e.g. no race wrt another process setting config.
   151  		defer sqlitex.Save(conn)(&err)
   152  
   153  		// check/init schema version
   154  		ver, err := r.config(conn, "schemaver")
   155  		if err != nil {
   156  			return err
   157  		}
   158  		if ver == "" {
   159  			ver = schemaVer
   160  			err = r.setConfig(conn, "schemaver", ver)
   161  			if err != nil {
   162  				return err
   163  			}
   164  		}
   165  		if ver != schemaVer {
   166  			return fmt.Errorf("schema version mismatch: want %q; have %q", schemaVer, ver)
   167  		}
   168  
   169  		// check/init network name
   170  		dbnetwork, err := r.config(conn, "network")
   171  		if err != nil {
   172  			return err
   173  		}
   174  		if dbnetwork == "" {
   175  			dbnetwork = network
   176  			err = r.setConfig(conn, "network", dbnetwork)
   177  			if err != nil {
   178  				return err
   179  			}
   180  		}
   181  		if dbnetwork != network {
   182  			return fmt.Errorf("network name mismatch: want %q; have %q", network, dbnetwork)
   183  		}
   184  
   185  
   186  		return nil
   187  	})
   188  }
   189  
   190  // config gets one registry configuration value by name.
   191  //
   192  // if there is no record corresponding to name - ("", nil) is returned.
   193  // XXX add ok ret to indicate presence of value?
   194  func (r *sqliteRegistry) config(conn *sqlite.Conn, name string) (value string, err error) {
   195  	defer xerr.Contextf(&err, "config: get %q", name)
   196  
   197  	err = query1(conn, "SELECT value FROM meta WHERE name = ?", func(stmt *sqlite.Stmt) {
   198  		value = stmt.ColumnText(0)
   199  	}, name)
   200  
   201  	switch err {
   202  	case errNoRows:
   203  		return "", nil
   204  	case errManyRows:
   205  		value = ""
   206  	}
   207  
   208  	return value, err
   209  }
   210  
   211  // setConfig sets one registry configuration value by name.
   212  func (r *sqliteRegistry) setConfig(conn *sqlite.Conn, name, value string) (err error) {
   213  	defer xerr.Contextf(&err, "config: set %q = %q", name, value)
   214  
   215  	err = sqlitex.Exec(conn,
   216  		"INSERT OR REPLACE INTO meta (name, value) VALUES (?, ?)", nil,
   217  		name, value)
   218  	return err
   219  }
   220  
   221  // Announce implements registry.
   222  func (r *sqliteRegistry) Announce(ctx context.Context, hostname, osladdr string) (err error) {
   223  	defer r.regerr(&err, "announce", hostname, osladdr)
   224  
   225  	return r.withConn(ctx, func(conn *sqlite.Conn) error {
   226  		err := sqlitex.Exec(conn,
   227  			"INSERT INTO hosts (hostname, osladdr) VALUES (?, ?)", nil,
   228  			hostname, osladdr)
   229  
   230  		switch sqlite.ErrCode(err) {
   231  		case sqlite.SQLITE_CONSTRAINT_UNIQUE:
   232  			err = virtnet.ErrHostDup
   233  		}
   234  
   235  		return err
   236  	})
   237  }
   238  
   239  var errRegDup = errors.New("registry broken: duplicate host entries")
   240  
   241  // Query implements registry.
   242  func (r *sqliteRegistry) Query(ctx context.Context, hostname string) (osladdr string, err error) {
   243  	defer r.regerr(&err, "query", hostname)
   244  
   245  	err = r.withConn(ctx, func(conn *sqlite.Conn) error {
   246  		err := query1(conn, "SELECT osladdr FROM hosts WHERE hostname = ?",
   247  			func (stmt *sqlite.Stmt) {
   248  				osladdr = stmt.ColumnText(0)
   249  			}, hostname)
   250  
   251  		switch err {
   252  		case errNoRows:
   253  			return virtnet.ErrNoHost
   254  
   255  		case errManyRows:
   256  			// hostname is PK - we should not get several results
   257  			osladdr = ""
   258  			return errRegDup
   259  		}
   260  
   261  		return err
   262  	})
   263  
   264  	return osladdr, err
   265  }
   266  
   267  // regerr is syntactic sugar to wrap !nil *errp into RegistryError.
   268  //
   269  // intended too be used like
   270  //
   271  //	defer r.regerr(&err, "operation", arg1, arg2, ...)
   272  func (r *sqliteRegistry) regerr(errp *error, op string, args ...interface{}) {
   273  	if *errp == nil {
   274  		return
   275  	}
   276  
   277  	*errp = &virtnet.RegistryError{
   278  		Registry: r.uri,
   279  		Op:       op,
   280  		Args:     args,
   281  		Err:      *errp,
   282  	}
   283  }