315 lines
8.5 KiB
Go
315 lines
8.5 KiB
Go
package ipam
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/weaveworks/weave/common"
|
|
"github.com/weaveworks/weave/ipam/address"
|
|
"github.com/weaveworks/weave/router"
|
|
wt "github.com/weaveworks/weave/testing"
|
|
)
|
|
|
|
type mockMessage struct {
|
|
dst router.PeerName
|
|
msgType byte
|
|
buf []byte
|
|
}
|
|
|
|
func (m *mockMessage) String() string {
|
|
return fmt.Sprintf("-> %s [%x]", m.dst, m.buf)
|
|
}
|
|
|
|
func toStringArray(messages []mockMessage) []string {
|
|
out := make([]string, len(messages))
|
|
for i := range out {
|
|
out[i] = messages[i].String()
|
|
}
|
|
return out
|
|
}
|
|
|
|
type mockGossipComms struct {
|
|
sync.RWMutex
|
|
t *testing.T
|
|
name string
|
|
messages []mockMessage
|
|
}
|
|
|
|
func (m *mockGossipComms) String() string {
|
|
m.RLock()
|
|
defer m.RUnlock()
|
|
return fmt.Sprintf("[mockGossipComms %s]", m.name)
|
|
}
|
|
|
|
// Note: this style of verification, using equalByteBuffer, requires
|
|
// that the contents of messages are never re-ordered. Which, for instance,
|
|
// requires they are not based off iterating through a map.
|
|
|
|
func (m *mockGossipComms) GossipBroadcast(update router.GossipData) error {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
buf := []byte{}
|
|
if len(m.messages) == 0 {
|
|
m.Fatalf("%s: Gossip broadcast message unexpected: \n%x", m.name, buf)
|
|
} else if msg := m.messages[0]; msg.dst != router.UnknownPeerName {
|
|
m.Fatalf("%s: Expected Gossip message to %s but got broadcast", m.name, msg.dst)
|
|
} else if msg.buf != nil && !equalByteBuffer(msg.buf, buf) {
|
|
m.Fatalf("%s: Gossip message not sent as expected: \nwant: %x\ngot : %x", m.name, msg.buf, buf)
|
|
} else {
|
|
// Swallow this message
|
|
m.messages = m.messages[1:]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func equalByteBuffer(a, b []byte) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := 0; i < len(a); i++ {
|
|
if a[i] != b[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (m *mockGossipComms) Fatalf(format string, args ...interface{}) {
|
|
// this sometimes hangs: wt.Fatalf(m.t, args...)
|
|
panic(fmt.Sprintf(format, args...))
|
|
}
|
|
|
|
func (m *mockGossipComms) GossipUnicast(dstPeerName router.PeerName, buf []byte) error {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
if len(m.messages) == 0 {
|
|
m.Fatalf("%s: Gossip message to %s unexpected: \n%s", m.name, dstPeerName, buf)
|
|
} else if msg := m.messages[0]; msg.dst == router.UnknownPeerName {
|
|
m.Fatalf("%s: Expected Gossip broadcast message but got dest %s", m.name, dstPeerName)
|
|
} else if msg.dst != dstPeerName {
|
|
m.Fatalf("%s: Expected Gossip message to %s but got dest %s", m.name, msg.dst, dstPeerName)
|
|
} else if buf[0] != msg.msgType {
|
|
m.Fatalf("%s: Expected Gossip message of type %d but got type %d", m.name, msg.msgType, buf[0])
|
|
} else if msg.buf != nil && !equalByteBuffer(msg.buf, buf[1:]) {
|
|
m.Fatalf("%s: Gossip message not sent as expected: \nwant: %x\ngot : %x", m.name, msg.buf, buf[1:])
|
|
} else {
|
|
// Swallow this message
|
|
m.messages = m.messages[1:]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ExpectMessage(alloc *Allocator, dst string, msgType byte, buf []byte) {
|
|
m := alloc.gossip.(*mockGossipComms)
|
|
dstPeerName, _ := router.PeerNameFromString(dst)
|
|
m.Lock()
|
|
m.messages = append(m.messages, mockMessage{dstPeerName, msgType, buf})
|
|
m.Unlock()
|
|
}
|
|
|
|
func ExpectBroadcastMessage(alloc *Allocator, buf []byte) {
|
|
m := alloc.gossip.(*mockGossipComms)
|
|
m.Lock()
|
|
m.messages = append(m.messages, mockMessage{router.UnknownPeerName, 0, buf})
|
|
m.Unlock()
|
|
}
|
|
|
|
func CheckAllExpectedMessagesSent(allocs ...*Allocator) {
|
|
for _, alloc := range allocs {
|
|
m := alloc.gossip.(*mockGossipComms)
|
|
m.RLock()
|
|
if len(m.messages) > 0 {
|
|
wt.Fatalf(m.t, "%s: Gossip message(s) not sent as expected: \n%x", m.name, m.messages)
|
|
}
|
|
m.RUnlock()
|
|
}
|
|
}
|
|
|
|
func makeAllocator(name string, cidrStr string, quorum uint) (*Allocator, address.Range) {
|
|
peername, err := router.PeerNameFromString(name)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
_, cidr, err := address.ParseCIDR(cidrStr)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
alloc := NewAllocator(peername, router.PeerUID(rand.Int63()),
|
|
"nick-"+name, cidr.Range(), quorum)
|
|
|
|
return alloc, cidr.HostRange()
|
|
}
|
|
|
|
func makeAllocatorWithMockGossip(t *testing.T, name string, universeCIDR string, quorum uint) (*Allocator, address.Range) {
|
|
alloc, subnet := makeAllocator(name, universeCIDR, quorum)
|
|
gossip := &mockGossipComms{t: t, name: name}
|
|
alloc.SetInterfaces(gossip)
|
|
alloc.Start()
|
|
return alloc, subnet
|
|
}
|
|
|
|
func (alloc *Allocator) claimRingForTesting(allocs ...*Allocator) {
|
|
peers := []router.PeerName{alloc.ourName}
|
|
for _, alloc2 := range allocs {
|
|
peers = append(peers, alloc2.ourName)
|
|
}
|
|
alloc.ring.ClaimForPeers(normalizeConsensus(peers))
|
|
alloc.space.AddRanges(alloc.ring.OwnedRanges())
|
|
}
|
|
|
|
func (alloc *Allocator) NumFreeAddresses(r address.Range) address.Offset {
|
|
resultChan := make(chan address.Offset)
|
|
alloc.actionChan <- func() {
|
|
resultChan <- alloc.space.NumFreeAddressesInRange(r)
|
|
}
|
|
return <-resultChan
|
|
}
|
|
|
|
// Check whether or not something was sent on a channel
|
|
func AssertSent(t *testing.T, ch <-chan bool) {
|
|
timeout := time.After(10 * time.Second)
|
|
select {
|
|
case <-ch:
|
|
// This case is ok
|
|
case <-timeout:
|
|
wt.Fatalf(t, "Nothing sent on channel")
|
|
}
|
|
}
|
|
|
|
func AssertNothingSent(t *testing.T, ch <-chan bool) {
|
|
select {
|
|
case val := <-ch:
|
|
wt.Fatalf(t, "Unexpected value on channel: %t", val)
|
|
default:
|
|
// no message received
|
|
}
|
|
}
|
|
|
|
func AssertNothingSentErr(t *testing.T, ch <-chan error) {
|
|
select {
|
|
case val := <-ch:
|
|
wt.Fatalf(t, "Unexpected value on channel: %t", val)
|
|
default:
|
|
// no message received
|
|
}
|
|
}
|
|
|
|
// Router to convey gossip from one gossiper to another, for testing
|
|
type gossipMessage struct {
|
|
isUnicast bool
|
|
sender *router.PeerName
|
|
buf []byte // for unicast
|
|
data router.GossipData // for broadcast
|
|
exitChan chan bool
|
|
}
|
|
|
|
type TestGossipRouter struct {
|
|
gossipChans map[router.PeerName]chan gossipMessage
|
|
loss float32 // 0.0 means no loss
|
|
}
|
|
|
|
func (grouter *TestGossipRouter) GossipBroadcast(update router.GossipData) error {
|
|
for _, gossipChan := range grouter.gossipChans {
|
|
select {
|
|
case gossipChan <- gossipMessage{data: update}:
|
|
default: // drop the message if we cannot send it
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (grouter *TestGossipRouter) removePeer(peer router.PeerName) {
|
|
gossipChan := grouter.gossipChans[peer]
|
|
resultChan := make(chan bool)
|
|
gossipChan <- gossipMessage{exitChan: resultChan}
|
|
<-resultChan
|
|
delete(grouter.gossipChans, peer)
|
|
}
|
|
|
|
type TestGossipRouterClient struct {
|
|
router *TestGossipRouter
|
|
sender router.PeerName
|
|
}
|
|
|
|
func (grouter *TestGossipRouter) connect(sender router.PeerName, gossiper router.Gossiper) router.Gossip {
|
|
gossipChan := make(chan gossipMessage, 100)
|
|
|
|
go func() {
|
|
gossipTimer := time.Tick(10 * time.Second)
|
|
for {
|
|
select {
|
|
case message := <-gossipChan:
|
|
if message.exitChan != nil {
|
|
message.exitChan <- true
|
|
return
|
|
}
|
|
|
|
if rand.Float32() > (1.0 - grouter.loss) {
|
|
continue
|
|
}
|
|
|
|
if message.isUnicast {
|
|
if err := gossiper.OnGossipUnicast(*message.sender, message.buf); err != nil {
|
|
panic(fmt.Sprintf("Error doing gossip unicast to %s: %s", sender, err))
|
|
}
|
|
} else {
|
|
for _, msg := range message.data.Encode() {
|
|
if _, err := gossiper.OnGossipBroadcast(msg); err != nil {
|
|
panic(fmt.Sprintf("Error doing gossip broadcast to %s: %s", sender, err))
|
|
}
|
|
}
|
|
}
|
|
case <-gossipTimer:
|
|
grouter.GossipBroadcast(gossiper.Gossip())
|
|
}
|
|
}
|
|
}()
|
|
|
|
grouter.gossipChans[sender] = gossipChan
|
|
return TestGossipRouterClient{grouter, sender}
|
|
}
|
|
|
|
func (client TestGossipRouterClient) GossipUnicast(dstPeerName router.PeerName, buf []byte) error {
|
|
select {
|
|
case client.router.gossipChans[dstPeerName] <- gossipMessage{isUnicast: true, sender: &client.sender, buf: buf}:
|
|
default: // drop the message if we cannot send it
|
|
common.Error.Printf("Dropping message")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (client TestGossipRouterClient) GossipBroadcast(update router.GossipData) error {
|
|
return client.router.GossipBroadcast(update)
|
|
}
|
|
|
|
func makeNetworkOfAllocators(size int, cidr string) ([]*Allocator, TestGossipRouter, address.Range) {
|
|
|
|
gossipRouter := TestGossipRouter{make(map[router.PeerName]chan gossipMessage), 0.0}
|
|
allocs := make([]*Allocator, size)
|
|
var subnet address.Range
|
|
|
|
for i := 0; i < size; i++ {
|
|
var alloc *Allocator
|
|
alloc, subnet = makeAllocator(fmt.Sprintf("%02d:00:00:02:00:00", i),
|
|
cidr, uint(size/2+1))
|
|
alloc.SetInterfaces(gossipRouter.connect(alloc.ourName, alloc))
|
|
alloc.Start()
|
|
allocs[i] = alloc
|
|
}
|
|
|
|
gossipRouter.GossipBroadcast(allocs[size-1].Gossip())
|
|
time.Sleep(1000 * time.Millisecond)
|
|
return allocs, gossipRouter, subnet
|
|
}
|
|
|
|
func stopNetworkOfAllocators(allocs []*Allocator) {
|
|
for _, alloc := range allocs {
|
|
alloc.Stop()
|
|
}
|
|
}
|