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 }