Remove a peer's live routes when its session goes down (closes #56)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
RIS sends no withdrawals for the routes of a peer whose BGP session goes down, so those routes stayed in the live tables for good. The streamer now hands STATE messages to the handlers that want them. When the prefix handler gets one whose state is down, it drops the peer's updates still in its batch and notes the peer; the next flush deletes all of the peer's live routes, IPv4 and IPv6, before writing the batch, so updates the peer sends after the down are kept. The new store method keeps the in-memory route counts and prefix distribution exact, and an index on peer_ip lets it find the peer's routes without reading the whole table. Model: opus-5-5
This commit is contained in:
@@ -108,6 +108,52 @@ func (h updateHandler) HandleMessage(*ristypes.RISMessage) {
|
||||
}
|
||||
}
|
||||
|
||||
// stateDownLine is the STATE message RIS Live sends when the collector's BGP
|
||||
// session with the peer stateDownPeer goes down.
|
||||
const (
|
||||
stateDownPeer = "192.0.2.1"
|
||||
stateDownLine = `{"type":"ris_message","data":{"type":"STATE","peer":"` + stateDownPeer + `",` +
|
||||
`"peer_asn":"64496","timestamp":1700000000,"state":"down"}}` + "\n"
|
||||
)
|
||||
|
||||
// stateHandler wants STATE messages only. Tests read what it is handed from
|
||||
// its queue, so HandleMessage does nothing.
|
||||
type stateHandler struct{}
|
||||
|
||||
func (stateHandler) WantsMessage(messageType string) bool {
|
||||
return messageType == ristypes.MessageTypeState
|
||||
}
|
||||
func (stateHandler) QueueCapacity() int { return 10 }
|
||||
func (stateHandler) HandleMessage(*ristypes.RISMessage) {}
|
||||
|
||||
// TestStreamHandsStateMessagesToHandlersThatWantThem has the server send one
|
||||
// STATE message whose state is down. The streamer must queue it, with its
|
||||
// peer and state, for a handler that wants STATE messages.
|
||||
func TestStreamHandsStateMessagesToHandlersThatWantThem(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = io.WriteString(w, stateDownLine)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
s := New(logger.New(), metrics.New())
|
||||
s.url = srv.URL
|
||||
s.RegisterHandler(stateHandler{})
|
||||
|
||||
if err := s.stream(context.Background()); !errors.Is(err, errStreamEnded) {
|
||||
t.Fatalf("stream returned %v, want %v", err, errStreamEnded)
|
||||
}
|
||||
|
||||
select {
|
||||
case msg := <-s.handlers[0].queue:
|
||||
if msg.Type != ristypes.MessageTypeState || msg.Peer != stateDownPeer || msg.State != ristypes.StateDown {
|
||||
t.Errorf("queued message has type %q, peer %q, state %q; want %q, %q, %q",
|
||||
msg.Type, msg.Peer, msg.State, ristypes.MessageTypeState, stateDownPeer, ristypes.StateDown)
|
||||
}
|
||||
default:
|
||||
t.Fatal("the STATE message was not queued for the handler that wants it")
|
||||
}
|
||||
}
|
||||
|
||||
// TestStopBeforeMessageReachesQueues stops the streamer after the read loop
|
||||
// has checked for cancellation but before it hands the message to the handler
|
||||
// queues. That is the gap a stop from another goroutine can land in, and it
|
||||
|
||||
Reference in New Issue
Block a user