swarm: consolidate snapshot sync and retrieval tests

This commit is contained in:
Fabio Barone 2018-04-12 08:45:42 -05:00
parent 459fafde7b
commit f530257139
2 changed files with 115 additions and 80 deletions

View file

@ -17,31 +17,26 @@ package stream
import ( import (
"context" "context"
// crand "crypto/rand"
"fmt" "fmt"
//"io"
"math/rand" "math/rand"
//"sync"
"testing" "testing"
"time" "time"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
//"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/simulations" "github.com/ethereum/go-ethereum/p2p/simulations"
//"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
//"github.com/ethereum/go-ethereum/pot"
//"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/swarm/network" "github.com/ethereum/go-ethereum/swarm/network"
streamTesting "github.com/ethereum/go-ethereum/swarm/network/stream/testing" streamTesting "github.com/ethereum/go-ethereum/swarm/network/stream/testing"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
) )
func initRetrievalTest() { func initRetrievalTest() {
//global func to get overlay address from discover ID
toAddr = func(id discover.NodeID) *network.BzzAddr { toAddr = func(id discover.NodeID) *network.BzzAddr {
addr := network.NewAddrFromNodeID(id) addr := network.NewAddrFromNodeID(id)
return addr return addr
} }
//global func to create local store
createStoreFunc = createTestLocalStorageForId createStoreFunc = createTestLocalStorageForId
//local stores //local stores
stores = make(map[discover.NodeID]storage.ChunkStore) stores = make(map[discover.NodeID]storage.ChunkStore)
@ -49,16 +44,15 @@ func initRetrievalTest() {
datadirs = make(map[discover.NodeID]string) datadirs = make(map[discover.NodeID]string)
//deliveries for each node //deliveries for each node
deliveries = make(map[discover.NodeID]*Delivery) deliveries = make(map[discover.NodeID]*Delivery)
//global retrieve func
getRetrieveFunc = func(id discover.NodeID) func(chunk *storage.Chunk) error { getRetrieveFunc = func(id discover.NodeID) func(chunk *storage.Chunk) error {
return func(chunk *storage.Chunk) error { return func(chunk *storage.Chunk) error {
skipCheck := true skipCheck := true
//fmt.Println(fmt.Sprintf("-- %s", id))
return deliveries[id].RequestFromPeers(chunk.Key[:], skipCheck) return deliveries[id].RequestFromPeers(chunk.Key[:], skipCheck)
} }
} }
//registries, map of discover.NodeID to its streamer //registries, map of discover.NodeID to its streamer
registries = make(map[discover.NodeID]*TestRegistry) registries = make(map[discover.NodeID]*TestRegistry)
//channel to wait for peers connected
//not needed for this test but required from common_test for NewStreamService //not needed for this test but required from common_test for NewStreamService
waitPeerErrC = make(chan error) waitPeerErrC = make(chan error)
//also not needed for this test but required for NewStreamService //also not needed for this test but required for NewStreamService
@ -70,17 +64,27 @@ func initRetrievalTest() {
} }
} }
//This test is a retrieval test for nodes.
//One node is randomly selected to be the pivot node.
//A configurable number of chunks and nodes can be
//provided to the test, the number of chunks is uploaded
//to the pivot node and other nodes try to retrieve the chunk(s).
//Number of chunks and nodes can be provided via commandline too.
func TestRetrieval(t *testing.T) { func TestRetrieval(t *testing.T) {
//if nodes/chunks have been provided via commandline,
//run the tests with these values
if *nodes != 0 && *chunks != 0 { if *nodes != 0 && *chunks != 0 {
retrievalTest(t, *chunks, *nodes) retrievalTest(t, *chunks, *nodes)
} else { } else {
var nodeCnt []int var nodeCnt []int
var chnkCnt []int var chnkCnt []int
//if the `longrunning` flag has been provided
//run more test combinations
if *longrunning { if *longrunning {
nodeCnt = []int{16, 32, 128} nodeCnt = []int{16, 32, 128}
chnkCnt = []int{4, 32, 256} chnkCnt = []int{4, 32, 256}
} else { } else {
//default test
nodeCnt = []int{16} nodeCnt = []int{16}
chnkCnt = []int{32} chnkCnt = []int{32}
} }
@ -92,6 +96,7 @@ func TestRetrieval(t *testing.T) {
} }
} }
//Every test runs 3 times, a live, a history, and a live AND history
func retrievalTest(t *testing.T, chunkCount int, nodeCount int) { func retrievalTest(t *testing.T, chunkCount int, nodeCount int) {
//test live and NO history //test live and NO history
log.Info("Testing live and no history", "chunkCount", chunkCount, "nodeCount", nodeCount) log.Info("Testing live and no history", "chunkCount", chunkCount, "nodeCount", nodeCount)
@ -119,22 +124,33 @@ func retrievalTest(t *testing.T, chunkCount int, nodeCount int) {
} }
/* /*
The test generates the given number of chunks, The test generates the given number of chunks.
then uploads these to a random node.
Afterwards for every chunk generated, the nearest node addresses The upload is done by dependency to the global
are identified, syncing is started, and finally we verify `live` and `history` variables;
that the nodes closer to the chunk addresses actually do have
the chunks in their local stores. If `live` is set, first stream subscriptions are established, then
upload to a random node.
If `history` is enabled, first upload then build up subscriptions.
The test loads a snapshot file to construct the swarm network, The test loads a snapshot file to construct the swarm network,
assuming that the snapshot file identifies a healthy assuming that the snapshot file identifies a healthy
kademlia network. The snapshot should have 'streamer' in its service list. kademlia network. Nevertheless a health check runs in the
simulation's `action` function.
The snapshot should have 'streamer' in its service list.
*/ */
func runRetrievalTest(chunkCount int, nodeCount int) error { func runRetrievalTest(chunkCount int, nodeCount int) error {
//for every run (live, history), int the variables
initRetrievalTest() initRetrievalTest()
//the ids of the snapshot nodes, initiate only now as we need nodeCount
ids = make([]discover.NodeID, nodeCount) ids = make([]discover.NodeID, nodeCount)
//channel to check for disconnection errors
disconnectC := make(chan error) disconnectC := make(chan error)
//channel to close disconnection watcher routine
quitC := make(chan struct{}) quitC := make(chan struct{})
//the test conf (using same as in `snapshot_sync_test`
conf = &synctestConfig{} conf = &synctestConfig{}
//map of discover ID to indexes of chunks expected at that ID //map of discover ID to indexes of chunks expected at that ID
conf.idToChunksMap = make(map[discover.NodeID][]int) conf.idToChunksMap = make(map[discover.NodeID][]int)
@ -142,6 +158,7 @@ func runRetrievalTest(chunkCount int, nodeCount int) error {
conf.idToAddrMap = make(map[discover.NodeID][]byte) conf.idToAddrMap = make(map[discover.NodeID][]byte)
//map of overlay address to discover ID //map of overlay address to discover ID
conf.addrToIdMap = make(map[string]discover.NodeID) conf.addrToIdMap = make(map[string]discover.NodeID)
//array where the generated chunk hashes will be stored
conf.chunks = make([]storage.Key, 0) conf.chunks = make([]storage.Key, 0)
//load nodes from the snapshot file //load nodes from the snapshot file
net, err := initNetWithSnapshot(nodeCount) net, err := initNetWithSnapshot(nodeCount)
@ -179,13 +196,11 @@ func runRetrievalTest(chunkCount int, nodeCount int) error {
//needed for healthy call //needed for healthy call
ppmap = network.NewPeerPot(testMinProxBinSize, ids, conf.addrs) ppmap = network.NewPeerPot(testMinProxBinSize, ids, conf.addrs)
// channel to signal simulation initialisation with action call complete
// or node disconnections
//disconnectC := make(chan error)
//quitC := make(chan struct{})
trigger := make(chan discover.NodeID) trigger := make(chan discover.NodeID)
//simulation action
action := func(ctx context.Context) error { action := func(ctx context.Context) error {
//first run the health check on all nodes,
//wait until nodes are all healthy
ticker := time.NewTicker(200 * time.Millisecond) ticker := time.NewTicker(200 * time.Millisecond)
defer ticker.Stop() defer ticker.Stop()
for range ticker.C { for range ticker.C {
@ -226,7 +241,14 @@ func runRetrievalTest(chunkCount int, nodeCount int) error {
defer watchCancel() defer watchCancel()
log.Info("Setting up stream subscription") log.Info("Setting up stream subscription")
// each node Subscribes to each other's swarmChunkServerStreamName //We need two iterations, one to subscribe to the subscription events
//(so we know when setup phase is finished), and one to
//actually run the stream subscriptions. We can't do it in the same iteration,
//because while the first nodes in the loop are setting up subscriptions,
//the latter ones have not subscribed to listen to peer events yet,
//and then we miss events.
//first iteration: setup disconnection watcher and subscribe to peer events
for j, id := range ids { for j, id := range ids {
log.Trace(fmt.Sprintf("Subscribe to subscription events: %d", j)) log.Trace(fmt.Sprintf("Subscribe to subscription events: %d", j))
client, err := net.GetNode(id).Client() client, err := net.GetNode(id).Client()
@ -239,9 +261,11 @@ func runRetrievalTest(chunkCount int, nodeCount int) error {
return err return err
} }
//check for `SubscribeMsg` events to know when setup phase is complete
watchSubscriptionEvents(ctx, id, client, errc) watchSubscriptionEvents(ctx, id, client, errc)
} }
//second iteration: start syncing and setup stream subscriptions
for j, id := range ids { for j, id := range ids {
log.Trace(fmt.Sprintf("Start syncing and stream subscriptions: %d", j)) log.Trace(fmt.Sprintf("Start syncing and stream subscriptions: %d", j))
client, err := net.GetNode(id).Client() client, err := net.GetNode(id).Client()
@ -254,7 +278,10 @@ func runRetrievalTest(chunkCount int, nodeCount int) error {
if err != nil { if err != nil {
return err return err
} }
//increment the number of subscriptions we need to wait for
//by the count returned from startSyncing (SYNC subscriptions)
subscriptionCount += cnt subscriptionCount += cnt
//now also add the number of RETRIEVAL_REQUEST subscriptions
for snid := range registries[id].peers { for snid := range registries[id].peers {
subscriptionCount++ subscriptionCount++
err = client.CallContext(ctx, nil, "stream_subscribeStream", snid, NewStream(swarmChunkServerStreamName, "", false), nil, Top) err = client.CallContext(ctx, nil, "stream_subscribeStream", snid, NewStream(swarmChunkServerStreamName, "", false), nil, Top)
@ -265,11 +292,15 @@ func runRetrievalTest(chunkCount int, nodeCount int) error {
} }
//now wait until the number of expected subscriptions has been finished //now wait until the number of expected subscriptions has been finished
//`watchSubscriptionEvents` will write with a `nil` value to errc
//every time a `SubscriptionMsg` has been received
for err := range errc { for err := range errc {
if err != nil { if err != nil {
return err return err
} }
//`nil` received, decrement count
subscriptionCount-- subscriptionCount--
//all subscriptions received
if subscriptionCount == 0 { if subscriptionCount == 0 {
break break
} }
@ -294,6 +325,7 @@ func runRetrievalTest(chunkCount int, nodeCount int) error {
//check defines what will be checked during the test //check defines what will be checked during the test
check := func(ctx context.Context, id discover.NodeID) (bool, error) { check := func(ctx context.Context, id discover.NodeID) (bool, error) {
//don't check the uploader node
if id == uploadNode.ID() { if id == uploadNode.ID() {
return true, nil return true, nil
} }
@ -310,9 +342,12 @@ func runRetrievalTest(chunkCount int, nodeCount int) error {
//if there are more than one chunk, test only succeeds if all expected chunks are found //if there are more than one chunk, test only succeeds if all expected chunks are found
allSuccess := true allSuccess := true
//check on the node's dpa (netstore)
dpa := registries[id].dpa dpa := registries[id].dpa
//check all chunks
for _, chnk := range conf.chunks { for _, chnk := range conf.chunks {
reader := dpa.Retrieve(chnk) reader := dpa.Retrieve(chnk)
//assuming that reading the Size of the chunk is enough to know we found it
if s, err := reader.Size(nil); err != nil || s != chunkSize { if s, err := reader.Size(nil); err != nil || s != chunkSize {
allSuccess = false allSuccess = false
log.Warn("Retrieve error", "err", err, "chunk", chnk, "nodeId", id) log.Warn("Retrieve error", "err", err, "chunk", chnk, "nodeId", id)
@ -350,29 +385,10 @@ func runRetrievalTest(chunkCount int, nodeCount int) error {
Check: check, Check: check,
}, },
}) })
//close(quitC)
if result.Error != nil { if result.Error != nil {
return result.Error return result.Error
} }
return nil return nil
} }
//upload a file(chunks)
/*
func uploadRandomChunks(net *simulations.Network, chunkCount int) error {
log.Debug(fmt.Sprintf("Uploading to node id: %s", id))
lstore := stores[id]
size := chunkCount * chunkSize
dpa := storage.NewDPA(lstore, storage.NewChunkerParams())
dpa.Start()
rootHash, wait, err := dpa.Store(io.LimitReader(crand.Reader, int64(size)), int64(size))
wait()
if err != nil {
return nil, err
}
defer dpa.Stop()
return rootHash, nil
}
*/

View file

@ -82,7 +82,7 @@ func initSyncTest() {
addr := network.NewAddrFromNodeID(id) addr := network.NewAddrFromNodeID(id)
return addr return addr
} }
//global func to create local store
createStoreFunc = createTestLocalStorageForId createStoreFunc = createTestLocalStorageForId
//local stores //local stores
stores = make(map[discover.NodeID]storage.ChunkStore) stores = make(map[discover.NodeID]storage.ChunkStore)
@ -92,7 +92,6 @@ func initSyncTest() {
deliveries = make(map[discover.NodeID]*Delivery) deliveries = make(map[discover.NodeID]*Delivery)
//registries, map of discover.NodeID to its streamer //registries, map of discover.NodeID to its streamer
registries = make(map[discover.NodeID]*TestRegistry) registries = make(map[discover.NodeID]*TestRegistry)
//channel to wait for peers connected
//not needed for this test but required from common_test for NewStreamService //not needed for this test but required from common_test for NewStreamService
waitPeerErrC = make(chan error) waitPeerErrC = make(chan error)
//also not needed for this test but required for NewStreamService //also not needed for this test but required for NewStreamService
@ -104,19 +103,29 @@ func initSyncTest() {
} }
} }
//This file executes a number of syncing tests //This test is a syncing test for nodes.
//node and chunk number can be provided via flags //One node is randomly selected to be the pivot node.
//A configurable number of chunks and nodes can be
//provided to the test, the number of chunks is uploaded
//to the pivot node, and we check that nodes get the chunks
//they are expected to store based on the syncing protocol.
//Number of chunks and nodes can be provided via commandline too.
func TestSyncing(t *testing.T) { func TestSyncing(t *testing.T) {
//if nodes/chunks have been provided via commandline,
//run the tests with these values
if *nodes != 0 && *chunks != 0 { if *nodes != 0 && *chunks != 0 {
log.Info(fmt.Sprintf("Running test with %d chunks and %d nodes...", *chunks, *nodes)) log.Info(fmt.Sprintf("Running test with %d chunks and %d nodes...", *chunks, *nodes))
testSyncing(t, *chunks, *nodes) testSyncing(t, *chunks, *nodes)
} else { } else {
var nodeCnt []int var nodeCnt []int
var chnkCnt []int var chnkCnt []int
//if the `longrunning` flag has been provided
//run more test combinations
if *longrunning { if *longrunning {
chnkCnt = []int{1, 8, 32, 256, 1024} chnkCnt = []int{1, 8, 32, 256, 1024}
nodeCnt = []int{16, 32, 64, 128, 256} nodeCnt = []int{16, 32, 64, 128, 256}
} else { } else {
//default test
chnkCnt = []int{4, 32} chnkCnt = []int{4, 32}
nodeCnt = []int{32, 16} nodeCnt = []int{32, 16}
} }
@ -129,11 +138,9 @@ func TestSyncing(t *testing.T) {
} }
} }
//do run the tests //Do run the tests
//Every test runs 3 times, a live, a history, and a live AND history
func testSyncing(t *testing.T, chunkCount int, nodeCount int) { func testSyncing(t *testing.T, chunkCount int, nodeCount int) {
initSyncTest()
ids = make([]discover.NodeID, nodeCount)
//test live and NO history //test live and NO history
log.Info("Testing live and no history") log.Info("Testing live and no history")
live = true live = true
@ -160,12 +167,19 @@ func testSyncing(t *testing.T, chunkCount int, nodeCount int) {
} }
/* /*
The test generates the given number of chunks, The test generates the given number of chunks
then uploads these to a random node.
Afterwards for every chunk generated, the nearest node addresses The upload is done by dependency to the global
are identified, syncing is started, and finally we verify `live` and `history` variables;
that the nodes closer to the chunk addresses actually do have
the chunks in their local stores. If `live` is set, first stream subscriptions are established, then
upload to a random node.
If `history` is enabled, first upload then build up subscriptions.
For every chunk generated, the nearest node addresses
are identified, we verify that the nodes closer to the
chunk addresses actually do have the chunks in their local stores.
The test loads a snapshot file to construct the swarm network, The test loads a snapshot file to construct the swarm network,
assuming that the snapshot file identifies a healthy assuming that the snapshot file identifies a healthy
@ -180,6 +194,9 @@ For every test run, a series of three tests will be executed:
are uploaded twice, once before and once after subscriptions are uploaded twice, once before and once after subscriptions
*/ */
func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error { func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error {
initSyncTest()
//the ids of the snapshot nodes, initiate only now as we need nodeCount
ids = make([]discover.NodeID, nodeCount)
//initialize the test struct //initialize the test struct
conf = &synctestConfig{} conf = &synctestConfig{}
//map of discover ID to indexes of chunks expected at that ID //map of discover ID to indexes of chunks expected at that ID
@ -188,12 +205,13 @@ func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error {
conf.idToAddrMap = make(map[discover.NodeID][]byte) conf.idToAddrMap = make(map[discover.NodeID][]byte)
//map of overlay address to discover ID //map of overlay address to discover ID
conf.addrToIdMap = make(map[string]discover.NodeID) conf.addrToIdMap = make(map[string]discover.NodeID)
//array where the generated chunk hashes will be stored
conf.chunks = make([]storage.Key, 0) conf.chunks = make([]storage.Key, 0)
//First load the snapshot from the file //channel to trigger node checks in the simulation
trigger := make(chan discover.NodeID) trigger := make(chan discover.NodeID)
// channel to signal simulation initialisation with action call complete //channel to check for disconnection errors
// or node disconnections
disconnectC := make(chan error) disconnectC := make(chan error)
//channel to close disconnection watcher routine
quitC := make(chan struct{}) quitC := make(chan struct{})
//load nodes from the snapshot file //load nodes from the snapshot file
@ -246,6 +264,8 @@ func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error {
//define the action to be performed before the test checks: start syncing //define the action to be performed before the test checks: start syncing
action := func(ctx context.Context) error { action := func(ctx context.Context) error {
//first run the health check on all nodes,
//wait until nodes are all healthy
ticker := time.NewTicker(200 * time.Millisecond) ticker := time.NewTicker(200 * time.Millisecond)
defer ticker.Stop() defer ticker.Stop()
for range ticker.C { for range ticker.C {
@ -290,7 +310,15 @@ func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error {
defer watchCancel() defer watchCancel()
log.Info("Setting up stream subscription") log.Info("Setting up stream subscription")
// each node Subscribes to each other's swarmChunkServerStreamName
//We need two iterations, one to subscribe to the subscription events
//(so we know when setup phase is finished), and one to
//actually run the stream subscriptions. We can't do it in the same iteration,
//because while the first nodes in the loop are setting up subscriptions,
//the latter ones have not subscribed to listen to peer events yet,
//and then we miss events.
//first iteration: setup disconnection watcher and subscribe to peer events
for j, id := range ids { for j, id := range ids {
log.Trace(fmt.Sprintf("Subscribe to subscription events: %d", j)) log.Trace(fmt.Sprintf("Subscribe to subscription events: %d", j))
client, err := net.GetNode(id).Client() client, err := net.GetNode(id).Client()
@ -309,19 +337,6 @@ func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error {
rpcSubscriptionsWg.Done() rpcSubscriptionsWg.Done()
}() }()
if log.Lvl(*loglevel) >= log.LvlTrace {
//this will print the kademlia tables of all nodes
//to only print the kademlia of the pivot node,
//use: if j == idx {}
var kt string
err = client.CallContext(ctx, &kt, "stream_getKad")
if err != nil {
return err
}
log.Debug("kad table " + node.ID().String())
log.Debug(kt)
}
//watch for peers disconnecting //watch for peers disconnecting
wdDoneC, err := streamTesting.WatchDisconnections(id, client, disconnectC, quitC) wdDoneC, err := streamTesting.WatchDisconnections(id, client, disconnectC, quitC)
if err != nil { if err != nil {
@ -334,6 +349,7 @@ func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error {
}() }()
} }
//second iteration: start syncing
for j, id := range ids { for j, id := range ids {
log.Trace(fmt.Sprintf("Start syncing subscriptions: %d", j)) log.Trace(fmt.Sprintf("Start syncing subscriptions: %d", j))
client, err := net.GetNode(id).Client() client, err := net.GetNode(id).Client()
@ -346,15 +362,20 @@ func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error {
if err != nil { if err != nil {
return err return err
} }
//increment the number of subscriptions we need to wait for
//by the count returned from startSyncing (SYNC subscriptions)
subscriptionCount += cnt subscriptionCount += cnt
} }
//now wait until the number of expected subscriptions has been finished //now wait until the number of expected subscriptions has been finished
//`watchSubscriptionEvents` will write with a `nil` value to errc
for err := range errc { for err := range errc {
if err != nil { if err != nil {
return err return err
} }
//`nil` received, decrement count
subscriptionCount-- subscriptionCount--
//all subscriptions received
if subscriptionCount == 0 { if subscriptionCount == 0 {
break break
} }
@ -449,12 +470,10 @@ func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error {
return nil return nil
} }
//Show kademlia of uploading node for debugging
func (r *TestRegistry) GetKad(ctx context.Context) string {
return r.delivery.overlay.String()
}
//the server func to start syncing //the server func to start syncing
//issues `RequestSubscriptionMsg` to peers, based on po, by iterating over
//the kademlia's `EachBin` function.
//returns the number of subscriptions requested
func (r *TestRegistry) StartSyncing(ctx context.Context) (int, error) { func (r *TestRegistry) StartSyncing(ctx context.Context) (int, error) {
var err error var err error