294 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			294 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// 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 <http://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
package testing
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io/ioutil"
 | 
						|
	"math/rand"
 | 
						|
	"os"
 | 
						|
	"sync"
 | 
						|
	"testing"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"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/rpc"
 | 
						|
	"github.com/ethereum/go-ethereum/swarm/log"
 | 
						|
	"github.com/ethereum/go-ethereum/swarm/network"
 | 
						|
	"github.com/ethereum/go-ethereum/swarm/storage"
 | 
						|
)
 | 
						|
 | 
						|
type Simulation struct {
 | 
						|
	Net    *simulations.Network
 | 
						|
	Stores []storage.ChunkStore
 | 
						|
	Addrs  []network.Addr
 | 
						|
	IDs    []discover.NodeID
 | 
						|
}
 | 
						|
 | 
						|
func SetStores(addrs ...network.Addr) ([]storage.ChunkStore, func(), error) {
 | 
						|
	var datadirs []string
 | 
						|
	stores := make([]storage.ChunkStore, len(addrs))
 | 
						|
	var err error
 | 
						|
	for i, addr := range addrs {
 | 
						|
		var datadir string
 | 
						|
		datadir, err = ioutil.TempDir("", "streamer")
 | 
						|
		if err != nil {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		var store storage.ChunkStore
 | 
						|
		params := storage.NewDefaultLocalStoreParams()
 | 
						|
		params.Init(datadir)
 | 
						|
		params.BaseKey = addr.Over()
 | 
						|
		store, err = storage.NewTestLocalStoreForAddr(params)
 | 
						|
		if err != nil {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		datadirs = append(datadirs, datadir)
 | 
						|
		stores[i] = store
 | 
						|
	}
 | 
						|
	teardown := func() {
 | 
						|
		for i, datadir := range datadirs {
 | 
						|
			stores[i].Close()
 | 
						|
			os.RemoveAll(datadir)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return stores, teardown, err
 | 
						|
}
 | 
						|
 | 
						|
func NewAdapter(adapterType string, services adapters.Services) (adapter adapters.NodeAdapter, teardown func(), err error) {
 | 
						|
	teardown = func() {}
 | 
						|
	switch adapterType {
 | 
						|
	case "sim":
 | 
						|
		adapter = adapters.NewSimAdapter(services)
 | 
						|
	case "exec":
 | 
						|
		baseDir, err0 := ioutil.TempDir("", "swarm-test")
 | 
						|
		if err0 != nil {
 | 
						|
			return nil, teardown, err0
 | 
						|
		}
 | 
						|
		teardown = func() { os.RemoveAll(baseDir) }
 | 
						|
		adapter = adapters.NewExecAdapter(baseDir)
 | 
						|
	case "docker":
 | 
						|
		adapter, err = adapters.NewDockerAdapter()
 | 
						|
		if err != nil {
 | 
						|
			return nil, teardown, err
 | 
						|
		}
 | 
						|
	default:
 | 
						|
		return nil, teardown, errors.New("adapter needs to be one of sim, exec, docker")
 | 
						|
	}
 | 
						|
	return adapter, teardown, nil
 | 
						|
}
 | 
						|
 | 
						|
func CheckResult(t *testing.T, result *simulations.StepResult, startedAt, finishedAt time.Time) {
 | 
						|
	t.Logf("Simulation passed in %s", result.FinishedAt.Sub(result.StartedAt))
 | 
						|
	if len(result.Passes) > 1 {
 | 
						|
		var min, max time.Duration
 | 
						|
		var sum int
 | 
						|
		for _, pass := range result.Passes {
 | 
						|
			duration := pass.Sub(result.StartedAt)
 | 
						|
			if sum == 0 || duration < min {
 | 
						|
				min = duration
 | 
						|
			}
 | 
						|
			if duration > max {
 | 
						|
				max = duration
 | 
						|
			}
 | 
						|
			sum += int(duration.Nanoseconds())
 | 
						|
		}
 | 
						|
		t.Logf("Min: %s, Max: %s, Average: %s", min, max, time.Duration(sum/len(result.Passes))*time.Nanosecond)
 | 
						|
	}
 | 
						|
	t.Logf("Setup: %s, Shutdown: %s", result.StartedAt.Sub(startedAt), finishedAt.Sub(result.FinishedAt))
 | 
						|
}
 | 
						|
 | 
						|
type RunConfig struct {
 | 
						|
	Adapter         string
 | 
						|
	Step            *simulations.Step
 | 
						|
	NodeCount       int
 | 
						|
	ConnLevel       int
 | 
						|
	ToAddr          func(discover.NodeID) *network.BzzAddr
 | 
						|
	Services        adapters.Services
 | 
						|
	DefaultService  string
 | 
						|
	EnableMsgEvents bool
 | 
						|
}
 | 
						|
 | 
						|
func NewSimulation(conf *RunConfig) (*Simulation, func(), error) {
 | 
						|
	// create network
 | 
						|
	nodes := conf.NodeCount
 | 
						|
	adapter, adapterTeardown, err := NewAdapter(conf.Adapter, conf.Services)
 | 
						|
	if err != nil {
 | 
						|
		return nil, adapterTeardown, err
 | 
						|
	}
 | 
						|
	defaultService := "streamer"
 | 
						|
	if conf.DefaultService != "" {
 | 
						|
		defaultService = conf.DefaultService
 | 
						|
	}
 | 
						|
	net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{
 | 
						|
		ID:             "0",
 | 
						|
		DefaultService: defaultService,
 | 
						|
	})
 | 
						|
	teardown := func() {
 | 
						|
		adapterTeardown()
 | 
						|
		net.Shutdown()
 | 
						|
	}
 | 
						|
	ids := make([]discover.NodeID, nodes)
 | 
						|
	addrs := make([]network.Addr, nodes)
 | 
						|
	// start nodes
 | 
						|
	for i := 0; i < nodes; i++ {
 | 
						|
		nodeconf := adapters.RandomNodeConfig()
 | 
						|
		nodeconf.EnableMsgEvents = conf.EnableMsgEvents
 | 
						|
		node, err := net.NewNodeWithConfig(nodeconf)
 | 
						|
		if err != nil {
 | 
						|
			return nil, teardown, fmt.Errorf("error creating node: %s", err)
 | 
						|
		}
 | 
						|
		ids[i] = node.ID()
 | 
						|
		addrs[i] = conf.ToAddr(ids[i])
 | 
						|
	}
 | 
						|
	// set nodes number of Stores available
 | 
						|
	stores, storeTeardown, err := SetStores(addrs...)
 | 
						|
	teardown = func() {
 | 
						|
		net.Shutdown()
 | 
						|
		adapterTeardown()
 | 
						|
		storeTeardown()
 | 
						|
	}
 | 
						|
	if err != nil {
 | 
						|
		return nil, teardown, err
 | 
						|
	}
 | 
						|
	s := &Simulation{
 | 
						|
		Net:    net,
 | 
						|
		Stores: stores,
 | 
						|
		IDs:    ids,
 | 
						|
		Addrs:  addrs,
 | 
						|
	}
 | 
						|
	return s, teardown, nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Simulation) Run(ctx context.Context, conf *RunConfig) (*simulations.StepResult, error) {
 | 
						|
	// bring up nodes, launch the servive
 | 
						|
	nodes := conf.NodeCount
 | 
						|
	conns := conf.ConnLevel
 | 
						|
	for i := 0; i < nodes; i++ {
 | 
						|
		if err := s.Net.Start(s.IDs[i]); err != nil {
 | 
						|
			return nil, fmt.Errorf("error starting node %s: %s", s.IDs[i].TerminalString(), err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	// run a simulation which connects the 10 nodes in a chain
 | 
						|
	wg := sync.WaitGroup{}
 | 
						|
	for i := range s.IDs {
 | 
						|
		// collect the overlay addresses, to
 | 
						|
		for j := 0; j < conns; j++ {
 | 
						|
			var k int
 | 
						|
			if j == 0 {
 | 
						|
				k = i - 1
 | 
						|
			} else {
 | 
						|
				k = rand.Intn(len(s.IDs))
 | 
						|
			}
 | 
						|
			if i > 0 {
 | 
						|
				wg.Add(1)
 | 
						|
				go func(i, k int) {
 | 
						|
					defer wg.Done()
 | 
						|
					s.Net.Connect(s.IDs[i], s.IDs[k])
 | 
						|
				}(i, k)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	wg.Wait()
 | 
						|
	log.Info(fmt.Sprintf("simulation with %v nodes", len(s.Addrs)))
 | 
						|
 | 
						|
	// create an only locally retrieving FileStore for the pivot node to test
 | 
						|
	// if retriee requests have arrived
 | 
						|
	result := simulations.NewSimulation(s.Net).Run(ctx, conf.Step)
 | 
						|
	return result, nil
 | 
						|
}
 | 
						|
 | 
						|
// WatchDisconnections subscribes to admin peerEvents and sends peer event drop
 | 
						|
// errors to the errc channel. Channel quitC signals the termination of the event loop.
 | 
						|
// Returned doneC will be closed after the rpc subscription is unsubscribed,
 | 
						|
// signaling that simulations network is safe to shutdown.
 | 
						|
func WatchDisconnections(id discover.NodeID, client *rpc.Client, errc chan error, quitC chan struct{}) (doneC <-chan struct{}, err error) {
 | 
						|
	events := make(chan *p2p.PeerEvent)
 | 
						|
	sub, err := client.Subscribe(context.Background(), "admin", events, "peerEvents")
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("error getting peer events for node %v: %s", id, err)
 | 
						|
	}
 | 
						|
	c := make(chan struct{})
 | 
						|
	go func() {
 | 
						|
		defer func() {
 | 
						|
			log.Trace("watch disconnections: unsubscribe", "id", id)
 | 
						|
			sub.Unsubscribe()
 | 
						|
			close(c)
 | 
						|
		}()
 | 
						|
		for {
 | 
						|
			select {
 | 
						|
			case <-quitC:
 | 
						|
				return
 | 
						|
			case e := <-events:
 | 
						|
				if e.Type == p2p.PeerEventTypeDrop {
 | 
						|
					select {
 | 
						|
					case errc <- fmt.Errorf("peerEvent for node %v: %v", id, e):
 | 
						|
					case <-quitC:
 | 
						|
						return
 | 
						|
					}
 | 
						|
				}
 | 
						|
			case err := <-sub.Err():
 | 
						|
				if err != nil {
 | 
						|
					select {
 | 
						|
					case errc <- fmt.Errorf("error getting peer events for node %v: %v", id, err):
 | 
						|
					case <-quitC:
 | 
						|
						return
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}()
 | 
						|
	return c, nil
 | 
						|
}
 | 
						|
 | 
						|
func Trigger(d time.Duration, quitC chan struct{}, ids ...discover.NodeID) chan discover.NodeID {
 | 
						|
	trigger := make(chan discover.NodeID)
 | 
						|
	go func() {
 | 
						|
		defer close(trigger)
 | 
						|
		ticker := time.NewTicker(d)
 | 
						|
		defer ticker.Stop()
 | 
						|
		// we are only testing the pivot node (net.Nodes[0])
 | 
						|
		for range ticker.C {
 | 
						|
			for _, id := range ids {
 | 
						|
				select {
 | 
						|
				case trigger <- id:
 | 
						|
				case <-quitC:
 | 
						|
					return
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}()
 | 
						|
	return trigger
 | 
						|
}
 | 
						|
 | 
						|
func (sim *Simulation) CallClient(id discover.NodeID, f func(*rpc.Client) error) error {
 | 
						|
	node := sim.Net.GetNode(id)
 | 
						|
	if node == nil {
 | 
						|
		return fmt.Errorf("unknown node: %s", id)
 | 
						|
	}
 | 
						|
	client, err := node.Client()
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("error getting node client: %s", err)
 | 
						|
	}
 | 
						|
	return f(client)
 | 
						|
}
 |