github.com/FUSIONFoundation/efsn@v3.6.2-0.20200916075423-dbb5dd5d2cc7+incompatible/swarm/network_test.go (about)

     1  // Copyright 2018 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum 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-ethereum 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-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package swarm
    18  
    19  import (
    20  	"context"
    21  	"flag"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"math/rand"
    25  	"os"
    26  	"sync"
    27  	"sync/atomic"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/FusionFoundation/efsn/crypto"
    32  	"github.com/FusionFoundation/efsn/log"
    33  	"github.com/FusionFoundation/efsn/node"
    34  	"github.com/FusionFoundation/efsn/p2p/discover"
    35  	"github.com/FusionFoundation/efsn/p2p/simulations/adapters"
    36  	"github.com/FusionFoundation/efsn/swarm/api"
    37  	"github.com/FusionFoundation/efsn/swarm/network/simulation"
    38  	"github.com/FusionFoundation/efsn/swarm/storage"
    39  	colorable "github.com/mattn/go-colorable"
    40  )
    41  
    42  var (
    43  	loglevel     = flag.Int("loglevel", 2, "verbosity of logs")
    44  	longrunning  = flag.Bool("longrunning", false, "do run long-running tests")
    45  	waitKademlia = flag.Bool("waitkademlia", false, "wait for healthy kademlia before checking files availability")
    46  )
    47  
    48  func init() {
    49  	rand.Seed(time.Now().UnixNano())
    50  
    51  	flag.Parse()
    52  
    53  	log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
    54  }
    55  
    56  // TestSwarmNetwork runs a series of test simulations with
    57  // static and dynamic Swarm nodes in network simulation, by
    58  // uploading files to every node and retrieving them.
    59  func TestSwarmNetwork(t *testing.T) {
    60  	for _, tc := range []struct {
    61  		name     string
    62  		steps    []testSwarmNetworkStep
    63  		options  *testSwarmNetworkOptions
    64  		disabled bool
    65  	}{
    66  		{
    67  			name: "10_nodes",
    68  			steps: []testSwarmNetworkStep{
    69  				{
    70  					nodeCount: 10,
    71  				},
    72  			},
    73  			options: &testSwarmNetworkOptions{
    74  				Timeout: 45 * time.Second,
    75  			},
    76  		},
    77  		{
    78  			name: "10_nodes_skip_check",
    79  			steps: []testSwarmNetworkStep{
    80  				{
    81  					nodeCount: 10,
    82  				},
    83  			},
    84  			options: &testSwarmNetworkOptions{
    85  				Timeout:   45 * time.Second,
    86  				SkipCheck: true,
    87  			},
    88  		},
    89  		{
    90  			name: "50_nodes",
    91  			steps: []testSwarmNetworkStep{
    92  				{
    93  					nodeCount: 50,
    94  				},
    95  			},
    96  			options: &testSwarmNetworkOptions{
    97  				Timeout: 3 * time.Minute,
    98  			},
    99  			disabled: !*longrunning,
   100  		},
   101  		{
   102  			name: "50_nodes_skip_check",
   103  			steps: []testSwarmNetworkStep{
   104  				{
   105  					nodeCount: 50,
   106  				},
   107  			},
   108  			options: &testSwarmNetworkOptions{
   109  				Timeout:   3 * time.Minute,
   110  				SkipCheck: true,
   111  			},
   112  			disabled: !*longrunning,
   113  		},
   114  		{
   115  			name: "inc_node_count",
   116  			steps: []testSwarmNetworkStep{
   117  				{
   118  					nodeCount: 2,
   119  				},
   120  				{
   121  					nodeCount: 5,
   122  				},
   123  				{
   124  					nodeCount: 10,
   125  				},
   126  			},
   127  			options: &testSwarmNetworkOptions{
   128  				Timeout: 90 * time.Second,
   129  			},
   130  			disabled: !*longrunning,
   131  		},
   132  		{
   133  			name: "dec_node_count",
   134  			steps: []testSwarmNetworkStep{
   135  				{
   136  					nodeCount: 10,
   137  				},
   138  				{
   139  					nodeCount: 6,
   140  				},
   141  				{
   142  					nodeCount: 3,
   143  				},
   144  			},
   145  			options: &testSwarmNetworkOptions{
   146  				Timeout: 90 * time.Second,
   147  			},
   148  			disabled: !*longrunning,
   149  		},
   150  		{
   151  			name: "dec_inc_node_count",
   152  			steps: []testSwarmNetworkStep{
   153  				{
   154  					nodeCount: 5,
   155  				},
   156  				{
   157  					nodeCount: 3,
   158  				},
   159  				{
   160  					nodeCount: 10,
   161  				},
   162  			},
   163  			options: &testSwarmNetworkOptions{
   164  				Timeout: 90 * time.Second,
   165  			},
   166  		},
   167  		{
   168  			name: "inc_dec_node_count",
   169  			steps: []testSwarmNetworkStep{
   170  				{
   171  					nodeCount: 3,
   172  				},
   173  				{
   174  					nodeCount: 5,
   175  				},
   176  				{
   177  					nodeCount: 25,
   178  				},
   179  				{
   180  					nodeCount: 10,
   181  				},
   182  				{
   183  					nodeCount: 4,
   184  				},
   185  			},
   186  			options: &testSwarmNetworkOptions{
   187  				Timeout: 5 * time.Minute,
   188  			},
   189  			disabled: !*longrunning,
   190  		},
   191  		{
   192  			name: "inc_dec_node_count_skip_check",
   193  			steps: []testSwarmNetworkStep{
   194  				{
   195  					nodeCount: 3,
   196  				},
   197  				{
   198  					nodeCount: 5,
   199  				},
   200  				{
   201  					nodeCount: 25,
   202  				},
   203  				{
   204  					nodeCount: 10,
   205  				},
   206  				{
   207  					nodeCount: 4,
   208  				},
   209  			},
   210  			options: &testSwarmNetworkOptions{
   211  				Timeout:   5 * time.Minute,
   212  				SkipCheck: true,
   213  			},
   214  			disabled: !*longrunning,
   215  		},
   216  	} {
   217  		if tc.disabled {
   218  			continue
   219  		}
   220  		t.Run(tc.name, func(t *testing.T) {
   221  			testSwarmNetwork(t, tc.options, tc.steps...)
   222  		})
   223  	}
   224  }
   225  
   226  // testSwarmNetworkStep is the configuration
   227  // for the state of the simulation network.
   228  type testSwarmNetworkStep struct {
   229  	// number of swarm nodes that must be in the Up state
   230  	nodeCount int
   231  }
   232  
   233  // file represents the file uploaded on a particular node.
   234  type file struct {
   235  	addr   storage.Address
   236  	data   string
   237  	nodeID discover.NodeID
   238  }
   239  
   240  // check represents a reference to a file that is retrieved
   241  // from a particular node.
   242  type check struct {
   243  	key    string
   244  	nodeID discover.NodeID
   245  }
   246  
   247  // testSwarmNetworkOptions contains optional parameters for running
   248  // testSwarmNetwork.
   249  type testSwarmNetworkOptions struct {
   250  	Timeout   time.Duration
   251  	SkipCheck bool
   252  }
   253  
   254  // testSwarmNetwork is a helper function used for testing different
   255  // static and dynamic Swarm network simulations.
   256  // It is responsible for:
   257  //  - Setting up a Swarm network simulation, and updates the number of nodes within the network on every step according to steps.
   258  //  - Uploading a unique file to every node on every step.
   259  //  - May wait for Kademlia on every node to be healthy.
   260  //  - Checking if a file is retrievable from all nodes.
   261  func testSwarmNetwork(t *testing.T, o *testSwarmNetworkOptions, steps ...testSwarmNetworkStep) {
   262  	if o == nil {
   263  		o = new(testSwarmNetworkOptions)
   264  	}
   265  
   266  	sim := simulation.New(map[string]simulation.ServiceFunc{
   267  		"swarm": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) {
   268  			config := api.NewConfig()
   269  
   270  			dir, err := ioutil.TempDir("", "swarm-network-test-node")
   271  			if err != nil {
   272  				return nil, nil, err
   273  			}
   274  			cleanup = func() {
   275  				err := os.RemoveAll(dir)
   276  				if err != nil {
   277  					log.Error("cleaning up swarm temp dir", "err", err)
   278  				}
   279  			}
   280  
   281  			config.Path = dir
   282  
   283  			privkey, err := crypto.GenerateKey()
   284  			if err != nil {
   285  				return nil, cleanup, err
   286  			}
   287  
   288  			config.Init(privkey)
   289  			config.DeliverySkipCheck = o.SkipCheck
   290  			config.Port = ""
   291  
   292  			swarm, err := NewSwarm(config, nil)
   293  			if err != nil {
   294  				return nil, cleanup, err
   295  			}
   296  			bucket.Store(simulation.BucketKeyKademlia, swarm.bzz.Hive.Kademlia)
   297  			log.Info("new swarm", "bzzKey", config.BzzKey, "baseAddr", fmt.Sprintf("%x", swarm.bzz.BaseAddr()))
   298  			return swarm, cleanup, nil
   299  		},
   300  	})
   301  	defer sim.Close()
   302  
   303  	ctx := context.Background()
   304  	if o.Timeout > 0 {
   305  		var cancel context.CancelFunc
   306  		ctx, cancel = context.WithTimeout(ctx, o.Timeout)
   307  		defer cancel()
   308  	}
   309  
   310  	files := make([]file, 0)
   311  
   312  	for i, step := range steps {
   313  		log.Debug("test sync step", "n", i+1, "nodes", step.nodeCount)
   314  
   315  		change := step.nodeCount - len(sim.UpNodeIDs())
   316  
   317  		if change > 0 {
   318  			_, err := sim.AddNodesAndConnectChain(change)
   319  			if err != nil {
   320  				t.Fatal(err)
   321  			}
   322  		} else if change < 0 {
   323  			_, err := sim.StopRandomNodes(-change)
   324  			if err != nil {
   325  				t.Fatal(err)
   326  			}
   327  		} else {
   328  			t.Logf("step %v: no change in nodes", i)
   329  			continue
   330  		}
   331  
   332  		var checkStatusM sync.Map
   333  		var nodeStatusM sync.Map
   334  		var totalFoundCount uint64
   335  
   336  		result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error {
   337  			nodeIDs := sim.UpNodeIDs()
   338  			shuffle(len(nodeIDs), func(i, j int) {
   339  				nodeIDs[i], nodeIDs[j] = nodeIDs[j], nodeIDs[i]
   340  			})
   341  			for _, id := range nodeIDs {
   342  				key, data, err := uploadFile(sim.Service("swarm", id).(*Swarm))
   343  				if err != nil {
   344  					return err
   345  				}
   346  				log.Trace("file uploaded", "node", id, "key", key.String())
   347  				files = append(files, file{
   348  					addr:   key,
   349  					data:   data,
   350  					nodeID: id,
   351  				})
   352  			}
   353  
   354  			if *waitKademlia {
   355  				if _, err := sim.WaitTillHealthy(ctx, 2); err != nil {
   356  					return err
   357  				}
   358  			}
   359  
   360  			// File retrieval check is repeated until all uploaded files are retrieved from all nodes
   361  			// or until the timeout is reached.
   362  			for {
   363  				if retrieve(sim, files, &checkStatusM, &nodeStatusM, &totalFoundCount) == 0 {
   364  					return nil
   365  				}
   366  			}
   367  		})
   368  
   369  		if result.Error != nil {
   370  			t.Fatal(result.Error)
   371  		}
   372  		log.Debug("done: test sync step", "n", i+1, "nodes", step.nodeCount)
   373  	}
   374  }
   375  
   376  // uploadFile, uploads a short file to the swarm instance
   377  // using the api.Put method.
   378  func uploadFile(swarm *Swarm) (storage.Address, string, error) {
   379  	b := make([]byte, 8)
   380  	_, err := rand.Read(b)
   381  	if err != nil {
   382  		return nil, "", err
   383  	}
   384  	// File data is very short, but it is ensured that its
   385  	// uniqueness is very certain.
   386  	data := fmt.Sprintf("test content %s %x", time.Now().Round(0), b)
   387  	ctx := context.TODO()
   388  	k, wait, err := swarm.api.Put(ctx, data, "text/plain", false)
   389  	if err != nil {
   390  		return nil, "", err
   391  	}
   392  	if wait != nil {
   393  		err = wait(ctx)
   394  	}
   395  	return k, data, err
   396  }
   397  
   398  // retrieve is the function that is used for checking the availability of
   399  // uploaded files in testSwarmNetwork test helper function.
   400  func retrieve(
   401  	sim *simulation.Simulation,
   402  	files []file,
   403  	checkStatusM *sync.Map,
   404  	nodeStatusM *sync.Map,
   405  	totalFoundCount *uint64,
   406  ) (missing uint64) {
   407  	shuffle(len(files), func(i, j int) {
   408  		files[i], files[j] = files[j], files[i]
   409  	})
   410  
   411  	var totalWg sync.WaitGroup
   412  	errc := make(chan error)
   413  
   414  	nodeIDs := sim.UpNodeIDs()
   415  
   416  	totalCheckCount := len(nodeIDs) * len(files)
   417  
   418  	for _, id := range nodeIDs {
   419  		if _, ok := nodeStatusM.Load(id); ok {
   420  			continue
   421  		}
   422  		start := time.Now()
   423  		var checkCount uint64
   424  		var foundCount uint64
   425  
   426  		totalWg.Add(1)
   427  
   428  		var wg sync.WaitGroup
   429  
   430  		swarm := sim.Service("swarm", id).(*Swarm)
   431  		for _, f := range files {
   432  
   433  			checkKey := check{
   434  				key:    f.addr.String(),
   435  				nodeID: id,
   436  			}
   437  			if n, ok := checkStatusM.Load(checkKey); ok && n.(int) == 0 {
   438  				continue
   439  			}
   440  
   441  			checkCount++
   442  			wg.Add(1)
   443  			go func(f file, id discover.NodeID) {
   444  				defer wg.Done()
   445  
   446  				log.Debug("api get: check file", "node", id.String(), "key", f.addr.String(), "total files found", atomic.LoadUint64(totalFoundCount))
   447  
   448  				r, _, _, _, err := swarm.api.Get(context.TODO(), api.NOOPDecrypt, f.addr, "/")
   449  				if err != nil {
   450  					errc <- fmt.Errorf("api get: node %s, key %s, kademlia %s: %v", id, f.addr, swarm.bzz.Hive, err)
   451  					return
   452  				}
   453  				d, err := ioutil.ReadAll(r)
   454  				if err != nil {
   455  					errc <- fmt.Errorf("api get: read response: node %s, key %s: kademlia %s: %v", id, f.addr, swarm.bzz.Hive, err)
   456  					return
   457  				}
   458  				data := string(d)
   459  				if data != f.data {
   460  					errc <- fmt.Errorf("file contend missmatch: node %s, key %s, expected %q, got %q", id, f.addr, f.data, data)
   461  					return
   462  				}
   463  				checkStatusM.Store(checkKey, 0)
   464  				atomic.AddUint64(&foundCount, 1)
   465  				log.Info("api get: file found", "node", id.String(), "key", f.addr.String(), "content", data, "files found", atomic.LoadUint64(&foundCount))
   466  			}(f, id)
   467  		}
   468  
   469  		go func(id discover.NodeID) {
   470  			defer totalWg.Done()
   471  			wg.Wait()
   472  
   473  			atomic.AddUint64(totalFoundCount, foundCount)
   474  
   475  			if foundCount == checkCount {
   476  				log.Info("all files are found for node", "id", id.String(), "duration", time.Since(start))
   477  				nodeStatusM.Store(id, 0)
   478  				return
   479  			}
   480  			log.Debug("files missing for node", "id", id.String(), "check", checkCount, "found", foundCount)
   481  		}(id)
   482  
   483  	}
   484  
   485  	go func() {
   486  		totalWg.Wait()
   487  		close(errc)
   488  	}()
   489  
   490  	var errCount int
   491  	for err := range errc {
   492  		if err != nil {
   493  			errCount++
   494  		}
   495  		log.Warn(err.Error())
   496  	}
   497  
   498  	log.Info("check stats", "total check count", totalCheckCount, "total files found", atomic.LoadUint64(totalFoundCount), "total errors", errCount)
   499  
   500  	return uint64(totalCheckCount) - atomic.LoadUint64(totalFoundCount)
   501  }
   502  
   503  // Backported from stdlib https://golang.org/src/math/rand/rand.go?s=11175:11215#L333
   504  //
   505  // Replace with rand.Shuffle from go 1.10 when go 1.9 support is dropped.
   506  //
   507  // shuffle pseudo-randomizes the order of elements.
   508  // n is the number of elements. Shuffle panics if n < 0.
   509  // swap swaps the elements with indexes i and j.
   510  func shuffle(n int, swap func(i, j int)) {
   511  	if n < 0 {
   512  		panic("invalid argument to Shuffle")
   513  	}
   514  
   515  	// Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
   516  	// Shuffle really ought not be called with n that doesn't fit in 32 bits.
   517  	// Not only will it take a very long time, but with 2³¹! possible permutations,
   518  	// there's no way that any PRNG can have a big enough internal state to
   519  	// generate even a minuscule percentage of the possible permutations.
   520  	// Nevertheless, the right API signature accepts an int n, so handle it as best we can.
   521  	i := n - 1
   522  	for ; i > 1<<31-1-1; i-- {
   523  		j := int(rand.Int63n(int64(i + 1)))
   524  		swap(i, j)
   525  	}
   526  	for ; i > 0; i-- {
   527  		j := int(rand.Int31n(int32(i + 1)))
   528  		swap(i, j)
   529  	}
   530  }