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)
|
||
|
}
|