Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bab8d1b73c |
No files matched your search
@@ -18,9 +18,9 @@ import (
|
|||||||
// columns for: the newest and the older one.
|
// columns for: the newest and the older one.
|
||||||
const keptRoutesPerRow = 2
|
const keptRoutesPerRow = 2
|
||||||
|
|
||||||
// errUnknownRoutePeer is returned for a kept route whose peer has no number in
|
// errUnknownRoutePeer is returned for a peer that route_peers lacks: on a
|
||||||
// route_peers.
|
// write, the peer of a kept route; on a read, a peer number in a row.
|
||||||
var errUnknownRoutePeer = errors.New("kept route of a peer without a number")
|
var errUnknownRoutePeer = errors.New("peer not in route_peers")
|
||||||
|
|
||||||
// The statements of the routes and route_peers tables (schema.sql).
|
// The statements of the routes and route_peers tables (schema.sql).
|
||||||
const (
|
const (
|
||||||
@@ -190,6 +190,7 @@ func routeRow(
|
|||||||
// tables hold, as startup reads it. It reads the peers first, so that the
|
// tables hold, as startup reads it. It reads the peers first, so that the
|
||||||
// table keeps their numbers, then loads each row into the table as it reads
|
// table keeps their numbers, then loads each row into the table as it reads
|
||||||
// it. The two reads are separate, so nothing may write the tables meanwhile.
|
// it. The two reads are separate, so nothing may write the tables meanwhile.
|
||||||
|
// It fails on a row that names a peer number route_peers lacks.
|
||||||
func (d *Database) ReadRouteTable(ctx context.Context) (*routetable.Table, error) {
|
func (d *Database) ReadRouteTable(ctx context.Context) (*routetable.Table, error) {
|
||||||
peerNumbers, err := d.readRoutePeers(ctx)
|
peerNumbers, err := d.readRoutePeers(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -197,8 +198,11 @@ func (d *Database) ReadRouteTable(ctx context.Context) (*routetable.Table, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
addresses := make(map[uint]string, len(peerNumbers))
|
addresses := make(map[uint]string, len(peerNumbers))
|
||||||
|
numbers := new(bitset.BitSet)
|
||||||
|
|
||||||
for address, number := range peerNumbers {
|
for address, number := range peerNumbers {
|
||||||
addresses[number] = address
|
addresses[number] = address
|
||||||
|
numbers.Set(number)
|
||||||
}
|
}
|
||||||
|
|
||||||
table := routetable.New(routetable.Params{PeerNumbers: peerNumbers})
|
table := routetable.New(routetable.Params{PeerNumbers: peerNumbers})
|
||||||
@@ -210,7 +214,7 @@ func (d *Database) ReadRouteTable(ctx context.Context) (*routetable.Table, error
|
|||||||
defer func() { _ = rows.Close() }()
|
defer func() { _ = rows.Close() }()
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
change, err := scanRouteRow(rows, addresses)
|
change, err := scanRouteRow(rows, addresses, numbers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -266,9 +270,11 @@ type keptRouteColumns struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// scanRouteRow returns the content of the routes row rows is at, with each kept
|
// scanRouteRow returns the content of the routes row rows is at, with each kept
|
||||||
// route's peer named by its address in addresses.
|
// route's peer named by its address in addresses. It fails when the row names a
|
||||||
|
// peer number that addresses lacks. numbers holds the numbers of addresses as a
|
||||||
|
// set, against which a row's whole peer set is checked at once.
|
||||||
func scanRouteRow(
|
func scanRouteRow(
|
||||||
rows *sql.Rows, addresses map[uint]string,
|
rows *sql.Rows, addresses map[uint]string, numbers *bitset.BitSet,
|
||||||
) (routetable.Change, error) {
|
) (routetable.Change, error) {
|
||||||
var (
|
var (
|
||||||
change routetable.Change
|
change routetable.Change
|
||||||
@@ -297,6 +303,13 @@ func scanRouteRow(
|
|||||||
prefix, err)
|
prefix, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !numbers.IsSuperSet(change.Peers) {
|
||||||
|
unknown, _ := change.Peers.Difference(numbers).NextSet(0)
|
||||||
|
|
||||||
|
return change, fmt.Errorf("%w: number %d in the peers of route %s",
|
||||||
|
errUnknownRoutePeer, unknown, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
for _, columns := range kept {
|
for _, columns := range kept {
|
||||||
if !columns.peer.Valid {
|
if !columns.peer.Valid {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -113,10 +113,62 @@ func TestReadRouteTableGivesBackWhatWasWritten(t *testing.T) {
|
|||||||
checkReadTable(t, db, source)
|
checkReadTable(t, db, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReadRouteTableGivesATableThatTakesLaterUpdates(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
db := newRoutesTestDatabase(t)
|
||||||
|
source := routetable.New(routetable.Params{})
|
||||||
|
|
||||||
|
// peerA and peerB carry tablePrefixV4, peerB and peerC tablePrefixV6.
|
||||||
|
announceTo(source, tablePrefixV4, asA, peerA, 10)
|
||||||
|
announceTo(source, tablePrefixV4, asA, peerB, 20)
|
||||||
|
announceTo(source, tablePrefixV6, asA, peerB, 30)
|
||||||
|
announceTo(source, tablePrefixV6, asA, peerC, 40)
|
||||||
|
|
||||||
|
writeTableChanges(t, db, source)
|
||||||
|
read := checkReadTable(t, db, source)
|
||||||
|
|
||||||
|
// The same updates to both tables: a loaded peer withdraws a prefix,
|
||||||
|
// another one goes down, and a new peer comes.
|
||||||
|
for _, table := range []*routetable.Table{source, read} {
|
||||||
|
table.Withdraw(netip.MustParsePrefix(tablePrefixV4), peerA)
|
||||||
|
table.PeerDown(peerB)
|
||||||
|
announceTo(table, tablePrefixShortV4, asB, peerD, 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSameReads(t, read, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadRouteTableFailsOnAPeerWithoutANumber(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
db := newRoutesTestDatabase(t)
|
||||||
|
table := routetable.New(routetable.Params{})
|
||||||
|
|
||||||
|
// Only the routes of peerB and peerC are kept, so only the peer set names
|
||||||
|
// peerA.
|
||||||
|
announceTo(table, tablePrefixV4, asA, peerA, 10)
|
||||||
|
announceTo(table, tablePrefixV4, asA, peerB, 20)
|
||||||
|
announceTo(table, tablePrefixV4, asA, peerC, 30)
|
||||||
|
writeTableChanges(t, db, table)
|
||||||
|
|
||||||
|
_, err := db.db.ExecContext(t.Context(),
|
||||||
|
"DELETE FROM route_peers WHERE address = ?", peerA)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("deleting a route peer: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.ReadRouteTable(t.Context())
|
||||||
|
if !errors.Is(err, errUnknownRoutePeer) {
|
||||||
|
t.Fatalf("ReadRouteTable = %v, want %v", err, errUnknownRoutePeer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// checkReadTable checks that the table ReadRouteTable reads answers every read
|
// checkReadTable checks that the table ReadRouteTable reads answers every read
|
||||||
// as want does, for the prefixes and the origin ASes the tests use, and that
|
// as want does, and that reading it recorded no change. It returns that table.
|
||||||
// reading it recorded no change.
|
func checkReadTable(
|
||||||
func checkReadTable(t *testing.T, db *Database, want *routetable.Table) {
|
t *testing.T, db *Database, want *routetable.Table,
|
||||||
|
) *routetable.Table {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
got, err := db.ReadRouteTable(t.Context())
|
got, err := db.ReadRouteTable(t.Context())
|
||||||
@@ -124,6 +176,22 @@ func checkReadTable(t *testing.T, db *Database, want *routetable.Table) {
|
|||||||
t.Fatalf("ReadRouteTable: %v", err)
|
t.Fatalf("ReadRouteTable: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkSameReads(t, got, want)
|
||||||
|
|
||||||
|
changes, newPeers := got.TakeChanges()
|
||||||
|
if len(changes) != 0 || len(newPeers) != 0 {
|
||||||
|
t.Errorf("the read recorded changes %+v and new peers %v, want none",
|
||||||
|
changes, newPeers)
|
||||||
|
}
|
||||||
|
|
||||||
|
return got
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkSameReads checks that got answers every read as want does, for the
|
||||||
|
// prefixes and the origin ASes the tests use.
|
||||||
|
func checkSameReads(t *testing.T, got, want *routetable.Table) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
if !reflect.DeepEqual(got.Counts(), want.Counts()) {
|
if !reflect.DeepEqual(got.Counts(), want.Counts()) {
|
||||||
t.Errorf("Counts() = %+v, want %+v", got.Counts(), want.Counts())
|
t.Errorf("Counts() = %+v, want %+v", got.Counts(), want.Counts())
|
||||||
}
|
}
|
||||||
@@ -167,12 +235,6 @@ func checkReadTable(t *testing.T, db *Database, want *routetable.Table) {
|
|||||||
got.OriginatedPrefixes(asn), want.OriginatedPrefixes(asn))
|
got.OriginatedPrefixes(asn), want.OriginatedPrefixes(asn))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changes, newPeers := got.TakeChanges()
|
|
||||||
if len(changes) != 0 || len(newPeers) != 0 {
|
|
||||||
t.Errorf("the read recorded changes %+v and new peers %v, want none",
|
|
||||||
changes, newPeers)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sortedSample returns every prefix of the IP version and mask length in
|
// sortedSample returns every prefix of the IP version and mask length in
|
||||||
|
|||||||
@@ -224,9 +224,8 @@ func (t *Table) PeerDown(peer string) {
|
|||||||
// Unlike an update, Load records no change, so TakeChanges does not hand the
|
// Unlike an update, Load records no change, so TakeChanges does not hand the
|
||||||
// pair back to be written again. Loading a pair twice changes nothing.
|
// pair back to be written again. Loading a pair twice changes nothing.
|
||||||
//
|
//
|
||||||
// The peer set of change holds the peers' numbers from Params.PeerNumbers. A
|
// The caller makes sure that every peer change names, in its peer set or in
|
||||||
// kept route whose peer has no number there is left out, because numbering
|
// its routes, has a number in Params.PeerNumbers.
|
||||||
// the peer would record a new peer.
|
|
||||||
func (t *Table) Load(change Change) {
|
func (t *Table) Load(change Change) {
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
defer t.mu.Unlock()
|
defer t.mu.Unlock()
|
||||||
@@ -253,10 +252,7 @@ func (t *Table) Load(change Change) {
|
|||||||
// Oldest first, as they were announced, so that two routes with the same
|
// Oldest first, as they were announced, so that two routes with the same
|
||||||
// time are kept in the order they had.
|
// time are kept in the order they had.
|
||||||
for _, r := range slices.Backward(change.Routes) {
|
for _, r := range slices.Backward(change.Routes) {
|
||||||
peer, known := t.peerNumbers[r.Peer]
|
peer := t.peerNumbers[r.Peer]
|
||||||
if !known {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
t.dropKeptRoute(o, peer)
|
t.dropKeptRoute(o, peer)
|
||||||
t.keepIfNewest(o, route{
|
t.keepIfNewest(o, route{
|
||||||
|
|||||||
@@ -522,31 +522,6 @@ func TestLoad(t *testing.T) {
|
|||||||
checkChanges(t, loaded, []change{}, map[string]uint{})
|
checkChanges(t, loaded, []change{}, map[string]uint{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadLeavesOutTheRouteOfAPeerWithoutANumber(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
prefix := netip.MustParsePrefix(prefixV4)
|
|
||||||
source := routetable.New(routetable.Params{})
|
|
||||||
announceAll(source, prefixV4, asA, 10, peerA, peerB)
|
|
||||||
|
|
||||||
// peerB has no number in the loaded table.
|
|
||||||
changes, _ := source.TakeChanges()
|
|
||||||
loaded := routetable.New(routetable.Params{
|
|
||||||
PeerNumbers: map[string]uint{peerA: 0},
|
|
||||||
})
|
|
||||||
loaded.Load(changes[0])
|
|
||||||
|
|
||||||
want := []routetable.Route{routeOf(peerA, asA, 10)}
|
|
||||||
|
|
||||||
origins := loaded.Origins(prefix)
|
|
||||||
if len(origins) != 1 || !reflect.DeepEqual(origins[0].Routes, want) {
|
|
||||||
t.Errorf("Origins(%s) = %+v, want one origin with routes %+v",
|
|
||||||
prefix, origins, want)
|
|
||||||
}
|
|
||||||
|
|
||||||
checkChanges(t, loaded, []change{}, map[string]uint{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestConcurrentUse has goroutines update one table while others read it. The
|
// TestConcurrentUse has goroutines update one table while others read it. The
|
||||||
// race detector, which make test runs, fails it when a method uses the table
|
// race detector, which make test runs, fails it when a method uses the table
|
||||||
// without its lock.
|
// without its lock.
|
||||||
|
|||||||
Reference in new issue
Block a user