github.com/susy-go/susy-graviton@v0.0.0-20190614130430-36cddae42305/swarm/network/stream/visualized_snapshot_sync_sim_test.go (about)

     1  // Copyleft 2018 The susy-graviton Authors
     2  // This file is part of the susy-graviton library.
     3  //
     4  // The susy-graviton library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The susy-graviton library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MSRCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the susy-graviton library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // +build withserver
    18  
    19  package stream
    20  
    21  import (
    22  	"bytes"
    23  	"context"
    24  	"errors"
    25  	"fmt"
    26  	"io"
    27  	"sync"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/susy-go/susy-graviton/node"
    32  	"github.com/susy-go/susy-graviton/p2p"
    33  	"github.com/susy-go/susy-graviton/p2p/enode"
    34  	"github.com/susy-go/susy-graviton/p2p/protocols"
    35  	"github.com/susy-go/susy-graviton/p2p/simulations"
    36  	"github.com/susy-go/susy-graviton/p2p/simulations/adapters"
    37  	"github.com/susy-go/susy-graviton/srlp"
    38  	"github.com/susy-go/susy-graviton/swarm/log"
    39  	"github.com/susy-go/susy-graviton/swarm/network/simulation"
    40  	"github.com/susy-go/susy-graviton/swarm/state"
    41  	"github.com/susy-go/susy-graviton/swarm/storage"
    42  )
    43  
    44  /*
    45  The tests in this file need to be executed with
    46  
    47  			-tags=withserver
    48  
    49  Also, they will stall if executed stand-alone, because they wait
    50  for the visualization frontend to send a POST /runsim message.
    51  */
    52  
    53  //setup the sim, evaluate nodeCount and chunkCount and create the sim
    54  func setupSim(serviceMap map[string]simulation.ServiceFunc) (int, int, *simulation.Simulation) {
    55  	nodeCount := *nodes
    56  	chunkCount := *chunks
    57  
    58  	if nodeCount == 0 || chunkCount == 0 {
    59  		nodeCount = 32
    60  		chunkCount = 1
    61  	}
    62  
    63  	//setup the simulation with server, which means the sim won't run
    64  	//until it receives a POST /runsim from the frontend
    65  	sim := simulation.New(serviceMap).WithServer(":8888")
    66  	return nodeCount, chunkCount, sim
    67  }
    68  
    69  //This test requests bogus hashes into the network
    70  func TestNonExistingHashesWithServer(t *testing.T) {
    71  
    72  	nodeCount, _, sim := setupSim(retrievalSimServiceMap)
    73  	defer sim.Close()
    74  
    75  	err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount))
    76  	if err != nil {
    77  		panic(err)
    78  	}
    79  
    80  	//in order to get some meaningful visualization, it is beneficial
    81  	//to define a minimum duration of this test
    82  	testDuration := 20 * time.Second
    83  
    84  	result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) (err error) {
    85  		disconnected := watchDisconnections(ctx, sim)
    86  		defer func() {
    87  			if err != nil {
    88  				if yes, ok := disconnected.Load().(bool); ok && yes {
    89  					err = errors.New("disconnect events received")
    90  				}
    91  			}
    92  		}()
    93  
    94  		//check on the node's FileStore (netstore)
    95  		id := sim.Net.GetRandomUpNode().ID()
    96  		item, ok := sim.NodeItem(id, bucketKeyFileStore)
    97  		if !ok {
    98  			return errors.New("No filestore")
    99  		}
   100  		fileStore := item.(*storage.FileStore)
   101  		//create a bogus hash
   102  		fakeHash := storage.GenerateRandomChunk(1000).Address()
   103  		//try to retrieve it - will propagate RetrieveRequestMsg into the network
   104  		reader, _ := fileStore.Retrieve(context.TODO(), fakeHash)
   105  		if _, err := reader.Size(ctx, nil); err != nil {
   106  			log.Debug("expected error for non-existing chunk")
   107  		}
   108  		//sleep so that the frontend can have something to display
   109  		time.Sleep(testDuration)
   110  
   111  		return nil
   112  	})
   113  	if result.Error != nil {
   114  		sendSimTerminatedEvent(sim)
   115  		t.Fatal(result.Error)
   116  	}
   117  
   118  	sendSimTerminatedEvent(sim)
   119  
   120  }
   121  
   122  //send a termination event to the frontend
   123  func sendSimTerminatedEvent(sim *simulation.Simulation) {
   124  	evt := &simulations.Event{
   125  		Type:    EventTypeSimTerminated,
   126  		Control: false,
   127  	}
   128  	sim.Net.Events().Send(evt)
   129  }
   130  
   131  //This test is the same as the snapshot sync test,
   132  //but with a HTTP server
   133  //It also sends some custom events so that the frontend
   134  //can visualize messages like SendOfferedMsg, WantedHashesMsg, DeliveryMsg
   135  func TestSnapshotSyncWithServer(t *testing.T) {
   136  	//t.Skip("temporarily disabled as simulations.WaitTillHealthy cannot be trusted")
   137  
   138  	//define a wrapper object to be able to pass around data
   139  	wrapper := &netWrapper{}
   140  
   141  	nodeCount := *nodes
   142  	chunkCount := *chunks
   143  
   144  	if nodeCount == 0 || chunkCount == 0 {
   145  		nodeCount = 32
   146  		chunkCount = 1
   147  	}
   148  
   149  	log.Info(fmt.Sprintf("Running the simulation with %d nodes and %d chunks", nodeCount, chunkCount))
   150  
   151  	sim := simulation.New(map[string]simulation.ServiceFunc{
   152  		"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
   153  			addr, netStore, delivery, clean, err := newNetStoreAndDeliveryWithRequestFunc(ctx, bucket, dummyRequestFromPeers)
   154  			if err != nil {
   155  				return nil, nil, err
   156  			}
   157  
   158  			r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{
   159  				Retrieval:       RetrievalDisabled,
   160  				Syncing:         SyncingAutoSubscribe,
   161  				SyncUpdateDelay: 3 * time.Second,
   162  			}, nil)
   163  
   164  			tr := &testRegistry{
   165  				Registry: r,
   166  				w:        wrapper,
   167  			}
   168  
   169  			bucket.Store(bucketKeyRegistry, tr)
   170  
   171  			cleanup = func() {
   172  				tr.Close()
   173  				clean()
   174  			}
   175  
   176  			return tr, cleanup, nil
   177  		},
   178  	}).WithServer(":8888") //start with the HTTP server
   179  
   180  	nodeCount, chunkCount, sim := setupSim(simServiceMap)
   181  	defer sim.Close()
   182  
   183  	log.Info("Initializing test config")
   184  
   185  	conf := &synctestConfig{}
   186  	//map of discover ID to indexes of chunks expected at that ID
   187  	conf.idToChunksMap = make(map[enode.ID][]int)
   188  	//map of overlay address to discover ID
   189  	conf.addrToIDMap = make(map[string]enode.ID)
   190  	//array where the generated chunk hashes will be stored
   191  	conf.hashes = make([]storage.Address, 0)
   192  	//pass the network to the wrapper object
   193  	wrapper.setNetwork(sim.Net)
   194  	err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount))
   195  	if err != nil {
   196  		panic(err)
   197  	}
   198  
   199  	//run the sim
   200  	result := runSim(conf, ctx, sim, chunkCount)
   201  
   202  	//send terminated event
   203  	evt := &simulations.Event{
   204  		Type:    EventTypeSimTerminated,
   205  		Control: false,
   206  	}
   207  	go sim.Net.Events().Send(evt)
   208  
   209  	if result.Error != nil {
   210  		panic(result.Error)
   211  	}
   212  	log.Info("Simulation ended")
   213  }
   214  
   215  //testRegistry embeds registry
   216  //it allows to replace the protocol run function
   217  type testRegistry struct {
   218  	*Registry
   219  	w *netWrapper
   220  }
   221  
   222  //Protocols replaces the protocol's run function
   223  func (tr *testRegistry) Protocols() []p2p.Protocol {
   224  	regProto := tr.Registry.Protocols()
   225  	//set the `stream` protocol's run function with the testRegistry's one
   226  	regProto[0].Run = tr.runProto
   227  	return regProto
   228  }
   229  
   230  //runProto is the new overwritten protocol's run function for this test
   231  func (tr *testRegistry) runProto(p *p2p.Peer, rw p2p.MsgReadWriter) error {
   232  	//create a custom rw message ReadWriter
   233  	testRw := &testMsgReadWriter{
   234  		MsgReadWriter: rw,
   235  		Peer:          p,
   236  		w:             tr.w,
   237  		Registry:      tr.Registry,
   238  	}
   239  	//now run the actual upper layer `Registry`'s protocol function
   240  	return tr.runProtocol(p, testRw)
   241  }
   242  
   243  //testMsgReadWriter is a custom rw
   244  //it will allow us to re-use the message twice
   245  type testMsgReadWriter struct {
   246  	*Registry
   247  	p2p.MsgReadWriter
   248  	*p2p.Peer
   249  	w *netWrapper
   250  }
   251  
   252  //netWrapper wrapper object so we can pass data around
   253  type netWrapper struct {
   254  	net *simulations.Network
   255  }
   256  
   257  //set the network to the wrapper for later use (used inside the custom rw)
   258  func (w *netWrapper) setNetwork(n *simulations.Network) {
   259  	w.net = n
   260  }
   261  
   262  //get he network from the wrapper (used inside the custom rw)
   263  func (w *netWrapper) getNetwork() *simulations.Network {
   264  	return w.net
   265  }
   266  
   267  // ReadMsg reads a message from the underlying MsgReadWriter and emits a
   268  // "message received" event
   269  //we do this because we are interested in the Payload of the message for custom use
   270  //in this test, but messages can only be consumed once (stream io.Reader)
   271  func (ev *testMsgReadWriter) ReadMsg() (p2p.Msg, error) {
   272  	//read the message from the underlying rw
   273  	msg, err := ev.MsgReadWriter.ReadMsg()
   274  	if err != nil {
   275  		return msg, err
   276  	}
   277  
   278  	//don't do anything with message codes we actually are not needing/reading
   279  	subCodes := []uint64{1, 2, 10}
   280  	found := false
   281  	for _, c := range subCodes {
   282  		if c == msg.Code {
   283  			found = true
   284  		}
   285  	}
   286  	//just return if not a msg code we are interested in
   287  	if !found {
   288  		return msg, nil
   289  	}
   290  
   291  	//we use a io.TeeReader so that we can read the message twice
   292  	//the Payload is a io.Reader, so if we read from it, the actual protocol handler
   293  	//cannot access it anymore.
   294  	//But we need that handler to be able to consume the message as normal,
   295  	//as if we would not do anything here with that message
   296  	var buf bytes.Buffer
   297  	tee := io.TeeReader(msg.Payload, &buf)
   298  
   299  	mcp := &p2p.Msg{
   300  		Code:       msg.Code,
   301  		Size:       msg.Size,
   302  		ReceivedAt: msg.ReceivedAt,
   303  		Payload:    tee,
   304  	}
   305  	//assign the copy for later use
   306  	msg.Payload = &buf
   307  
   308  	//now let's look into the message
   309  	var wmsg protocols.WrappedMsg
   310  	err = mcp.Decode(&wmsg)
   311  	if err != nil {
   312  		log.Error(err.Error())
   313  		return msg, err
   314  	}
   315  	//create a new message from the code
   316  	val, ok := ev.Registry.GetSpec().NewMsg(mcp.Code)
   317  	if !ok {
   318  		return msg, errors.New(fmt.Sprintf("Invalid message code: %v", msg.Code))
   319  	}
   320  	//decode it
   321  	if err := srlp.DecodeBytes(wmsg.Payload, val); err != nil {
   322  		return msg, errors.New(fmt.Sprintf("Decoding error <= %v: %v", msg, err))
   323  	}
   324  	//now for every message type we are interested in, create a custom event and send it
   325  	var evt *simulations.Event
   326  	switch val := val.(type) {
   327  	case *OfferedHashesMsg:
   328  		evt = &simulations.Event{
   329  			Type:    EventTypeChunkOffered,
   330  			Node:    ev.w.getNetwork().GetNode(ev.ID()),
   331  			Control: false,
   332  			Data:    val.Hashes,
   333  		}
   334  	case *WantedHashesMsg:
   335  		evt = &simulations.Event{
   336  			Type:    EventTypeChunkWanted,
   337  			Node:    ev.w.getNetwork().GetNode(ev.ID()),
   338  			Control: false,
   339  		}
   340  	case *ChunkDeliveryMsgSyncing:
   341  		evt = &simulations.Event{
   342  			Type:    EventTypeChunkDelivered,
   343  			Node:    ev.w.getNetwork().GetNode(ev.ID()),
   344  			Control: false,
   345  			Data:    val.Addr.String(),
   346  		}
   347  	}
   348  	if evt != nil {
   349  		//send custom event to feed; frontend will listen to it and display
   350  		ev.w.getNetwork().Events().Send(evt)
   351  	}
   352  	return msg, nil
   353  }