Topology gossip can be received on a connection before that connection has been marked as 'established'. If we do not include unestablished connections in topology gossip, then parts of the topology can be removed by gc, since they are deemed unreachable, which can cause subsequent incremental topology gossips to be rejected due to them containing references to peers that have been removed by the gc. Including unestablished connections in topology gossip requires: - removing the check for 'established' connections in the encoding of peers for topology gossip, - adding the 'established' flag to the internal representation of remote connections, - adding the 'established' flag to to encoding of connections in topology gossip, - treating the *addition* of a connection as an update to the local peer. The latter entails - bumping the peer version on connection addition (as well as on marking 'established' as previously), - bumping the peer version *unconditionally* on connection deletion (rather than only when the connection was 'established', as previously), - moving the initial sending of complete topology on the connection, and the broadcasting of a topology update containing the two peers at the ends of the connection, from the connection 'established' logic to the connection addition logic, - broadcasting just an topology update for the local peer when a connection gets marked as 'established', - broadcasting a topology update for the local peer *unconditionally* when a connection is removed (rather than only doing this when the connection was 'established', as previously).
147 lines
5.0 KiB
Go
147 lines
5.0 KiB
Go
package router
|
|
|
|
import (
|
|
wt "github.com/zettio/weave/testing"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TODO test gossip unicast and broadcast; atm we only test topology
|
|
// gossip, which does not employ unicast or broadcast.
|
|
|
|
type mockChannelConnection struct {
|
|
RemoteConnection
|
|
dest *Router
|
|
}
|
|
|
|
// Construct a "passive" Router, i.e. without any goroutines.
|
|
//
|
|
// We need to create some dummy channels otherwise tests hang on nil
|
|
// channels when Router.OnGossip() calls async methods.
|
|
func NewTestRouter(name PeerName) *Router {
|
|
router := NewRouter(nil, name, nil, 10, 1024, nil)
|
|
router.ConnectionMaker.queryChan = make(chan *ConnectionMakerInteraction, ChannelSize)
|
|
router.Routes.queryChan = make(chan *Interaction, ChannelSize)
|
|
return router
|
|
}
|
|
|
|
func (conn *mockChannelConnection) SendProtocolMsg(protocolMsg ProtocolMsg) {
|
|
if err := conn.dest.handleGossip(protocolMsg.msg, deliverGossip); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (router *Router) AddTestChannelConnection(r *Router) {
|
|
fromName := router.Ourself.Peer.Name
|
|
toName := r.Ourself.Peer.Name
|
|
|
|
fromPeer := NewPeer(fromName, router.Ourself.Peer.UID, 0)
|
|
toPeer := NewPeer(toName, r.Ourself.Peer.UID, 0)
|
|
|
|
r.Peers.FetchWithDefault(fromPeer) // Has side-effect of incrementing refcount
|
|
router.Peers.FetchWithDefault(toPeer) //
|
|
|
|
conn := &mockChannelConnection{RemoteConnection{router.Ourself.Peer, toPeer, "", false}, r}
|
|
router.Ourself.handleAddConnection(conn)
|
|
router.Ourself.handleConnectionEstablished(conn)
|
|
}
|
|
|
|
func (router *Router) DeleteTestChannelConnection(r *Router) {
|
|
fromName := router.Ourself.Peer.Name
|
|
toName := r.Ourself.Peer.Name
|
|
|
|
fromPeer, _ := r.Peers.Fetch(fromName)
|
|
toPeer, _ := router.Peers.Fetch(toName)
|
|
|
|
fromPeer.DecrementLocalRefCount()
|
|
toPeer.DecrementLocalRefCount()
|
|
|
|
conn, _ := router.Ourself.ConnectionTo(toName)
|
|
router.Ourself.handleDeleteConnection(conn)
|
|
}
|
|
|
|
func TestGossipTopology(t *testing.T) {
|
|
wt.RunWithTimeout(t, 1*time.Second, func() {
|
|
implTestGossipTopology(t)
|
|
})
|
|
}
|
|
|
|
// Create a Peer representing the receiver router, with connections to
|
|
// the routers supplied as arguments, carrying across all UID and
|
|
// version information.
|
|
func (router *Router) tp(routers ...*Router) *Peer {
|
|
peer := NewPeer(router.Ourself.Peer.Name, router.Ourself.Peer.UID, 0)
|
|
connections := make(map[PeerName]Connection)
|
|
for _, r := range routers {
|
|
p := NewPeer(r.Ourself.Peer.Name, r.Ourself.Peer.UID, r.Ourself.Peer.version)
|
|
connections[r.Ourself.Peer.Name] = newMockConnection(peer, p)
|
|
}
|
|
peer.SetVersionAndConnections(router.Ourself.Peer.version, connections)
|
|
return peer
|
|
}
|
|
|
|
// Check that the topology of router matches the peers and all of their connections
|
|
func checkTopology(t *testing.T, router *Router, wantedPeers ...*Peer) {
|
|
checkTopologyPeers(t, true, router.Peers.allPeers(), wantedPeers...)
|
|
}
|
|
|
|
func implTestGossipTopology(t *testing.T) {
|
|
// Create some peers that will talk to each other
|
|
peer1Name, _ := PeerNameFromString("01:00:00:01:00:00")
|
|
peer2Name, _ := PeerNameFromString("02:00:00:02:00:00")
|
|
peer3Name, _ := PeerNameFromString("03:00:00:03:00:00")
|
|
r1 := NewTestRouter(peer1Name)
|
|
r2 := NewTestRouter(peer2Name)
|
|
r3 := NewTestRouter(peer3Name)
|
|
|
|
// Check state when they have no connections
|
|
checkTopology(t, r1, r1.tp())
|
|
checkTopology(t, r2, r2.tp())
|
|
|
|
// Now try adding some connections
|
|
r1.AddTestChannelConnection(r2)
|
|
checkTopology(t, r1, r1.tp(r2), r2.tp())
|
|
checkTopology(t, r2, r1.tp(r2), r2.tp())
|
|
r2.AddTestChannelConnection(r1)
|
|
checkTopology(t, r1, r1.tp(r2), r2.tp(r1))
|
|
checkTopology(t, r2, r1.tp(r2), r2.tp(r1))
|
|
|
|
// Currently, the connection from 2 to 3 is one-way only
|
|
r2.AddTestChannelConnection(r3)
|
|
checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp())
|
|
checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp())
|
|
// When r2 gossiped to r3, 1 was unreachable from r3 so it got removed from the
|
|
// list of peers, but remains referenced in the connection from 1 to 3.
|
|
checkTopology(t, r3, r2.tp(r1, r3), r3.tp())
|
|
|
|
// Add a connection from 3 to 1 and now r1 is reachable.
|
|
r3.AddTestChannelConnection(r1)
|
|
checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp(r1))
|
|
checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp(r1))
|
|
checkTopology(t, r3, r1.tp(), r2.tp(r1, r3), r3.tp(r1))
|
|
|
|
r1.AddTestChannelConnection(r3)
|
|
checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1))
|
|
checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1))
|
|
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1))
|
|
|
|
// Drop the connection from 2 to 3
|
|
r2.DeleteTestChannelConnection(r3)
|
|
checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1))
|
|
checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1))
|
|
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1))
|
|
|
|
// Drop the connection from 1 to 3
|
|
r1.DeleteTestChannelConnection(r3)
|
|
checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1))
|
|
|
|
checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1))
|
|
checkTopology(t, r2, r1.tp(r2), r2.tp(r1))
|
|
// r3 still thinks r1 has a connection to it
|
|
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1))
|
|
|
|
// On a timer, r3 will gossip to r1
|
|
r3.SendAllGossip()
|
|
checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1))
|
|
}
|