github.com/etsc3259/etsc@v0.0.0-20190109113336-a9c2c10f9c95/swarm/network/stream/snapshot_retrieval_test.go (about)

     1  // Copyright 2018 The go-etsc Authors
     2  // This file is part of the go-etsc library.
     3  //
     4  // The go-etsc 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 go-etsc library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY 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 go-etsc library. If not, see <http://www.gnu.org/licenses/>.
    16  package stream
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"os"
    22  	"sync"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/ETSC3259/etsc/node"
    27  	"github.com/ETSC3259/etsc/p2p/enode"
    28  	"github.com/ETSC3259/etsc/p2p/simulations/adapters"
    29  	"github.com/ETSC3259/etsc/swarm/log"
    30  	"github.com/ETSC3259/etsc/swarm/network"
    31  	"github.com/ETSC3259/etsc/swarm/network/simulation"
    32  	"github.com/ETSC3259/etsc/swarm/state"
    33  	"github.com/ETSC3259/etsc/swarm/storage"
    34  )
    35  
    36  //constants for random file generation
    37  const (
    38  	minFileSize = 2
    39  	maxFileSize = 40
    40  )
    41  
    42  //This test is a retrieval test for nodes.
    43  //A configurable number of nodes can be
    44  //provided to the test.
    45  //Files are uploaded to nodes, other nodes try to retrieve the file
    46  //Number of nodes can be provided via commandline too.
    47  func TestFileRetrieval(t *testing.T) {
    48  	if *nodes != 0 {
    49  		err := runFileRetrievalTest(*nodes)
    50  		if err != nil {
    51  			t.Fatal(err)
    52  		}
    53  	} else {
    54  		nodeCnt := []int{16}
    55  		//if the `longrunning` flag has been provided
    56  		//run more test combinations
    57  		if *longrunning {
    58  			nodeCnt = append(nodeCnt, 32, 64, 128)
    59  		}
    60  		for _, n := range nodeCnt {
    61  			err := runFileRetrievalTest(n)
    62  			if err != nil {
    63  				t.Fatal(err)
    64  			}
    65  		}
    66  	}
    67  }
    68  
    69  //This test is a retrieval test for nodes.
    70  //One node is randomly selected to be the pivot node.
    71  //A configurable number of chunks and nodes can be
    72  //provided to the test, the number of chunks is uploaded
    73  //to the pivot node and other nodes try to retrieve the chunk(s).
    74  //Number of chunks and nodes can be provided via commandline too.
    75  func TestRetrieval(t *testing.T) {
    76  	//if nodes/chunks have been provided via commandline,
    77  	//run the tests with these values
    78  	if *nodes != 0 && *chunks != 0 {
    79  		err := runRetrievalTest(*chunks, *nodes)
    80  		if err != nil {
    81  			t.Fatal(err)
    82  		}
    83  	} else {
    84  		var nodeCnt []int
    85  		var chnkCnt []int
    86  		//if the `longrunning` flag has been provided
    87  		//run more test combinations
    88  		if *longrunning {
    89  			nodeCnt = []int{16, 32, 128}
    90  			chnkCnt = []int{4, 32, 256}
    91  		} else {
    92  			//default test
    93  			nodeCnt = []int{16}
    94  			chnkCnt = []int{32}
    95  		}
    96  		for _, n := range nodeCnt {
    97  			for _, c := range chnkCnt {
    98  				err := runRetrievalTest(c, n)
    99  				if err != nil {
   100  					t.Fatal(err)
   101  				}
   102  			}
   103  		}
   104  	}
   105  }
   106  
   107  var retrievalSimServiceMap = map[string]simulation.ServiceFunc{
   108  	"streamer": retrievalStreamerFunc,
   109  }
   110  
   111  func retrievalStreamerFunc(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
   112  	n := ctx.Config.Node()
   113  	addr := network.NewAddr(n)
   114  	store, datadir, err := createTestLocalStorageForID(n.ID(), addr)
   115  	if err != nil {
   116  		return nil, nil, err
   117  	}
   118  	bucket.Store(bucketKeyStore, store)
   119  
   120  	localStore := store.(*storage.LocalStore)
   121  	netStore, err := storage.NewNetStore(localStore, nil)
   122  	if err != nil {
   123  		return nil, nil, err
   124  	}
   125  	kad := network.NewKademlia(addr.Over(), network.NewKadParams())
   126  	delivery := NewDelivery(kad, netStore)
   127  	netStore.NewNetFetcherFunc = network.NewFetcherFactory(delivery.RequestFromPeers, true).New
   128  
   129  	r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{
   130  		Retrieval:       RetrievalEnabled,
   131  		Syncing:         SyncingAutoSubscribe,
   132  		SyncUpdateDelay: 3 * time.Second,
   133  	})
   134  
   135  	fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams())
   136  	bucket.Store(bucketKeyFileStore, fileStore)
   137  
   138  	cleanup = func() {
   139  		os.RemoveAll(datadir)
   140  		netStore.Close()
   141  		r.Close()
   142  	}
   143  
   144  	return r, cleanup, nil
   145  }
   146  
   147  /*
   148  The test loads a snapshot file to construct the swarm network,
   149  assuming that the snapshot file identifies a healthy
   150  kademlia network. Nevertheless a health check runs in the
   151  simulation's `action` function.
   152  
   153  The snapshot should have 'streamer' in its service list.
   154  */
   155  func runFileRetrievalTest(nodeCount int) error {
   156  	sim := simulation.New(retrievalSimServiceMap)
   157  	defer sim.Close()
   158  
   159  	log.Info("Initializing test config")
   160  
   161  	conf := &synctestConfig{}
   162  	//map of discover ID to indexes of chunks expected at that ID
   163  	conf.idToChunksMap = make(map[enode.ID][]int)
   164  	//map of overlay address to discover ID
   165  	conf.addrToIDMap = make(map[string]enode.ID)
   166  	//array where the generated chunk hashes will be stored
   167  	conf.hashes = make([]storage.Address, 0)
   168  
   169  	err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount))
   170  	if err != nil {
   171  		return err
   172  	}
   173  
   174  	ctx, cancelSimRun := context.WithTimeout(context.Background(), 1*time.Minute)
   175  	defer cancelSimRun()
   176  
   177  	result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error {
   178  		nodeIDs := sim.UpNodeIDs()
   179  		for _, n := range nodeIDs {
   180  			//get the kademlia overlay address from this ID
   181  			a := n.Bytes()
   182  			//append it to the array of all overlay addresses
   183  			conf.addrs = append(conf.addrs, a)
   184  			//the proximity calculation is on overlay addr,
   185  			//the p2p/simulations check func triggers on enode.ID,
   186  			//so we need to know which overlay addr maps to which nodeID
   187  			conf.addrToIDMap[string(a)] = n
   188  		}
   189  
   190  		//an array for the random files
   191  		var randomFiles []string
   192  		//channel to signal when the upload has finished
   193  		//uploadFinished := make(chan struct{})
   194  		//channel to trigger new node checks
   195  
   196  		conf.hashes, randomFiles, err = uploadFilesToNodes(sim)
   197  		if err != nil {
   198  			return err
   199  		}
   200  		if _, err := sim.WaitTillHealthy(ctx, 2); err != nil {
   201  			return err
   202  		}
   203  
   204  		// File retrieval check is repeated until all uploaded files are retrieved from all nodes
   205  		// or until the timeout is reached.
   206  	REPEAT:
   207  		for {
   208  			for _, id := range nodeIDs {
   209  				//for each expected file, check if it is in the local store
   210  				item, ok := sim.NodeItem(id, bucketKeyFileStore)
   211  				if !ok {
   212  					return fmt.Errorf("No filestore")
   213  				}
   214  				fileStore := item.(*storage.FileStore)
   215  				//check all chunks
   216  				for i, hash := range conf.hashes {
   217  					reader, _ := fileStore.Retrieve(context.TODO(), hash)
   218  					//check that we can read the file size and that it corresponds to the generated file size
   219  					if s, err := reader.Size(ctx, nil); err != nil || s != int64(len(randomFiles[i])) {
   220  						log.Debug("Retrieve error", "err", err, "hash", hash, "nodeId", id)
   221  						time.Sleep(500 * time.Millisecond)
   222  						continue REPEAT
   223  					}
   224  					log.Debug(fmt.Sprintf("File with root hash %x successfully retrieved", hash))
   225  				}
   226  			}
   227  			return nil
   228  		}
   229  	})
   230  
   231  	if result.Error != nil {
   232  		return result.Error
   233  	}
   234  
   235  	return nil
   236  }
   237  
   238  /*
   239  The test generates the given number of chunks.
   240  
   241  The test loads a snapshot file to construct the swarm network,
   242  assuming that the snapshot file identifies a healthy
   243  kademlia network. Nevertheless a health check runs in the
   244  simulation's `action` function.
   245  
   246  The snapshot should have 'streamer' in its service list.
   247  */
   248  func runRetrievalTest(chunkCount int, nodeCount int) error {
   249  	sim := simulation.New(retrievalSimServiceMap)
   250  	defer sim.Close()
   251  
   252  	conf := &synctestConfig{}
   253  	//map of discover ID to indexes of chunks expected at that ID
   254  	conf.idToChunksMap = make(map[enode.ID][]int)
   255  	//map of overlay address to discover ID
   256  	conf.addrToIDMap = make(map[string]enode.ID)
   257  	//array where the generated chunk hashes will be stored
   258  	conf.hashes = make([]storage.Address, 0)
   259  
   260  	err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount))
   261  	if err != nil {
   262  		return err
   263  	}
   264  
   265  	ctx := context.Background()
   266  	result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error {
   267  		nodeIDs := sim.UpNodeIDs()
   268  		for _, n := range nodeIDs {
   269  			//get the kademlia overlay address from this ID
   270  			a := n.Bytes()
   271  			//append it to the array of all overlay addresses
   272  			conf.addrs = append(conf.addrs, a)
   273  			//the proximity calculation is on overlay addr,
   274  			//the p2p/simulations check func triggers on enode.ID,
   275  			//so we need to know which overlay addr maps to which nodeID
   276  			conf.addrToIDMap[string(a)] = n
   277  		}
   278  
   279  		//this is the node selected for upload
   280  		node := sim.RandomUpNode()
   281  		item, ok := sim.NodeItem(node.ID, bucketKeyStore)
   282  		if !ok {
   283  			return fmt.Errorf("No localstore")
   284  		}
   285  		lstore := item.(*storage.LocalStore)
   286  		conf.hashes, err = uploadFileToSingleNodeStore(node.ID, chunkCount, lstore)
   287  		if err != nil {
   288  			return err
   289  		}
   290  		if _, err := sim.WaitTillHealthy(ctx, 2); err != nil {
   291  			return err
   292  		}
   293  
   294  		// File retrieval check is repeated until all uploaded files are retrieved from all nodes
   295  		// or until the timeout is reached.
   296  	REPEAT:
   297  		for {
   298  			for _, id := range nodeIDs {
   299  				//for each expected chunk, check if it is in the local store
   300  				//check on the node's FileStore (netstore)
   301  				item, ok := sim.NodeItem(id, bucketKeyFileStore)
   302  				if !ok {
   303  					return fmt.Errorf("No filestore")
   304  				}
   305  				fileStore := item.(*storage.FileStore)
   306  				//check all chunks
   307  				for _, hash := range conf.hashes {
   308  					reader, _ := fileStore.Retrieve(context.TODO(), hash)
   309  					//check that we can read the chunk size and that it corresponds to the generated chunk size
   310  					if s, err := reader.Size(ctx, nil); err != nil || s != int64(chunkSize) {
   311  						log.Debug("Retrieve error", "err", err, "hash", hash, "nodeId", id, "size", s)
   312  						time.Sleep(500 * time.Millisecond)
   313  						continue REPEAT
   314  					}
   315  					log.Debug(fmt.Sprintf("Chunk with root hash %x successfully retrieved", hash))
   316  				}
   317  			}
   318  			// all nodes and files found, exit loop and return without error
   319  			return nil
   320  		}
   321  	})
   322  
   323  	if result.Error != nil {
   324  		return result.Error
   325  	}
   326  
   327  	return nil
   328  }