Commit f80241b8 authored by Kirill Smelkov's avatar Kirill Smelkov

.

parent d4509066
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
// Package lonet provides TCP network simulated on top of localhost TCP loopback. // Package lonet provides TCP network simulated on top of localhost TCP loopback.
// //
// For testing distributed systems it is sometimes handy to imitate network of // For testing distributed systems it is sometimes handy to imitate network of
// several TCP hosts. It is also handy that port allocated on Dial/Listen/Accept on // several TCP hosts. It is also handy that ports allocated on Dial/Listen/Accept on
// that hosts be predictable - that would help tests to verify network events // that hosts be predictable - that would help tests to verify network events
// against expected sequence. When whole system could be imitated in 1 OS-level // against expected sequence. When whole system could be imitated in 1 OS-level
// process, package lab.nexedi.com/kirr/go123/xnet/pipenet serves the task via // process, package lab.nexedi.com/kirr/go123/xnet/pipenet serves the task via
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
// //
// Similarly to pipenet addresses on lonet are host:port pairs and several // Similarly to pipenet addresses on lonet are host:port pairs and several
// hosts could be created with different names. A host is xnet.Networker and // hosts could be created with different names. A host is xnet.Networker and
// so can be worked with similarly to regular TCP network with // so can be worked with similarly to regular TCP network access-point with
// Dial/Listen/Accept. Host's ports allocation is predictable: ports of a host // Dial/Listen/Accept. Host's ports allocation is predictable: ports of a host
// are contiguous integer sequence starting from 1 that are all initially free, // are contiguous integer sequence starting from 1 that are all initially free,
// and whenever autobind is requested the first free port of the host will be // and whenever autobind is requested the first free port of the host will be
......
...@@ -23,9 +23,12 @@ package lonet ...@@ -23,9 +23,12 @@ package lonet
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"crawshaw.io/sqlite" "crawshaw.io/sqlite"
"crawshaw.io/sqlite/sqliteutil" "crawshaw.io/sqlite/sqliteutil"
"lab.nexedi.com/kirr/go123/xerr"
) )
// registry schema // registry schema
...@@ -41,6 +44,8 @@ import ( ...@@ -41,6 +44,8 @@ import (
// "schemaver" str(int) - version of schema. // "schemaver" str(int) - version of schema.
// "network" text - name of lonet network this registry serves. // "network" text - name of lonet network this registry serves.
const schemaVer = "lonet.1"
type sqliteRegistry struct { type sqliteRegistry struct {
dbpool *sqlite.Pool dbpool *sqlite.Pool
...@@ -48,8 +53,9 @@ type sqliteRegistry struct { ...@@ -48,8 +53,9 @@ type sqliteRegistry struct {
} }
// openRegistrySqlite opens SQLite registry located at dburi. // openRegistrySqlite opens SQLite registry located at dburi.
// XXX network name? //
func openRegistrySQLite(ctx context.Context, dburi string) (_ *sqliteRegistry, err error) { // the registry is setup/verified to be serving specified lonet network.
func openRegistrySQLite(ctx context.Context, dburi, network string) (_ *sqliteRegistry, err error) {
r := &sqliteRegistry{uri: dburi} r := &sqliteRegistry{uri: dburi}
defer r.regerr(&err, "open") defer r.regerr(&err, "open")
...@@ -60,7 +66,7 @@ func openRegistrySQLite(ctx context.Context, dburi string) (_ *sqliteRegistry, e ...@@ -60,7 +66,7 @@ func openRegistrySQLite(ctx context.Context, dburi string) (_ *sqliteRegistry, e
r.dbpool = dbpool r.dbpool = dbpool
err = r.setup(ctx) err = r.setup(ctx, network)
if err != nil { if err != nil {
r.Close() r.Close()
return nil, err return nil, err
...@@ -90,8 +96,8 @@ func (r *sqliteRegistry) withConn(ctx context.Context, f func(*sqlite.Conn) erro ...@@ -90,8 +96,8 @@ func (r *sqliteRegistry) withConn(ctx context.Context, f func(*sqlite.Conn) erro
return f(conn) return f(conn)
} }
var errNoRows = errors.New("query1: empty result") var errNoRows = errors.New("query: empty result")
var errManyRows = errors.New("query1: multiple results") var errManyRows = errors.New("query: multiple results")
// query1 is like sqliteutil.Exec but checks that exactly 1 row is returned. // query1 is like sqliteutil.Exec but checks that exactly 1 row is returned.
// //
...@@ -116,9 +122,9 @@ func query1(conn *sqlite.Conn, query string, resultf func(stmt *sqlite.Stmt), ar ...@@ -116,9 +122,9 @@ func query1(conn *sqlite.Conn, query string, resultf func(stmt *sqlite.Stmt), ar
return nil return nil
} }
func (r *sqliteRegistry) setup(ctx context.Context) (err error) { func (r *sqliteRegistry) setup(ctx context.Context, network string) error {
return r.withConn(ctx, func(conn *sqlite.Conn) error { return r.withConn(ctx, func(conn *sqlite.Conn) (err error) {
err := sqliteutil.ExecScript(conn, ` err = sqliteutil.ExecScript(conn, `
CREATE TABLE IF NOT EXISTS hosts ( CREATE TABLE IF NOT EXISTS hosts (
hostname TEXT NON NULL PRIMARY KEY, hostname TEXT NON NULL PRIMARY KEY,
osladdr TEXT NON NULL osladdr TEXT NON NULL
...@@ -133,13 +139,79 @@ func (r *sqliteRegistry) setup(ctx context.Context) (err error) { ...@@ -133,13 +139,79 @@ func (r *sqliteRegistry) setup(ctx context.Context) (err error) {
return err return err
} }
// XXX check schemaver // do check/init under transaction
// XXX check network name defer sqliteutil.Save(conn)(&err)
// XXX vvv review error handling
// check/init schema version
ver, err := r.config(conn, "schemaver")
if err != nil {
return err
}
if ver == "" {
ver = schemaVer
err = r.setConfig(conn, "schemaver", ver)
if err != nil {
return err
}
}
if ver != schemaVer {
return fmt.Errorf("schema version mismatch: want %q; have %q", schemaVer, ver)
}
// check/init network name
dbnetwork, err := r.config(conn, "network")
if err != nil {
return err
}
if dbnetwork == "" {
dbnetwork = network
err = r.setConfig(conn, "network", dbnetwork)
if err != nil {
return err
}
}
if dbnetwork != network {
return fmt.Errorf("network name mismatch: want %q; have %q", network, dbnetwork)
}
return nil return nil
}) })
} }
// config gets one registry configuration value by name.
//
// if there is no record corresponding to name ("", nil) is returned.
// XXX add ok ret to indicate presence of value?
func (r *sqliteRegistry) config(conn *sqlite.Conn, name string) (value string, err error) {
defer xerr.Contextf(&err, "config: get %q", name)
err = query1(conn, "SELECT value FROM meta WHERE name = ?", func(stmt *sqlite.Stmt) {
value = stmt.ColumnText(0)
}, name)
switch err {
case errNoRows:
return "", nil
case errManyRows:
value = ""
}
return value, err
}
// setConfig sets one registry configuration value by name.
func (r *sqliteRegistry) setConfig(conn *sqlite.Conn, name, value string) (err error) {
defer xerr.Contextf(&err, "config: set %q = %q", name, value)
err = sqliteutil.Exec(conn,
"INSERT OR REPLACE INTO meta (name, value) VALUES (?, ?)", nil,
name, value)
return err
}
func (r *sqliteRegistry) Announce(ctx context.Context, hostname, osladdr string) (err error) { func (r *sqliteRegistry) Announce(ctx context.Context, hostname, osladdr string) (err error) {
defer r.regerr(&err, "announce", hostname, osladdr) defer r.regerr(&err, "announce", hostname, osladdr)
...@@ -149,7 +221,7 @@ func (r *sqliteRegistry) Announce(ctx context.Context, hostname, osladdr string) ...@@ -149,7 +221,7 @@ func (r *sqliteRegistry) Announce(ctx context.Context, hostname, osladdr string)
hostname, osladdr) hostname, osladdr)
switch sqlite.ErrCode(err) { switch sqlite.ErrCode(err) {
case sqlite.SQLITE_CONSTRAINT_UNIQUE: // XXX test case sqlite.SQLITE_CONSTRAINT_UNIQUE:
err = errHostDup err = errHostDup
} }
......
...@@ -21,6 +21,7 @@ package lonet ...@@ -21,6 +21,7 @@ package lonet
import ( import (
"context" "context"
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
...@@ -39,7 +40,7 @@ func TestRegistrySQLite(t *testing.T) { ...@@ -39,7 +40,7 @@ func TestRegistrySQLite(t *testing.T) {
ctx := context.Background() ctx := context.Background()
r, err := openRegistrySQLite(ctx, dbpath) r, err := openRegistrySQLite(ctx, dbpath, "aaa")
X(err) X(err)
// query checks that result of Query(hostname) is as expected. // query checks that result of Query(hostname) is as expected.
...@@ -112,7 +113,7 @@ func TestRegistrySQLite(t *testing.T) { ...@@ -112,7 +113,7 @@ func TestRegistrySQLite(t *testing.T) {
query(r, "α", "alpha:1234") query(r, "α", "alpha:1234")
query(r, "β", ø) query(r, "β", ø)
r2, err := openRegistrySQLite(ctx, dbpath) r2, err := openRegistrySQLite(ctx, dbpath, "aaa")
// r2.Network() == ... // r2.Network() == ...
query(r2, "α", "alpha:1234") query(r2, "α", "alpha:1234")
query(r2, "β", ø) query(r2, "β", ø)
...@@ -133,4 +134,15 @@ func TestRegistrySQLite(t *testing.T) { ...@@ -133,4 +134,15 @@ func TestRegistrySQLite(t *testing.T) {
X(r2.Close()) X(r2.Close())
query(r2, "α", errRegistryDown) query(r2, "α", errRegistryDown)
// verify network mismatch detection works
r3, err := openRegistrySQLite(ctx, dbpath, "bbb")
if !(r3 == nil && err != nil) {
t.Fatalf("network mismatch: not detected")
}
errWant := fmt.Sprintf(`%s: open []: network name mismatch: want "bbb"; have "aaa"`, dbpath)
if err.Error() != errWant {
t.Fatalf("network mismatch: error:\nhave: %q\nwant: %q", err.Error(), errWant)
}
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment