check / check (push) Failing after 2m14s
Hold the 100,000-row cap after a failed write's rows are put back, dropping and counting the oldest rows past it. Stop counts the unwritten origin changes. The prefix handler's per-write origin change limit is a field, so a test checks that one write leaves the rest. The server and README now say where origin changes come from. README, TODO and schema say 72 hours or 500 rows, whichever keeps more. The prefix page and TODO list the origin change columns and say what the peer count counts. The page takes its 5,000-row note from the constant. Model: deepseek-v4-pro
279 lines
7.8 KiB
Go
279 lines
7.8 KiB
Go
package routetable_test
|
|
|
|
import (
|
|
"math"
|
|
"net/netip"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/bits-and-blooms/bitset"
|
|
|
|
"git.eeqj.de/sneak/routewatch/internal/routetable"
|
|
)
|
|
|
|
// originChange returns the origin change the table records for prefix and asn
|
|
// at the clock's time, announced or withdrawn, with peerCount peers.
|
|
func originChange(
|
|
prefix netip.Prefix, asn int, announced bool, peerCount int, at time.Time,
|
|
) routetable.OriginChange {
|
|
return routetable.OriginChange{
|
|
Prefix: prefix,
|
|
OriginASN: asn,
|
|
Announced: announced,
|
|
PeerCount: peerCount,
|
|
Time: at,
|
|
}
|
|
}
|
|
|
|
// TestOriginChangesRecordAnnouncedAndWithdrawn checks that a prefix and origin
|
|
// AS pair records an announced row when it gains its first peer, nothing for a
|
|
// second peer, and a withdrawn row when it loses its last peer.
|
|
func TestOriginChangesRecordAnnouncedAndWithdrawn(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
now := at(0)
|
|
table := routetable.New(routetable.Params{
|
|
Now: func() time.Time { return now },
|
|
})
|
|
prefix := netip.MustParsePrefix(prefixV4)
|
|
|
|
table.Announce(prefix, asA, routeOf(peerA, asA, 10))
|
|
table.Announce(prefix, asA, routeOf(peerB, asA, 20))
|
|
|
|
checkOriginChanges(t, table, []routetable.OriginChange{
|
|
originChange(prefix, asA, true, 1, now),
|
|
})
|
|
|
|
table.Withdraw(prefix, peerA)
|
|
table.Withdraw(prefix, peerB)
|
|
|
|
checkOriginChanges(t, table, []routetable.OriginChange{
|
|
originChange(prefix, asA, false, 0, now),
|
|
})
|
|
}
|
|
|
|
// TestOriginChangesRecordAPeerMovingOrigin checks that a peer moving from one
|
|
// origin AS to another records a withdrawn row for the old one and an
|
|
// announced row for the new one.
|
|
func TestOriginChangesRecordAPeerMovingOrigin(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
now := at(0)
|
|
table := routetable.New(routetable.Params{
|
|
Now: func() time.Time { return now },
|
|
})
|
|
prefix := netip.MustParsePrefix(prefixV4)
|
|
|
|
table.Announce(prefix, asA, routeOf(peerA, asA, 10))
|
|
table.Announce(prefix, asB, routeOf(peerA, asB, 20))
|
|
|
|
checkOriginChanges(t, table, []routetable.OriginChange{
|
|
originChange(prefix, asA, true, 1, now),
|
|
originChange(prefix, asA, false, 0, now),
|
|
originChange(prefix, asB, true, 1, now),
|
|
})
|
|
}
|
|
|
|
// TestOriginChangesRecordPeersDown checks that PeersDown records a withdrawn
|
|
// row for each prefix and origin AS pair it empties.
|
|
func TestOriginChangesRecordPeersDown(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
now := at(0)
|
|
table := routetable.New(routetable.Params{
|
|
Now: func() time.Time { return now },
|
|
})
|
|
v4 := netip.MustParsePrefix(prefixV4)
|
|
v6 := netip.MustParsePrefix(prefixV6)
|
|
|
|
table.Announce(v4, asA, routeOf(peerA, asA, 10))
|
|
table.Announce(v6, asA, routeOf(peerA, asA, 10))
|
|
table.Announce(v4, asB, routeOf(peerB, asB, 20))
|
|
|
|
table.PeersDown([]string{peerA})
|
|
|
|
// peerA carried v4 and v6 with asA; both pairs are emptied. peerB still
|
|
// carries v4 with asB, so that pair is not.
|
|
checkOriginChanges(t, table, []routetable.OriginChange{
|
|
originChange(v4, asA, true, 1, now),
|
|
originChange(v6, asA, true, 1, now),
|
|
originChange(v4, asB, true, 2, now),
|
|
originChange(v4, asA, false, 1, now),
|
|
originChange(v6, asA, false, 0, now),
|
|
})
|
|
}
|
|
|
|
// TestOriginChangesLoadRecordsNothing checks that Load records no origin
|
|
// change.
|
|
func TestOriginChangesLoadRecordsNothing(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
table := routetable.New(routetable.Params{
|
|
PeerNumbers: map[string]uint{peerA: 0},
|
|
})
|
|
prefix := netip.MustParsePrefix(prefixV4)
|
|
|
|
peers := new(bitset.BitSet)
|
|
peers.Set(0)
|
|
|
|
table.Load(routetable.Change{
|
|
Prefix: prefix,
|
|
OriginASN: asA,
|
|
Peers: peers,
|
|
Routes: []routetable.Route{routeOf(peerA, asA, 10)},
|
|
})
|
|
|
|
changes, dropped := table.TakeOriginChanges(math.MaxInt)
|
|
if dropped != 0 || len(changes) != 0 {
|
|
t.Errorf("TakeOriginChanges = %+v, %d; want none", changes, dropped)
|
|
}
|
|
}
|
|
|
|
// TestOriginChangesTakeReturnsOldestFirst checks that TakeOriginChanges
|
|
// returns the changes oldest first and at most max.
|
|
func TestOriginChangesTakeReturnsOldestFirst(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
table := routetable.New(routetable.Params{})
|
|
prefix := netip.MustParsePrefix(prefixV4)
|
|
|
|
table.Announce(prefix, asA, routeOf(peerA, asA, 10))
|
|
table.Announce(prefix, asB, routeOf(peerB, asB, 20))
|
|
table.Announce(prefix, asC, routeOf(peerC, asC, 30))
|
|
|
|
first, dropped := table.TakeOriginChanges(2)
|
|
if dropped != 0 {
|
|
t.Errorf("dropped = %d, want 0", dropped)
|
|
}
|
|
|
|
if len(first) != 2 || first[0].OriginASN != asA || first[1].OriginASN != asB {
|
|
t.Errorf("first TakeOriginChanges = %+v, want the first two", first)
|
|
}
|
|
|
|
second, _ := table.TakeOriginChanges(math.MaxInt)
|
|
if len(second) != 1 || second[0].OriginASN != asC {
|
|
t.Errorf("second TakeOriginChanges = %+v, want the third", second)
|
|
}
|
|
}
|
|
|
|
// TestOriginChangesPutBackPutsRowsInFront checks that PutBackOriginChanges
|
|
// puts rows back in front, so the next TakeOriginChanges returns them first.
|
|
func TestOriginChangesPutBackPutsRowsInFront(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
table := routetable.New(routetable.Params{})
|
|
prefix := netip.MustParsePrefix(prefixV4)
|
|
|
|
table.Announce(prefix, asA, routeOf(peerA, asA, 10))
|
|
table.Announce(prefix, asB, routeOf(peerB, asB, 20))
|
|
|
|
taken, _ := table.TakeOriginChanges(math.MaxInt)
|
|
table.Announce(prefix, asC, routeOf(peerC, asC, 30))
|
|
table.PutBackOriginChanges(taken)
|
|
|
|
changes, _ := table.TakeOriginChanges(math.MaxInt)
|
|
if len(changes) != 3 || changes[0].OriginASN != asA ||
|
|
changes[1].OriginASN != asB || changes[2].OriginASN != asC {
|
|
t.Errorf("TakeOriginChanges = %+v, want the put-back rows first", changes)
|
|
}
|
|
}
|
|
|
|
// TestOriginChangesDropPastTheLimit checks that a change past 100,000 waiting
|
|
// rows is dropped and counted.
|
|
func TestOriginChangesDropPastTheLimit(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const limit = 100000
|
|
|
|
table := routetable.New(routetable.Params{})
|
|
addr := netip.MustParseAddr("10.0.0.0")
|
|
|
|
for range limit + 1 {
|
|
prefix := netip.PrefixFrom(addr, 32)
|
|
table.Announce(prefix, asA, routeOf(peerA, asA, 10))
|
|
|
|
addr = addr.Next()
|
|
}
|
|
|
|
changes, dropped := table.TakeOriginChanges(limit)
|
|
if dropped != 1 {
|
|
t.Errorf("dropped = %d, want 1", dropped)
|
|
}
|
|
|
|
if len(changes) != limit {
|
|
t.Errorf("len(changes) = %d, want %d", len(changes), limit)
|
|
}
|
|
}
|
|
|
|
// TestOriginChangesPutBackTrimsToTheLimit checks that putting back what a
|
|
// failed write took, after new changes filled the table again, drops the
|
|
// oldest changes past the cap and counts them, so the cap still holds. The
|
|
// clock advances one second per change, so a change's time says its age.
|
|
func TestOriginChangesPutBackTrimsToTheLimit(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const (
|
|
limit = 100000
|
|
taken = 20000
|
|
added = 20000
|
|
)
|
|
|
|
now := time.Unix(0, 0)
|
|
table := routetable.New(routetable.Params{
|
|
Now: func() time.Time {
|
|
now = now.Add(time.Second)
|
|
|
|
return now
|
|
},
|
|
})
|
|
addr := netip.MustParseAddr("10.0.0.0")
|
|
|
|
announce := func(n int) {
|
|
for range n {
|
|
table.Announce(netip.PrefixFrom(addr, 32), asA, routeOf(peerA, asA, 10))
|
|
addr = addr.Next()
|
|
}
|
|
}
|
|
|
|
announce(limit)
|
|
|
|
takenChanges, _ := table.TakeOriginChanges(taken)
|
|
|
|
announce(added)
|
|
|
|
table.PutBackOriginChanges(takenChanges)
|
|
|
|
changes, dropped := table.TakeOriginChanges(math.MaxInt)
|
|
if dropped != added {
|
|
t.Errorf("dropped = %d, want %d", dropped, added)
|
|
}
|
|
|
|
if len(changes) != limit {
|
|
t.Errorf("len(changes) = %d, want %d", len(changes), limit)
|
|
}
|
|
|
|
// The put-back rows are the oldest, so they are the ones dropped. The
|
|
// first remaining change is the one that followed them.
|
|
if want := time.Unix(int64(added+1), 0); !changes[0].Time.Equal(want) {
|
|
t.Errorf("first change's time = %v, want %v", changes[0].Time, want)
|
|
}
|
|
}
|
|
|
|
// checkOriginChanges checks that TakeOriginChanges, taking every change,
|
|
// returns want.
|
|
func checkOriginChanges(
|
|
t *testing.T, table *routetable.Table, want []routetable.OriginChange,
|
|
) {
|
|
t.Helper()
|
|
|
|
changes, dropped := table.TakeOriginChanges(math.MaxInt)
|
|
if dropped != 0 {
|
|
t.Errorf("dropped = %d, want 0", dropped)
|
|
}
|
|
|
|
if !reflect.DeepEqual(changes, want) {
|
|
t.Errorf("TakeOriginChanges = %+v, want %+v", changes, want)
|
|
}
|
|
}
|