p2p/simulations: replace mocker with noderunner

This commit is contained in:
Fabio Barone 2017-09-19 21:56:45 -05:00 committed by Felix Lange
parent 32516c768e
commit 6f69e790e9
3 changed files with 271 additions and 0 deletions

View file

@ -278,6 +278,7 @@ func NewServer(network *Network) *Server {
s.GET("/", s.GetNetwork)
s.POST("/start", s.StartNetwork)
s.POST("/stop", s.StopNetwork)
s.POST("/reset", s.ResetNetwork)
s.GET("/events", s.StreamNetworkEvents)
s.GET("/snapshot", s.CreateSnapshot)
s.POST("/snapshot", s.LoadSnapshot)
@ -318,6 +319,13 @@ func (s *Server) StopNetwork(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK)
}
// StopNetwork stops all nodes in the network
func (s *Server) ResetNetwork(w http.ResponseWriter, req *http.Request) {
s.network.Reset()
w.WriteHeader(http.StatusOK)
}
// StreamNetworkEvents streams network events as a server-sent-events stream
func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) {
events := make(chan *Event)

View file

@ -502,6 +502,19 @@ func (self *Network) Shutdown() {
close(self.quitc)
}
//Reset resets all network properties:
//emtpies the nodes and the connection list
func (self *Network) Reset() {
for k := range self.nodeMap {
delete(self.nodeMap, k)
}
for c := range self.connMap {
delete(self.connMap, c)
}
self.Nodes = nil
self.Conns = nil
}
// Node is a wrapper around adapters.Node which is used to track the status
// of a node in the network
type Node struct {

View file

@ -0,0 +1,250 @@
// Copyright 2017 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 simulations simulates p2p networks.
// A NodeRunner simulates starting and stopping real nodes in a network.
package simulations
import (
"fmt"
"math/rand"
"net/http"
"sync"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/julienschmidt/httprouter"
)
//A NodeRunner only has a Run function
type NodeRunner interface {
Run()
}
//used to stop the NodeRunner loop
var quit chan struct{}
//nodeRunner holds properties needed
//the idea is that we create specific noderunner instances
//with their own run loop, with each one implementing Run()
type nodeRunner struct {
router *httprouter.Router
nodes []discover.NodeID
network *Network
nodeCount int
}
//startStopRunner: just starts and stops nodes periodically
type startStopRunner struct {
nodeRunner
}
//probabilisticRunner: starts and stops nodes in a more probabilistic pattern
type probabilisticRunner struct {
nodeRunner
}
//bootRunner: only boots up all nodes
type bootRunner struct {
nodeRunner
}
//create a new runner
func NewNodeRunner(network *Network, runnerId string, nodeCount int) {
var runner NodeRunner
//init properties
nr := nodeRunner{
nodeCount: nodeCount,
network: network,
}
//create stop channel
quit = make(chan struct{}, 1)
//instantiate specific runner
switch runnerId {
case "startStop":
runner = &startStopRunner{nr}
case "probabilistic":
runner = &probabilisticRunner{nr}
case "boot":
runner = &bootRunner{nr}
default:
runner = nil
}
if runner == nil {
panic("No runner assigned")
}
//setup HHTP Routes
nr.setupRoutes(runner)
//start HTTP endpoint
http.ListenAndServe(":8889", nil)
}
//setup HTTP endpoints for a runner
//available routes are:
//* `runSim`: run a node simulation
//* `stopSim`: stop the node simulation
func (r *nodeRunner) setupRoutes(runner NodeRunner) {
//runSim
http.HandleFunc("/runSim", func(w http.ResponseWriter, req *http.Request) {
log.Info("Starting node simulation...")
go runner.Run()
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.WriteHeader(http.StatusOK)
})
//stopSim
http.HandleFunc("/stopSim", func(w http.ResponseWriter, req *http.Request) {
log.Info("Stopping node simulation...")
r.stopSim()
r.network.StopAll()
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.WriteHeader(http.StatusOK)
})
}
//stopping the node simulation is just sending an empty signal to the channel
func (r nodeRunner) stopSim() {
quit <- struct{}{}
}
//the bootRunner only starts up all nodes and doesn't do anything else
func (r *bootRunner) Run() {
r.nodeRunner.connectNodesInRing()
}
//the startStopRunner first boots all nodes in a ring,
//then starts and stops a (randomly selected) node in a periodic interval
func (r *startStopRunner) Run() {
nodes := r.nodeRunner.connectNodesInRing()
net := r.nodeRunner.network
for range time.Tick(10 * time.Second) {
select {
case <-quit:
log.Info("Terminating simulation loop")
return
default:
}
id := nodes[rand.Intn(len(nodes))]
go func() {
log.Info("stopping node", "id", id)
if err := net.Stop(id); err != nil {
log.Error("error stopping node", "id", id, "err", err)
return
}
time.Sleep(3 * time.Second)
log.Debug("starting node", "id", id)
if err := net.Start(id); err != nil {
log.Error("error starting node", "id", id, "err", err)
return
}
}()
}
}
//the probabilisticRunner has a more probabilistic pattern (can be improved of course):
//nodes are connected in a ring, then selects a varying number of random nodes
//stops and starts them in random intervals, and continues the loop
func (r *probabilisticRunner) Run() {
nodes := r.nodeRunner.connectNodesInRing()
net := r.nodeRunner.network
for {
select {
case <-quit:
log.Info("Terminating simulation loop")
return
default:
}
var lowid, highid int
var wg sync.WaitGroup
randWait := rand.Intn(5000) + 1000
rand1 := rand.Intn(9)
rand2 := rand.Intn(9)
if rand1 < rand2 {
lowid = rand1
highid = rand2
} else if rand1 > rand2 {
highid = rand1
lowid = rand2
} else {
if rand1 == 0 {
rand2 = 9
} else if rand1 == 9 {
rand1 = 0
}
lowid = rand1
highid = rand2
}
var steps = highid - lowid
wg.Add(steps)
for i := lowid; i < highid; i++ {
select {
case <-quit:
log.Info("Terminating simulation loop")
return
default:
}
log.Debug(fmt.Sprintf("node %v shutting down", nodes[i]))
net.Stop(nodes[i])
go func(id discover.NodeID) {
time.Sleep(time.Duration(randWait) * time.Millisecond)
net.Start(id)
wg.Done()
}(nodes[i])
time.Sleep(time.Duration(randWait) * time.Millisecond)
}
wg.Wait()
}
}
//connect nodeCount number of nodes in a ring
func (r *nodeRunner) connectNodesInRing() []discover.NodeID {
ids := make([]discover.NodeID, r.nodeCount)
net := r.network
for i := 0; i < r.nodeCount; i++ {
node, err := net.NewNode()
if err != nil {
panic(err.Error())
}
ids[i] = node.ID()
}
for _, id := range ids {
if err := net.Start(id); err != nil {
panic(err.Error())
}
log.Debug(fmt.Sprintf("node %v starting up", id))
}
for i, id := range ids {
var peerID discover.NodeID
//first node connects with last one
if i == 0 {
peerID = ids[len(ids)-1]
} else {
//every other one connects with previous
peerID = ids[i-1]
}
if err := net.Connect(id, peerID); err != nil {
panic(err.Error())
}
}
return ids
}