From 459fafde7b52f9600dd4c2750372a6751b9b297d Mon Sep 17 00:00:00 2001 From: Fabio Barone Date: Wed, 11 Apr 2018 16:50:25 -0500 Subject: [PATCH] swarm: implemented retrieval test --- swarm/network/stream/common_test.go | 25 +- .../network/stream/snapshot_retrieval_test.go | 378 ++++++++++++++++++ swarm/network/stream/snapshot_sync_test.go | 78 ++-- 3 files changed, 442 insertions(+), 39 deletions(-) create mode 100644 swarm/network/stream/snapshot_retrieval_test.go diff --git a/swarm/network/stream/common_test.go b/swarm/network/stream/common_test.go index 8428fd3fb4..a67913774a 100644 --- a/swarm/network/stream/common_test.go +++ b/swarm/network/stream/common_test.go @@ -50,14 +50,20 @@ var ( peerCount func(discover.NodeID) int adapter = flag.String("adapter", "sim", "type of simulation: sim|socket|exec|docker") loglevel = flag.Int("loglevel", 2, "verbosity of logs") + nodes = flag.Int("nodes", 0, "number of nodes") + chunks = flag.Int("chunks", 0, "number of chunks") ) var ( - defaultSkipCheck bool - waitPeerErrC chan error - chunkSize = 4096 - registries map[discover.NodeID]*TestRegistry - createStoreFunc func(id discover.NodeID, addr *network.BzzAddr) (storage.ChunkStore, error) + defaultSkipCheck bool + defaultDoRetrieve bool + waitPeerErrC chan error + chunkSize = 4096 + registries map[discover.NodeID]*TestRegistry + createStoreFunc func(id discover.NodeID, addr *network.BzzAddr) (storage.ChunkStore, error) + getRetrieveFunc = defaultRetrieveFunc + doRetrieve = defaultDoRetrieve + subscriptionCount = 0 ) var services = adapters.Services{ @@ -90,19 +96,24 @@ func NewStreamerService(ctx *adapters.ServiceContext) (node.Service, error) { delivery := NewDelivery(kad, db) deliveries[id] = delivery r := NewRegistry(addr, delivery, db, state.NewMemStore(), &RegistryOptions{ - SkipCheck: defaultSkipCheck, + SkipCheck: defaultSkipCheck, + DoRetrieve: defaultDoRetrieve, }) RegisterSwarmSyncerServer(r, db) RegisterSwarmSyncerClient(r, db) go func() { waitPeerErrC <- waitForPeers(r, 1*time.Second, peerCount(id)) }() - dpa := storage.NewDPA(storage.NewNetStore(store, nil), storage.NewDPAParams()) + dpa := storage.NewDPA(storage.NewNetStore(store, getRetrieveFunc(id)), storage.NewDPAParams()) testRegistry := &TestRegistry{Registry: r, dpa: dpa} registries[id] = testRegistry return testRegistry, nil } +func defaultRetrieveFunc(id discover.NodeID) func(chunk *storage.Chunk) error { + return nil +} + func datadirsCleanup() { for _, id := range ids { os.RemoveAll(datadirs[id]) diff --git a/swarm/network/stream/snapshot_retrieval_test.go b/swarm/network/stream/snapshot_retrieval_test.go new file mode 100644 index 0000000000..6a932584ee --- /dev/null +++ b/swarm/network/stream/snapshot_retrieval_test.go @@ -0,0 +1,378 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . +package stream + +import ( + "context" + // crand "crypto/rand" + "fmt" + //"io" + "math/rand" + //"sync" + "testing" + "time" + + "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/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" + streamTesting "github.com/ethereum/go-ethereum/swarm/network/stream/testing" + "github.com/ethereum/go-ethereum/swarm/storage" +) + +func initRetrievalTest() { + toAddr = func(id discover.NodeID) *network.BzzAddr { + addr := network.NewAddrFromNodeID(id) + return addr + } + createStoreFunc = createTestLocalStorageForId + //local stores + stores = make(map[discover.NodeID]storage.ChunkStore) + //data directories for each node and store + datadirs = make(map[discover.NodeID]string) + //deliveries for each node + deliveries = make(map[discover.NodeID]*Delivery) + getRetrieveFunc = func(id discover.NodeID) func(chunk *storage.Chunk) error { + return func(chunk *storage.Chunk) error { + skipCheck := true + //fmt.Println(fmt.Sprintf("-- %s", id)) + return deliveries[id].RequestFromPeers(chunk.Key[:], skipCheck) + } + } + //registries, map of discover.NodeID to its streamer + registries = make(map[discover.NodeID]*TestRegistry) + //channel to wait for peers connected + //not needed for this test but required from common_test for NewStreamService + waitPeerErrC = make(chan error) + //also not needed for this test but required for NewStreamService + peerCount = func(id discover.NodeID) int { + if ids[0] == id || ids[len(ids)-1] == id { + return 1 + } + return 2 + } +} + +func TestRetrieval(t *testing.T) { + + if *nodes != 0 && *chunks != 0 { + retrievalTest(t, *chunks, *nodes) + } else { + var nodeCnt []int + var chnkCnt []int + if *longrunning { + nodeCnt = []int{16, 32, 128} + chnkCnt = []int{4, 32, 256} + } else { + nodeCnt = []int{16} + chnkCnt = []int{32} + } + for _, n := range nodeCnt { + for _, c := range chnkCnt { + retrievalTest(t, c, n) + } + } + } +} + +func retrievalTest(t *testing.T, chunkCount int, nodeCount int) { + //test live and NO history + log.Info("Testing live and no history", "chunkCount", chunkCount, "nodeCount", nodeCount) + live = true + history = false + err := runRetrievalTest(chunkCount, nodeCount) + if err != nil { + t.Fatal(err) + } + //test history only + log.Info("Testing history only", "chunkCount", chunkCount, "nodeCount", nodeCount) + live = false + history = true + err = runRetrievalTest(chunkCount, nodeCount) + if err != nil { + t.Fatal(err) + } + //finally test live and history + log.Info("Testing live and history", "chunkCount", chunkCount, "nodeCount", nodeCount) + live = true + err = runRetrievalTest(chunkCount, nodeCount) + if err != nil { + t.Fatal(err) + } +} + +/* +The test generates the given number of chunks, +then uploads these to a random node. +Afterwards for every chunk generated, the nearest node addresses +are identified, syncing is started, and finally 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, +assuming that the snapshot file identifies a healthy +kademlia network. The snapshot should have 'streamer' in its service list. +*/ +func runRetrievalTest(chunkCount int, nodeCount int) error { + initRetrievalTest() + ids = make([]discover.NodeID, nodeCount) + disconnectC := make(chan error) + quitC := make(chan struct{}) + conf = &synctestConfig{} + //map of discover ID to indexes of chunks expected at that ID + conf.idToChunksMap = make(map[discover.NodeID][]int) + //map of discover ID to kademlia overlay address + conf.idToAddrMap = make(map[discover.NodeID][]byte) + //map of overlay address to discover ID + conf.addrToIdMap = make(map[string]discover.NodeID) + conf.chunks = make([]storage.Key, 0) + //load nodes from the snapshot file + net, err := initNetWithSnapshot(nodeCount) + if err != nil { + return err + } + //do cleanup after test is terminated + defer func() { + doRetrieve = defaultDoRetrieve + //shutdown the snapshot network + net.Shutdown() + //after the test, clean up local stores initialized with createLocalStoreForId + localStoreCleanup() + //finally clear all data directories + datadirsCleanup() + }() + //get the nodes of the network + nodes := net.GetNodes() + //select one index at random... + idx := rand.Intn(len(nodes)) + //...and get the the node at that index + //this is the node selected for upload + uploadNode := nodes[idx] + //iterate over all nodes... + for c := 0; c < len(nodes); c++ { + //create an array of discovery nodeIDS + ids[c] = nodes[c].ID() + a := network.ToOverlayAddr(ids[c].Bytes()) + //append it to the array of all overlay addresses + conf.addrs = append(conf.addrs, a) + conf.idToAddrMap[ids[c]] = a + conf.addrToIdMap[string(a)] = ids[c] + } + + //needed for healthy call + 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) + action := func(ctx context.Context) error { + ticker := time.NewTicker(200 * time.Millisecond) + defer ticker.Stop() + for range ticker.C { + healthy := true + for _, id := range ids { + r := registries[id] + //PeerPot for this node + pp := ppmap[id] + //call Healthy RPC + h := r.delivery.overlay.Healthy(pp) + //print info + log.Debug(r.delivery.overlay.String()) + log.Debug(fmt.Sprintf("IS HEALTHY: %t", h.GotNN && h.KnowNN && h.Full)) + if !h.GotNN || !h.Full { + healthy = false + break + } + } + if healthy { + break + } + } + + if history { + log.Info("Uploading for history") + //If testing only history, we upload the chunk(s) first + conf.chunks, err = uploadFileToSingleNodeStore(uploadNode.ID(), chunkCount) + if err != nil { + return err + } + } + + //variables needed to wait for all subscriptions established before uploading + errc := make(chan error) + + //now setup and start event watching in order to know when we can upload + ctx, watchCancel := context.WithTimeout(context.Background(), MAX_TIMEOUT*time.Second) + defer watchCancel() + + log.Info("Setting up stream subscription") + // each node Subscribes to each other's swarmChunkServerStreamName + for j, id := range ids { + log.Trace(fmt.Sprintf("Subscribe to subscription events: %d", j)) + client, err := net.GetNode(id).Client() + if err != nil { + return err + } + //watch for peers disconnecting + err = streamTesting.WatchDisconnections(id, client, disconnectC, quitC) + if err != nil { + return err + } + + watchSubscriptionEvents(ctx, id, client, errc) + } + + for j, id := range ids { + log.Trace(fmt.Sprintf("Start syncing and stream subscriptions: %d", j)) + client, err := net.GetNode(id).Client() + if err != nil { + return err + } + //start syncing! + var cnt int + err = client.CallContext(ctx, &cnt, "stream_startSyncing") + if err != nil { + return err + } + subscriptionCount += cnt + for snid := range registries[id].peers { + subscriptionCount++ + err = client.CallContext(ctx, nil, "stream_subscribeStream", snid, NewStream(swarmChunkServerStreamName, "", false), nil, Top) + if err != nil { + return err + } + } + } + + //now wait until the number of expected subscriptions has been finished + for err := range errc { + if err != nil { + return err + } + subscriptionCount-- + if subscriptionCount == 0 { + break + } + } + + log.Info("Stream subscriptions successfully requested, action terminated") + + if live { + //now upload the chunks to the selected random single node + chnks, err := uploadFileToSingleNodeStore(uploadNode.ID(), chunkCount) + if err != nil { + return err + } + conf.chunks = append(conf.chunks, chnks...) + } + + return nil + } + + chunkSize := storage.DefaultChunkSize + + //check defines what will be checked during the test + check := func(ctx context.Context, id discover.NodeID) (bool, error) { + + if id == uploadNode.ID() { + return true, nil + } + + select { + case <-ctx.Done(): + return false, ctx.Err() + case e := <-disconnectC: + log.Error(e.Error()) + return false, fmt.Errorf("Disconnect event detected, network unhealthy") + default: + } + log.Trace(fmt.Sprintf("Checking node: %s", id)) + //if there are more than one chunk, test only succeeds if all expected chunks are found + allSuccess := true + + dpa := registries[id].dpa + for _, chnk := range conf.chunks { + reader := dpa.Retrieve(chnk) + if s, err := reader.Size(nil); err != nil || s != chunkSize { + allSuccess = false + log.Warn("Retrieve error", "err", err, "chunk", chnk, "nodeId", id) + } else { + log.Debug(fmt.Sprintf("Chunk %x found", chnk)) + } + } + return allSuccess, nil + } + + //for each tick, run the checks on all nodes + timingTicker := time.NewTicker(5 * time.Second) + defer timingTicker.Stop() + go func() { + for range timingTicker.C { + for i := 0; i < len(ids); i++ { + log.Trace(fmt.Sprintf("triggering step %d, id %s", i, ids[i])) + trigger <- ids[i] + } + } + }() + + log.Info("Starting simulation run...") + + timeout := MAX_TIMEOUT * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + //run the simulation + result := simulations.NewSimulation(net).Run(ctx, &simulations.Step{ + Action: action, + Trigger: trigger, + Expect: &simulations.Expectation{ + Nodes: ids, + Check: check, + }, + }) + //close(quitC) + if result.Error != nil { + return result.Error + } + 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 +} +*/ diff --git a/swarm/network/stream/snapshot_sync_test.go b/swarm/network/stream/snapshot_sync_test.go index 2df870cc77..7932aab48e 100644 --- a/swarm/network/stream/snapshot_sync_test.go +++ b/swarm/network/stream/snapshot_sync_test.go @@ -25,7 +25,6 @@ import ( "io/ioutil" "math/rand" "os" - "sync" "testing" "time" @@ -54,8 +53,6 @@ var ( datadirs map[discover.NodeID]string ppmap map[string]*network.PeerPot - globalWg sync.WaitGroup - live bool history bool @@ -107,20 +104,24 @@ func initSyncTest() { } } -//This file executes a number of tests with the syntax -//TestSyncing_x_y -//x is the number of chunks which will be uploaded -//y is the number of nodes for the test -func TestSyncing_4_32(t *testing.T) { testSyncing(t, 4, 32) } -func TestSyncing_32_16(t *testing.T) { testSyncing(t, 32, 16) } - -func TestLongRunningSyncing(t *testing.T) { - if *longrunning { - chnkCnt := []int{1, 8, 32, 256, 1024} - nCnt := []int{16, 32, 64, 128, 256} - +//This file executes a number of syncing tests +//node and chunk number can be provided via flags +func TestSyncing(t *testing.T) { + if *nodes != 0 && *chunks != 0 { + log.Info(fmt.Sprintf("Running test with %d chunks and %d nodes...", *chunks, *nodes)) + testSyncing(t, *chunks, *nodes) + } else { + var nodeCnt []int + var chnkCnt []int + if *longrunning { + chnkCnt = []int{1, 8, 32, 256, 1024} + nodeCnt = []int{16, 32, 64, 128, 256} + } else { + chnkCnt = []int{4, 32} + nodeCnt = []int{32, 16} + } for _, chnk := range chnkCnt { - for _, n := range nCnt { + for _, n := range nodeCnt { log.Info(fmt.Sprintf("Long running test with %d chunks and %d nodes...", chnk, n)) testSyncing(t, chnk, n) } @@ -291,7 +292,7 @@ func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error { log.Info("Setting up stream subscription") // each node Subscribes to each other's swarmChunkServerStreamName for j, id := range ids { - log.Trace(fmt.Sprintf("subscribe: %d", j)) + log.Trace(fmt.Sprintf("Subscribe to subscription events: %d", j)) client, err := net.GetNode(id).Client() if err != nil { return err @@ -331,23 +332,34 @@ func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error { <-wdDoneC rpcSubscriptionsWg.Done() }() - //start syncing! - err = client.CallContext(ctx, nil, "stream_startSyncing") + } + + for j, id := range ids { + log.Trace(fmt.Sprintf("Start syncing subscriptions: %d", j)) + client, err := net.GetNode(id).Client() if err != nil { return err } + //start syncing! + var cnt int + err = client.CallContext(ctx, &cnt, "stream_startSyncing") + if err != nil { + return err + } + subscriptionCount += cnt } //now wait until the number of expected subscriptions has been finished - go func() { - globalWg.Wait() - errc <- nil - }() - - err := <-errc - if err != nil { - return err + for err := range errc { + if err != nil { + return err + } + subscriptionCount-- + if subscriptionCount == 0 { + break + } } + log.Info("Stream subscriptions successfully requested") if live { //now upload the chunks to the selected random single node @@ -443,7 +455,7 @@ func (r *TestRegistry) GetKad(ctx context.Context) string { } //the server func to start syncing -func (r *TestRegistry) StartSyncing(ctx context.Context) error { +func (r *TestRegistry) StartSyncing(ctx context.Context) (int, error) { var err error if log.Lvl(*loglevel) == log.LvlDebug { @@ -459,9 +471,10 @@ func (r *TestRegistry) StartSyncing(ctx context.Context) error { kad, ok := r.delivery.overlay.(*network.Kademlia) if !ok { - return fmt.Errorf("Not a Kademlia!") + return 0, fmt.Errorf("Not a Kademlia!") } + subCnt := 0 //iterate over each bin and solicit needed subscription to bins kad.EachBin(r.addr.Over(), pof, 0, func(conn network.OverlayConn, po int) bool { //identify begin and start index of the bin(s) we want to subscribe to @@ -471,7 +484,7 @@ func (r *TestRegistry) StartSyncing(ctx context.Context) error { histRange = &Range{} } - globalWg.Add(1) + subCnt++ err = r.RequestSubscription(conf.addrToIdMap[string(conn.Address())], NewStream("SYNC", FormatSyncBinKey(uint8(po)), live), histRange, Top) if err != nil { log.Error(fmt.Sprintf("Error in RequestSubsciption! %v", err)) @@ -480,7 +493,7 @@ func (r *TestRegistry) StartSyncing(ctx context.Context) error { return true }) - return nil + return subCnt, nil } //map chunk keys to addresses which are responsible @@ -616,6 +629,7 @@ func watchSubscriptionEvents(ctx context.Context, id discover.NodeID, client *rp return } c := make(chan struct{}) + go func() { defer func() { log.Trace("watch subscription events: unsubscribe", "id", id) @@ -636,7 +650,7 @@ func watchSubscriptionEvents(ctx context.Context, id discover.NodeID, client *rp case e := <-events: //just catch SubscribeMsg if e.Type == p2p.PeerEventTypeMsgRecv && e.Protocol == "stream" && e.MsgCode != nil && *e.MsgCode == 4 { - globalWg.Done() + errc <- nil } case err := <-sub.Err(): if err != nil {