Merge pull request #108 from ethersphere/network-testing-framework-http

p2p/simulations: Use a single network for the HTTP API
This commit is contained in:
Viktor Trón 2017-06-28 16:17:01 +02:00 committed by GitHub
commit 3946fc3bb3
7 changed files with 208 additions and 463 deletions

View file

@ -140,13 +140,14 @@ func (s *SimAdapter) GetNode(id discover.NodeID) (*SimNode, bool) {
// It implements the p2p.Server interface so it can be used transparently // It implements the p2p.Server interface so it can be used transparently
// by the underlying service. // by the underlying service.
type SimNode struct { type SimNode struct {
lock sync.RWMutex lock sync.RWMutex
ID discover.NodeID ID discover.NodeID
config *NodeConfig config *NodeConfig
adapter *SimAdapter adapter *SimAdapter
node *node.Node node *node.Node
running map[string]node.Service running map[string]node.Service
client *rpc.Client client *rpc.Client
registerOnce sync.Once
} }
// Addr returns the node's discovery address // Addr returns the node's discovery address
@ -210,8 +211,6 @@ func (self *SimNode) Snapshots() (map[string][]byte, error) {
// Start starts the RPC handler and the underlying service // Start starts the RPC handler and the underlying service
func (self *SimNode) Start(snapshots map[string][]byte) error { func (self *SimNode) Start(snapshots map[string][]byte) error {
self.lock.Lock()
defer self.lock.Unlock()
newService := func(name string) func(ctx *node.ServiceContext) (node.Service, error) { newService := func(name string) func(ctx *node.ServiceContext) (node.Service, error) {
return func(nodeCtx *node.ServiceContext) (node.Service, error) { return func(nodeCtx *node.ServiceContext) (node.Service, error) {
ctx := &ServiceContext{ ctx := &ServiceContext{
@ -232,10 +231,17 @@ func (self *SimNode) Start(snapshots map[string][]byte) error {
} }
} }
for _, name := range self.config.Services { var regErr error
if err := self.node.Register(newService(name)); err != nil { self.registerOnce.Do(func() {
return err for _, name := range self.config.Services {
if err := self.node.Register(newService(name)); err != nil {
regErr = err
return
}
} }
})
if regErr != nil {
return regErr
} }
if err := self.node.Start(); err != nil { if err := self.node.Start(); err != nil {
@ -247,7 +253,10 @@ func (self *SimNode) Start(snapshots map[string][]byte) error {
if err != nil { if err != nil {
return err return err
} }
self.lock.Lock()
self.client = rpc.DialInProc(handler) self.client = rpc.DialInProc(handler)
self.lock.Unlock()
return nil return nil
} }

View file

@ -3,22 +3,19 @@
// Here is an example of creating a 2 node network with the first node // Here is an example of creating a 2 node network with the first node
// connected to the second: // connected to the second:
// //
// $ p2psim network create // $ p2psim node create
// Created network net1
//
// $ p2psim node create net1
// Created node01 // Created node01
// //
// $ p2psim node start net1 node01 // $ p2psim node start node01
// Started node01 // Started node01
// //
// $ p2psim node create net1 // $ p2psim node create
// Created node02 // Created node02
// //
// $ p2psim node start net1 node02 // $ p2psim node start node02
// Started node02 // Started node02
// //
// $ p2psim node connect net1 node01 node02 // $ p2psim node connect node01 node02
// Connected node01 to node02 // Connected node01 to node02
// //
package main package main
@ -26,7 +23,6 @@ package main
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -42,10 +38,7 @@ import (
"gopkg.in/urfave/cli.v1" "gopkg.in/urfave/cli.v1"
) )
var ( var client *simulations.Client
client *simulations.Client
networkID string
)
func main() { func main() {
app := cli.NewApp() app := cli.NewApp()
@ -64,78 +57,29 @@ func main() {
} }
app.Commands = []cli.Command{ app.Commands = []cli.Command{
{ {
Name: "network", Name: "show",
Usage: "manage simulation networks", Usage: "show network information",
Action: listNetworks, Action: showNetwork,
Subcommands: []cli.Command{ },
{ {
Name: "list", Name: "events",
Usage: "list networks", Usage: "stream network events",
Action: listNetworks, Action: streamNetwork,
}, },
{ {
Name: "create", Name: "snapshot",
Usage: "create a network", Usage: "create a network snapshot to stdout",
Action: createNetwork, Action: createSnapshot,
Flags: []cli.Flag{ },
cli.StringFlag{ {
Name: "id", Name: "load",
Value: "", Usage: "load a network snapshot from stdin",
Usage: "network ID", Action: loadSnapshot,
},
cli.StringFlag{
Name: "default-service",
Value: "",
Usage: "default service",
},
},
},
{
Name: "show",
ArgsUsage: "<network>",
Usage: "show network information",
Action: showNetwork,
},
{
Name: "events",
ArgsUsage: "<network>",
Usage: "stream network events",
Action: streamNetwork,
},
{
Name: "snapshot",
ArgsUsage: "<network>",
Usage: "create a network snapshot to stdout",
Action: createSnapshot,
},
{
Name: "load",
ArgsUsage: "<network>",
Usage: "load a network snapshot from stdin",
Action: loadSnapshot,
},
},
}, },
{ {
Name: "node", Name: "node",
Usage: "manage simulation nodes", Usage: "manage simulation nodes",
Action: listNodes, Action: listNodes,
Flags: []cli.Flag{
cli.StringFlag{
Name: "network",
Usage: "simulation network",
},
},
Before: func(ctx *cli.Context) error {
networkID = ctx.GlobalString("network")
if networkID == "" {
networkID = os.Getenv("P2PSIM_NETWORK")
}
if networkID == "" {
return errors.New("missing network, set with --network or P2PSIM_NETWORK")
}
return nil
},
Subcommands: []cli.Command{ Subcommands: []cli.Command{
{ {
Name: "list", Name: "list",
@ -212,65 +156,27 @@ func main() {
app.Run(os.Args) app.Run(os.Args)
} }
func listNetworks(ctx *cli.Context) error {
if len(ctx.Args()) != 0 {
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
networks, err := client.GetNetworks()
if err != nil {
return err
}
w := tabwriter.NewWriter(ctx.App.Writer, 1, 2, 2, ' ', 0)
defer w.Flush()
fmt.Fprintf(w, "ID\tNODES\tCONNS\n")
for _, network := range networks {
fmt.Fprintf(w, "%s\t%d\t%d\n", network.ID, len(network.Nodes), len(network.Conns))
}
return nil
}
func createNetwork(ctx *cli.Context) error {
if len(ctx.Args()) != 0 {
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
config := &simulations.NetworkConfig{
ID: ctx.String("id"),
DefaultService: ctx.String("default-service"),
}
network, err := client.CreateNetwork(config)
if err != nil {
return err
}
fmt.Fprintln(ctx.App.Writer, "Created network", network.ID)
return nil
}
func showNetwork(ctx *cli.Context) error { func showNetwork(ctx *cli.Context) error {
args := ctx.Args() if len(ctx.Args()) != 0 {
if len(args) != 1 {
return cli.ShowCommandHelp(ctx, ctx.Command.Name) return cli.ShowCommandHelp(ctx, ctx.Command.Name)
} }
networkID := args[0] network, err := client.GetNetwork()
network, err := client.GetNetwork(networkID)
if err != nil { if err != nil {
return err return err
} }
w := tabwriter.NewWriter(ctx.App.Writer, 1, 2, 2, ' ', 0) w := tabwriter.NewWriter(ctx.App.Writer, 1, 2, 2, ' ', 0)
defer w.Flush() defer w.Flush()
fmt.Fprintf(w, "ID\t%s\n", network.ID)
fmt.Fprintf(w, "NODES\t%d\n", len(network.Nodes)) fmt.Fprintf(w, "NODES\t%d\n", len(network.Nodes))
fmt.Fprintf(w, "CONNS\t%d\n", len(network.Conns)) fmt.Fprintf(w, "CONNS\t%d\n", len(network.Conns))
return nil return nil
} }
func streamNetwork(ctx *cli.Context) error { func streamNetwork(ctx *cli.Context) error {
args := ctx.Args() if len(ctx.Args()) != 0 {
if len(args) != 1 {
return cli.ShowCommandHelp(ctx, ctx.Command.Name) return cli.ShowCommandHelp(ctx, ctx.Command.Name)
} }
networkID := args[0]
events := make(chan *simulations.Event) events := make(chan *simulations.Event)
sub, err := client.SubscribeNetwork(networkID, events) sub, err := client.SubscribeNetwork(events)
if err != nil { if err != nil {
return err return err
} }
@ -289,12 +195,10 @@ func streamNetwork(ctx *cli.Context) error {
} }
func createSnapshot(ctx *cli.Context) error { func createSnapshot(ctx *cli.Context) error {
args := ctx.Args() if len(ctx.Args()) != 0 {
if len(args) != 1 {
return cli.ShowCommandHelp(ctx, ctx.Command.Name) return cli.ShowCommandHelp(ctx, ctx.Command.Name)
} }
networkID := args[0] snap, err := client.CreateSnapshot()
snap, err := client.CreateSnapshot(networkID)
if err != nil { if err != nil {
return err return err
} }
@ -302,23 +206,21 @@ func createSnapshot(ctx *cli.Context) error {
} }
func loadSnapshot(ctx *cli.Context) error { func loadSnapshot(ctx *cli.Context) error {
args := ctx.Args() if len(ctx.Args()) != 0 {
if len(args) != 1 {
return cli.ShowCommandHelp(ctx, ctx.Command.Name) return cli.ShowCommandHelp(ctx, ctx.Command.Name)
} }
networkID := args[0]
snap := &simulations.Snapshot{} snap := &simulations.Snapshot{}
if err := json.NewDecoder(os.Stdin).Decode(snap); err != nil { if err := json.NewDecoder(os.Stdin).Decode(snap); err != nil {
return err return err
} }
return client.LoadSnapshot(networkID, snap) return client.LoadSnapshot(snap)
} }
func listNodes(ctx *cli.Context) error { func listNodes(ctx *cli.Context) error {
if len(ctx.Args()) != 0 { if len(ctx.Args()) != 0 {
return cli.ShowCommandHelp(ctx, ctx.Command.Name) return cli.ShowCommandHelp(ctx, ctx.Command.Name)
} }
nodes, err := client.GetNodes(networkID) nodes, err := client.GetNodes()
if err != nil { if err != nil {
return err return err
} }
@ -357,7 +259,7 @@ func createNode(ctx *cli.Context) error {
if services := ctx.String("services"); services != "" { if services := ctx.String("services"); services != "" {
config.Services = strings.Split(services, ",") config.Services = strings.Split(services, ",")
} }
node, err := client.CreateNode(networkID, config) node, err := client.CreateNode(config)
if err != nil { if err != nil {
return err return err
} }
@ -371,7 +273,7 @@ func showNode(ctx *cli.Context) error {
return cli.ShowCommandHelp(ctx, ctx.Command.Name) return cli.ShowCommandHelp(ctx, ctx.Command.Name)
} }
nodeName := args[0] nodeName := args[0]
node, err := client.GetNode(networkID, nodeName) node, err := client.GetNode(nodeName)
if err != nil { if err != nil {
return err return err
} }
@ -396,7 +298,7 @@ func startNode(ctx *cli.Context) error {
return cli.ShowCommandHelp(ctx, ctx.Command.Name) return cli.ShowCommandHelp(ctx, ctx.Command.Name)
} }
nodeName := args[0] nodeName := args[0]
if err := client.StartNode(networkID, nodeName); err != nil { if err := client.StartNode(nodeName); err != nil {
return err return err
} }
fmt.Fprintln(ctx.App.Writer, "Started", nodeName) fmt.Fprintln(ctx.App.Writer, "Started", nodeName)
@ -409,7 +311,7 @@ func stopNode(ctx *cli.Context) error {
return cli.ShowCommandHelp(ctx, ctx.Command.Name) return cli.ShowCommandHelp(ctx, ctx.Command.Name)
} }
nodeName := args[0] nodeName := args[0]
if err := client.StopNode(networkID, nodeName); err != nil { if err := client.StopNode(nodeName); err != nil {
return err return err
} }
fmt.Fprintln(ctx.App.Writer, "Stopped", nodeName) fmt.Fprintln(ctx.App.Writer, "Stopped", nodeName)
@ -423,7 +325,7 @@ func connectNode(ctx *cli.Context) error {
} }
nodeName := args[0] nodeName := args[0]
peerName := args[1] peerName := args[1]
if err := client.ConnectNode(networkID, nodeName, peerName); err != nil { if err := client.ConnectNode(nodeName, peerName); err != nil {
return err return err
} }
fmt.Fprintln(ctx.App.Writer, "Connected", nodeName, "to", peerName) fmt.Fprintln(ctx.App.Writer, "Connected", nodeName, "to", peerName)
@ -437,7 +339,7 @@ func disconnectNode(ctx *cli.Context) error {
} }
nodeName := args[0] nodeName := args[0]
peerName := args[1] peerName := args[1]
if err := client.DisconnectNode(networkID, nodeName, peerName); err != nil { if err := client.DisconnectNode(nodeName, peerName); err != nil {
return err return err
} }
fmt.Fprintln(ctx.App.Writer, "Disconnected", nodeName, "from", peerName) fmt.Fprintln(ctx.App.Writer, "Disconnected", nodeName, "from", peerName)
@ -451,7 +353,7 @@ func rpcNode(ctx *cli.Context) error {
} }
nodeName := args[0] nodeName := args[0]
method := args[1] method := args[1]
rpcClient, err := client.RPCClient(context.Background(), networkID, nodeName) rpcClient, err := client.RPCClient(context.Background(), nodeName)
if err != nil { if err != nil {
return err return err
} }

View file

@ -34,16 +34,16 @@ func main() {
adapters.RegisterServices(services) adapters.RegisterServices(services)
config := &simulations.ServerConfig{} adapterType := flag.String("adapter", "sim", `node adapter to use (one of "sim", "exec" or "docker")`)
adapter := flag.String("adapter", "sim", `node adapter to use (one of "sim", "exec" or "docker")`)
flag.Parse() flag.Parse()
switch *adapter { var adapter adapters.NodeAdapter
switch *adapterType {
case "sim": case "sim":
log.Info("using sim adapter") log.Info("using sim adapter")
config.NewAdapter = func() adapters.NodeAdapter { return adapters.NewSimAdapter(services) } adapter = adapters.NewSimAdapter(services)
case "exec": case "exec":
tmpdir, err := ioutil.TempDir("", "p2p-example") tmpdir, err := ioutil.TempDir("", "p2p-example")
@ -52,22 +52,26 @@ func main() {
} }
defer os.RemoveAll(tmpdir) defer os.RemoveAll(tmpdir)
log.Info("using exec adapter", "tmpdir", tmpdir) log.Info("using exec adapter", "tmpdir", tmpdir)
config.NewAdapter = func() adapters.NodeAdapter { return adapters.NewExecAdapter(tmpdir) } adapter = adapters.NewExecAdapter(tmpdir)
case "docker": case "docker":
log.Info("using docker adapter") log.Info("using docker adapter")
adapter, err := adapters.NewDockerAdapter() var err error
adapter, err = adapters.NewDockerAdapter()
if err != nil { if err != nil {
log.Crit("error creating docker adapter", "err", err) log.Crit("error creating docker adapter", "err", err)
} }
config.NewAdapter = func() adapters.NodeAdapter { return adapter }
default: default:
log.Crit(fmt.Sprintf("unknown node adapter %q", *adapter)) log.Crit(fmt.Sprintf("unknown node adapter %q", *adapterType))
} }
log.Info("starting simulation server on 0.0.0.0:8888...") log.Info("starting simulation server on 0.0.0.0:8888...")
if err := http.ListenAndServe(":8888", simulations.NewServer(config)); err != nil { network := simulations.NewNetwork(adapter, &simulations.NetworkConfig{
DefaultService: "ping-pong",
})
config := simulations.ServerConfig{}
if err := http.ListenAndServe(":8888", simulations.NewServer(network, config)); err != nil {
log.Crit("error starting simulation server", "err", err) log.Crit("error starting simulation server", "err", err)
} }
} }

View file

@ -9,13 +9,9 @@ main() {
fail "missing p2psim binary (you need to build p2p/simulations/cmd/p2psim)" fail "missing p2psim binary (you need to build p2p/simulations/cmd/p2psim)"
fi fi
info "creating the example network"
export P2PSIM_NETWORK="example"
p2psim network create --id "${P2PSIM_NETWORK}"
info "creating 10 nodes" info "creating 10 nodes"
for i in $(seq 1 10); do for i in $(seq 1 10); do
p2psim node create --name "$(node_name $i)" --services "ping-pong" p2psim node create --name "$(node_name $i)"
p2psim node start "$(node_name $i)" p2psim node start "$(node_name $i)"
done done

View file

@ -10,7 +10,6 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"strings" "strings"
"sync"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
@ -41,54 +40,37 @@ func NewClient(url string) *Client {
} }
} }
// GetNetworks returns a list of simulations networks // GetNetwork returns details of the network
func (c *Client) GetNetworks() ([]*Network, error) { func (c *Client) GetNetwork() (*Network, error) {
var networks []*Network
return networks, c.Get("/networks", &networks)
}
// CreateNetwork creates a new simulation network
func (c *Client) CreateNetwork(config *NetworkConfig) (*Network, error) {
network := &Network{} network := &Network{}
return network, c.Post("/networks", config, network) return network, c.Get("/", network)
} }
// GetNetwork returns details of a network // StartNetwork starts all existing nodes in the simulation network
func (c *Client) GetNetwork(networkID string) (*Network, error) { func (c *Client) StartNetwork() error {
network := &Network{} return c.Post("/start", nil, nil)
return network, c.Get(fmt.Sprintf("/networks/%s", networkID), network)
}
// StartNetwork starts all existing nodes in a simulation network
func (c *Client) StartNetwork(networkID string) error {
return c.Post(fmt.Sprintf("/networks/%s/start", networkID), nil, nil)
} }
// StopNetwork stops all existing nodes in a simulation network // StopNetwork stops all existing nodes in a simulation network
func (c *Client) StopNetwork(networkID string) error { func (c *Client) StopNetwork() error {
return c.Post(fmt.Sprintf("/networks/%s/stop", networkID), nil, nil) return c.Post("/stop", nil, nil)
}
// DeleteNetwork stops and deletes a simulation network
func (c *Client) DeleteNetwork(networkID string) error {
return c.Delete(fmt.Sprintf("/networks/%s", networkID))
} }
// CreateSnapshot creates a network snapshot // CreateSnapshot creates a network snapshot
func (c *Client) CreateSnapshot(networkID string) (*Snapshot, error) { func (c *Client) CreateSnapshot() (*Snapshot, error) {
snap := &Snapshot{} snap := &Snapshot{}
return snap, c.Get(fmt.Sprintf("/networks/%s/snapshot", networkID), snap) return snap, c.Get("/snapshot", snap)
} }
// LoadSnapshot loads a snapshot into a network // LoadSnapshot loads a snapshot into the network
func (c *Client) LoadSnapshot(networkID string, snap *Snapshot) error { func (c *Client) LoadSnapshot(snap *Snapshot) error {
return c.Post(fmt.Sprintf("/networks/%s/snapshot", networkID), snap, nil) return c.Post("/snapshot", snap, nil)
} }
// SubscribeNetwork subscribes to network events which are sent from the server // SubscribeNetwork subscribes to network events which are sent from the server
// as a server-sent-events stream // as a server-sent-events stream
func (c *Client) SubscribeNetwork(networkID string, events chan *Event) (event.Subscription, error) { func (c *Client) SubscribeNetwork(events chan *Event) (event.Subscription, error) {
req, err := http.NewRequest("GET", fmt.Sprintf("%s/networks/%s/events", c.URL, networkID), nil) req, err := http.NewRequest("GET", fmt.Sprintf("%s/events", c.URL), nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -154,48 +136,48 @@ func (c *Client) SubscribeNetwork(networkID string, events chan *Event) (event.S
return event.NewSubscription(producer), nil return event.NewSubscription(producer), nil
} }
// GetNodes returns all nodes which exist in a network // GetNodes returns all nodes which exist in the network
func (c *Client) GetNodes(networkID string) ([]*p2p.NodeInfo, error) { func (c *Client) GetNodes() ([]*p2p.NodeInfo, error) {
var nodes []*p2p.NodeInfo var nodes []*p2p.NodeInfo
return nodes, c.Get(fmt.Sprintf("/networks/%s/nodes", networkID), &nodes) return nodes, c.Get("/nodes", &nodes)
} }
// CreateNode creates a node in a network using the given configuration // CreateNode creates a node in the network using the given configuration
func (c *Client) CreateNode(networkID string, config *adapters.NodeConfig) (*p2p.NodeInfo, error) { func (c *Client) CreateNode(config *adapters.NodeConfig) (*p2p.NodeInfo, error) {
node := &p2p.NodeInfo{} node := &p2p.NodeInfo{}
return node, c.Post(fmt.Sprintf("/networks/%s/nodes", networkID), config, node) return node, c.Post("/nodes", config, node)
} }
// GetNode returns details of a node // GetNode returns details of a node
func (c *Client) GetNode(networkID, nodeID string) (*p2p.NodeInfo, error) { func (c *Client) GetNode(nodeID string) (*p2p.NodeInfo, error) {
node := &p2p.NodeInfo{} node := &p2p.NodeInfo{}
return node, c.Get(fmt.Sprintf("/networks/%s/nodes/%s", networkID, nodeID), node) return node, c.Get(fmt.Sprintf("/nodes/%s", nodeID), node)
} }
// StartNode starts a node // StartNode starts a node
func (c *Client) StartNode(networkID, nodeID string) error { func (c *Client) StartNode(nodeID string) error {
return c.Post(fmt.Sprintf("/networks/%s/nodes/%s/start", networkID, nodeID), nil, nil) return c.Post(fmt.Sprintf("/nodes/%s/start", nodeID), nil, nil)
} }
// StopNode stops a node // StopNode stops a node
func (c *Client) StopNode(networkID, nodeID string) error { func (c *Client) StopNode(nodeID string) error {
return c.Post(fmt.Sprintf("/networks/%s/nodes/%s/stop", networkID, nodeID), nil, nil) return c.Post(fmt.Sprintf("/nodes/%s/stop", nodeID), nil, nil)
} }
// ConnectNode connects a node to a peer node // ConnectNode connects a node to a peer node
func (c *Client) ConnectNode(networkID, nodeID, peerID string) error { func (c *Client) ConnectNode(nodeID, peerID string) error {
return c.Post(fmt.Sprintf("/networks/%s/nodes/%s/conn/%s", networkID, nodeID, peerID), nil, nil) return c.Post(fmt.Sprintf("/nodes/%s/conn/%s", nodeID, peerID), nil, nil)
} }
// DisconnectNode disconnects a node from a peer node // DisconnectNode disconnects a node from a peer node
func (c *Client) DisconnectNode(networkID, nodeID, peerID string) error { func (c *Client) DisconnectNode(nodeID, peerID string) error {
return c.Delete(fmt.Sprintf("/networks/%s/nodes/%s/conn/%s", networkID, nodeID, peerID)) return c.Delete(fmt.Sprintf("/nodes/%s/conn/%s", nodeID, peerID))
} }
// RPCClient returns an RPC client connected to a node // RPCClient returns an RPC client connected to a node
func (c *Client) RPCClient(ctx context.Context, networkID, nodeID string) (*rpc.Client, error) { func (c *Client) RPCClient(ctx context.Context, nodeID string) (*rpc.Client, error) {
baseURL := strings.Replace(c.URL, "http", "ws", 1) baseURL := strings.Replace(c.URL, "http", "ws", 1)
return rpc.DialWebsocket(ctx, fmt.Sprintf("%s/networks/%s/nodes/%s/rpc", baseURL, networkID, nodeID), "") return rpc.DialWebsocket(ctx, fmt.Sprintf("%s/nodes/%s/rpc", baseURL, nodeID), "")
} }
// Get performs a HTTP GET request decoding the resulting JSON response // Get performs a HTTP GET request decoding the resulting JSON response
@ -251,17 +233,9 @@ func (c *Client) Send(method, path string, in, out interface{}) error {
// ServerConfig is the configuration used to start an API server // ServerConfig is the configuration used to start an API server
type ServerConfig struct { type ServerConfig struct {
// NewAdapter is called to create a new NodeAdapter for each new
// network
NewAdapter func() adapters.NodeAdapter
// ExternalNetworks are externally defined networks to expose via the
// HTTP server
ExternalNetworks map[string]*Network
// Mocker is the function which will be called when a client sends a // Mocker is the function which will be called when a client sends a
// POST request to /networks/<netid>/mock and is expected to // POST request to /mock and is expected to generate some mock events
// generate some mock events in the network // in the network
Mocker func(*Network) Mocker func(*Network)
// In case of multiple mockers, set the default here // In case of multiple mockers, set the default here
DefaultMockerID string DefaultMockerID string
@ -269,108 +243,51 @@ type ServerConfig struct {
Mockers map[string]*MockerConfig Mockers map[string]*MockerConfig
} }
// Server is an HTTP server providing an API to create and manage simulation // Server is an HTTP server providing an API to manage a simulation network
// networks
type Server struct { type Server struct {
ServerConfig ServerConfig
router *httprouter.Router router *httprouter.Router
networks map[string]*Network network *Network
mtx sync.Mutex
} }
// NewServer returns a new simulation API server // NewServer returns a new simulation API server
func NewServer(config *ServerConfig) *Server { func NewServer(network *Network, config ServerConfig) *Server {
if config.NewAdapter == nil {
panic("NewAdapter not set")
}
s := &Server{ s := &Server{
ServerConfig: *config, ServerConfig: config,
router: httprouter.New(), router: httprouter.New(),
networks: make(map[string]*Network), network: network,
}
for name, network := range config.ExternalNetworks {
s.networks[name] = network
} }
s.OPTIONS("/networks", s.Options) s.OPTIONS("/", s.Options)
s.POST("/networks", s.CreateNetwork) s.GET("/", s.GetNetwork)
s.GET("/networks", s.GetNetworks) s.POST("/start", s.StartNetwork)
s.GET("/networks/:netid", s.GetNetwork) s.POST("/stop", s.StopNetwork)
s.POST("/networks/:netid/start", s.StartNetwork) s.GET("/events", s.StreamNetworkEvents)
s.POST("/networks/:netid/stop", s.StopNetwork) s.GET("/snapshot", s.CreateSnapshot)
s.DELETE("/networks/:netid", s.DeleteNetwork) s.POST("/snapshot", s.LoadSnapshot)
s.GET("/networks/:netid/events", s.StreamNetworkEvents) s.POST("/mock/:mockid", s.StartMocker)
s.GET("/networks/:netid/snapshot", s.CreateSnapshot) s.GET("/mock", s.GetMocker)
s.POST("/networks/:netid/snapshot", s.LoadSnapshot) s.POST("/nodes", s.CreateNode)
s.POST("/networks/:netid/mock/:mockid", s.StartMocker) s.GET("/nodes", s.GetNodes)
s.GET("/networks/:netid/mock", s.GetMocker) s.GET("/nodes/:nodeid", s.GetNode)
s.POST("/networks/:netid/nodes", s.CreateNode) s.POST("/nodes/:nodeid/start", s.StartNode)
s.GET("/networks/:netid/nodes", s.GetNodes) s.POST("/nodes/:nodeid/stop", s.StopNode)
s.GET("/networks/:netid/nodes/:nodeid", s.GetNode) s.POST("/nodes/:nodeid/conn/:peerid", s.ConnectNode)
s.POST("/networks/:netid/nodes/:nodeid/start", s.StartNode) s.DELETE("/nodes/:nodeid/conn/:peerid", s.DisconnectNode)
s.POST("/networks/:netid/nodes/:nodeid/stop", s.StopNode) s.GET("/nodes/:nodeid/rpc", s.NodeRPC)
s.POST("/networks/:netid/nodes/:nodeid/conn/:peerid", s.ConnectNode)
s.DELETE("/networks/:netid/nodes/:nodeid/conn/:peerid", s.DisconnectNode)
s.GET("/networks/:netid/nodes/:nodeid/rpc", s.NodeRPC)
return s return s
} }
// CreateNetwork creates a new simulation network // GetNetwork returns details of the network
func (s *Server) CreateNetwork(w http.ResponseWriter, req *http.Request) {
config := &NetworkConfig{}
if err := json.NewDecoder(req.Body).Decode(config); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
network, err := func() (*Network, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
if config.ID == "" {
config.ID = fmt.Sprintf("net%d", len(s.networks)+1)
}
if _, exists := s.networks[config.ID]; exists {
return nil, fmt.Errorf("network exists: %s", config.ID)
}
network := NewNetwork(s.NewAdapter(), config)
s.networks[config.ID] = network
return network, nil
}()
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
s.JSON(w, http.StatusCreated, network)
}
// GetNetworks returns a list of simulations networks
func (s *Server) GetNetworks(w http.ResponseWriter, req *http.Request) {
s.mtx.Lock()
networks := make([]*Network, 0, len(s.networks))
for _, network := range s.networks {
networks = append(networks, network)
}
s.mtx.Unlock()
s.JSON(w, http.StatusOK, networks)
}
// GetNetwork returns details of a network
func (s *Server) GetNetwork(w http.ResponseWriter, req *http.Request) { func (s *Server) GetNetwork(w http.ResponseWriter, req *http.Request) {
network := req.Context().Value("network").(*Network) s.JSON(w, http.StatusOK, s.network)
s.JSON(w, http.StatusOK, network)
} }
// StartNetwork starts all nodes in a network // StartNetwork starts all nodes in the network
func (s *Server) StartNetwork(w http.ResponseWriter, req *http.Request) { func (s *Server) StartNetwork(w http.ResponseWriter, req *http.Request) {
network := req.Context().Value("network").(*Network) if err := s.network.StartAll(); err != nil {
if err := network.StartAll(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -378,11 +295,9 @@ func (s *Server) StartNetwork(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
// StopNetwork stops all nodes in a network // StopNetwork stops all nodes in the network
func (s *Server) StopNetwork(w http.ResponseWriter, req *http.Request) { func (s *Server) StopNetwork(w http.ResponseWriter, req *http.Request) {
network := req.Context().Value("network").(*Network) if err := s.network.StopAll(); err != nil {
if err := network.StopAll(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -390,22 +305,6 @@ func (s *Server) StopNetwork(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
// DeleteNetwork stops all nodes in a network and deletes it
func (s *Server) DeleteNetwork(w http.ResponseWriter, req *http.Request) {
network := req.Context().Value("network").(*Network)
if err := network.StopAll(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
s.mtx.Lock()
delete(s.networks, network.ID)
s.mtx.Unlock()
w.WriteHeader(http.StatusOK)
}
//Get the info for a particular mocker //Get the info for a particular mocker
func (s *Server) GetMocker(w http.ResponseWriter, req *http.Request) { func (s *Server) GetMocker(w http.ResponseWriter, req *http.Request) {
m := make(map[string]string) m := make(map[string]string)
@ -418,7 +317,6 @@ func (s *Server) GetMocker(w http.ResponseWriter, req *http.Request) {
} }
func (s *Server) StartMocker(w http.ResponseWriter, req *http.Request) { func (s *Server) StartMocker(w http.ResponseWriter, req *http.Request) {
network := req.Context().Value("network").(*Network)
mockerid := req.Context().Value("mock").(string) mockerid := req.Context().Value("mock").(string)
if len(s.Mockers) == 0 { if len(s.Mockers) == 0 {
@ -436,7 +334,7 @@ func (s *Server) StartMocker(w http.ResponseWriter, req *http.Request) {
http.Error(w, "mocker not configured", http.StatusInternalServerError) http.Error(w, "mocker not configured", http.StatusInternalServerError)
return return
} }
go mocker.Mocker(network) go mocker.Mocker(s.network)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} else { } else {
http.Error(w, "invalid mockerid provided", http.StatusBadRequest) http.Error(w, "invalid mockerid provided", http.StatusBadRequest)
@ -446,10 +344,8 @@ func (s *Server) StartMocker(w http.ResponseWriter, req *http.Request) {
// StreamNetworkEvents streams network events as a server-sent-events stream // StreamNetworkEvents streams network events as a server-sent-events stream
func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) { func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) {
network := req.Context().Value("network").(*Network)
events := make(chan *Event) events := make(chan *Event)
sub := network.events.Subscribe(events) sub := s.network.events.Subscribe(events)
defer sub.Unsubscribe() defer sub.Unsubscribe()
// stop the stream if the client goes away // stop the stream if the client goes away
@ -494,9 +390,7 @@ func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) {
// CreateSnapshot creates a network snapshot // CreateSnapshot creates a network snapshot
func (s *Server) CreateSnapshot(w http.ResponseWriter, req *http.Request) { func (s *Server) CreateSnapshot(w http.ResponseWriter, req *http.Request) {
network := req.Context().Value("network").(*Network) snap, err := s.network.Snapshot()
snap, err := network.Snapshot()
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -505,28 +399,24 @@ func (s *Server) CreateSnapshot(w http.ResponseWriter, req *http.Request) {
s.JSON(w, http.StatusOK, snap) s.JSON(w, http.StatusOK, snap)
} }
// LoadSnapshot loads a snapshot into a network // LoadSnapshot loads a snapshot into the network
func (s *Server) LoadSnapshot(w http.ResponseWriter, req *http.Request) { func (s *Server) LoadSnapshot(w http.ResponseWriter, req *http.Request) {
network := req.Context().Value("network").(*Network)
snap := &Snapshot{} snap := &Snapshot{}
if err := json.NewDecoder(req.Body).Decode(snap); err != nil { if err := json.NewDecoder(req.Body).Decode(snap); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
if err := network.Load(snap); err != nil { if err := s.network.Load(snap); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
s.JSON(w, http.StatusOK, network) s.JSON(w, http.StatusOK, s.network)
} }
// CreateNode creates a node in a network using the given configuration // CreateNode creates a node in the network using the given configuration
func (s *Server) CreateNode(w http.ResponseWriter, req *http.Request) { func (s *Server) CreateNode(w http.ResponseWriter, req *http.Request) {
network := req.Context().Value("network").(*Network)
config := adapters.RandomNodeConfig() config := adapters.RandomNodeConfig()
err := json.NewDecoder(req.Body).Decode(config) err := json.NewDecoder(req.Body).Decode(config)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
@ -534,7 +424,7 @@ func (s *Server) CreateNode(w http.ResponseWriter, req *http.Request) {
return return
} }
node, err := network.NewNodeWithConfig(config) node, err := s.network.NewNodeWithConfig(config)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@ -543,11 +433,9 @@ func (s *Server) CreateNode(w http.ResponseWriter, req *http.Request) {
s.JSON(w, http.StatusCreated, node.NodeInfo()) s.JSON(w, http.StatusCreated, node.NodeInfo())
} }
// GetNodes returns all nodes which exist in a network // GetNodes returns all nodes which exist in the network
func (s *Server) GetNodes(w http.ResponseWriter, req *http.Request) { func (s *Server) GetNodes(w http.ResponseWriter, req *http.Request) {
network := req.Context().Value("network").(*Network) nodes := s.network.GetNodes()
nodes := network.GetNodes()
infos := make([]*p2p.NodeInfo, len(nodes)) infos := make([]*p2p.NodeInfo, len(nodes))
for i, node := range nodes { for i, node := range nodes {
@ -566,10 +454,9 @@ func (s *Server) GetNode(w http.ResponseWriter, req *http.Request) {
// StartNode starts a node // StartNode starts a node
func (s *Server) StartNode(w http.ResponseWriter, req *http.Request) { func (s *Server) StartNode(w http.ResponseWriter, req *http.Request) {
network := req.Context().Value("network").(*Network)
node := req.Context().Value("node").(*Node) node := req.Context().Value("node").(*Node)
if err := network.Start(node.ID()); err != nil { if err := s.network.Start(node.ID()); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -579,10 +466,9 @@ func (s *Server) StartNode(w http.ResponseWriter, req *http.Request) {
// StopNode stops a node // StopNode stops a node
func (s *Server) StopNode(w http.ResponseWriter, req *http.Request) { func (s *Server) StopNode(w http.ResponseWriter, req *http.Request) {
network := req.Context().Value("network").(*Network)
node := req.Context().Value("node").(*Node) node := req.Context().Value("node").(*Node)
if err := network.Stop(node.ID()); err != nil { if err := s.network.Stop(node.ID()); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -592,11 +478,10 @@ func (s *Server) StopNode(w http.ResponseWriter, req *http.Request) {
// ConnectNode connects a node to a peer node // ConnectNode connects a node to a peer node
func (s *Server) ConnectNode(w http.ResponseWriter, req *http.Request) { func (s *Server) ConnectNode(w http.ResponseWriter, req *http.Request) {
network := req.Context().Value("network").(*Network)
node := req.Context().Value("node").(*Node) node := req.Context().Value("node").(*Node)
peer := req.Context().Value("peer").(*Node) peer := req.Context().Value("peer").(*Node)
if err := network.Connect(node.ID(), peer.ID()); err != nil { if err := s.network.Connect(node.ID(), peer.ID()); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -606,11 +491,10 @@ func (s *Server) ConnectNode(w http.ResponseWriter, req *http.Request) {
// DisconnectNode disconnects a node from a peer node // DisconnectNode disconnects a node from a peer node
func (s *Server) DisconnectNode(w http.ResponseWriter, req *http.Request) { func (s *Server) DisconnectNode(w http.ResponseWriter, req *http.Request) {
network := req.Context().Value("network").(*Network)
node := req.Context().Value("node").(*Node) node := req.Context().Value("node").(*Node)
peer := req.Context().Value("peer").(*Node) peer := req.Context().Value("peer").(*Node)
if err := network.Disconnect(node.ID(), peer.ID()); err != nil { if err := s.network.Disconnect(node.ID(), peer.ID()); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -677,29 +561,12 @@ func (s *Server) wrapHandler(handler http.HandlerFunc) httprouter.Handle {
ctx := context.Background() ctx := context.Background()
var network *Network
if id := params.ByName("netid"); id != "" {
s.mtx.Lock()
var ok bool
network, ok = s.networks[id]
s.mtx.Unlock()
if !ok {
http.NotFound(w, req)
return
}
ctx = context.WithValue(ctx, "network", network)
}
if id := params.ByName("nodeid"); id != "" { if id := params.ByName("nodeid"); id != "" {
if network == nil {
http.NotFound(w, req)
return
}
var node *Node var node *Node
if nodeID, err := discover.HexID(id); err == nil { if nodeID, err := discover.HexID(id); err == nil {
node = network.GetNode(nodeID) node = s.network.GetNode(nodeID)
} else { } else {
node = network.GetNodeByName(id) node = s.network.GetNodeByName(id)
} }
if node == nil { if node == nil {
http.NotFound(w, req) http.NotFound(w, req)
@ -709,15 +576,11 @@ func (s *Server) wrapHandler(handler http.HandlerFunc) httprouter.Handle {
} }
if id := params.ByName("peerid"); id != "" { if id := params.ByName("peerid"); id != "" {
if network == nil {
http.NotFound(w, req)
return
}
var peer *Node var peer *Node
if peerID, err := discover.HexID(id); err == nil { if peerID, err := discover.HexID(id); err == nil {
peer = network.GetNode(peerID) peer = s.network.GetNode(peerID)
} else { } else {
peer = network.GetNodeByName(id) peer = s.network.GetNodeByName(id)
} }
if peer == nil { if peer == nil {
http.NotFound(w, req) http.NotFound(w, req)
@ -727,10 +590,6 @@ func (s *Server) wrapHandler(handler http.HandlerFunc) httprouter.Handle {
} }
if id := params.ByName("mockid"); id != "" { if id := params.ByName("mockid"); id != "" {
if network == nil {
http.NotFound(w, req)
return
}
ctx = context.WithValue(ctx, "mock", id) ctx = context.WithValue(ctx, "mock", id)
} }

View file

@ -127,54 +127,32 @@ var testServices = adapters.Services{
"test": newTestService, "test": newTestService,
} }
func testHTTPServer(t *testing.T) *httptest.Server { func testHTTPServer(t *testing.T) (*Network, *httptest.Server) {
return httptest.NewServer(NewServer(&ServerConfig{ adapter := adapters.NewSimAdapter(testServices)
NewAdapter: func() adapters.NodeAdapter { return adapters.NewSimAdapter(testServices) }, network := NewNetwork(adapter, &NetworkConfig{
})) DefaultService: "test",
})
return network, httptest.NewServer(NewServer(network, ServerConfig{}))
} }
// TestHTTPNetwork tests creating and interacting with a simulation // TestHTTPNetwork tests interacting with a simulation network using the HTTP
// network using the HTTP API // API
func TestHTTPNetwork(t *testing.T) { func TestHTTPNetwork(t *testing.T) {
// start the server // start the server
s := testHTTPServer(t) network, s := testHTTPServer(t)
defer s.Close() defer s.Close()
// create a network
client := NewClient(s.URL)
config := &NetworkConfig{
DefaultService: "test",
}
network, err := client.CreateNetwork(config)
if err != nil {
t.Fatalf("error creating network: %s", err)
}
// subscribe to events so we can check them later // subscribe to events so we can check them later
client := NewClient(s.URL)
events := make(chan *Event, 100) events := make(chan *Event, 100)
sub, err := client.SubscribeNetwork(network.ID, events) sub, err := client.SubscribeNetwork(events)
if err != nil { if err != nil {
t.Fatalf("error subscribing to network events: %s", err) t.Fatalf("error subscribing to network events: %s", err)
} }
defer sub.Unsubscribe() defer sub.Unsubscribe()
// check the network has an ID // check we can retrieve details about the network
if network.ID == "" { gotNetwork, err := client.GetNetwork()
t.Fatal("expected network.ID to be set")
}
// check the network exists
networks, err := client.GetNetworks()
if err != nil {
t.Fatalf("error getting networks: %s", err)
}
if len(networks) != 1 {
t.Fatalf("expected 1 network, got %d", len(networks))
}
if networks[0].ID != network.ID {
t.Fatalf("expected network to have ID %q, got %q", network.ID, networks[0].ID)
}
gotNetwork, err := client.GetNetwork(network.ID)
if err != nil { if err != nil {
t.Fatalf("error getting network: %s", err) t.Fatalf("error getting network: %s", err)
} }
@ -185,7 +163,7 @@ func TestHTTPNetwork(t *testing.T) {
// create 2 nodes // create 2 nodes
nodeIDs := make([]string, 2) nodeIDs := make([]string, 2)
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
node, err := client.CreateNode(network.ID, nil) node, err := client.CreateNode(nil)
if err != nil { if err != nil {
t.Fatalf("error creating node: %s", err) t.Fatalf("error creating node: %s", err)
} }
@ -193,7 +171,7 @@ func TestHTTPNetwork(t *testing.T) {
} }
// check both nodes exist // check both nodes exist
nodes, err := client.GetNodes(network.ID) nodes, err := client.GetNodes()
if err != nil { if err != nil {
t.Fatalf("error getting nodes: %s", err) t.Fatalf("error getting nodes: %s", err)
} }
@ -204,7 +182,7 @@ func TestHTTPNetwork(t *testing.T) {
if nodes[i].ID != nodeID { if nodes[i].ID != nodeID {
t.Fatalf("expected node %d to have ID %q, got %q", i, nodeID, nodes[i].ID) t.Fatalf("expected node %d to have ID %q, got %q", i, nodeID, nodes[i].ID)
} }
node, err := client.GetNode(network.ID, nodeID) node, err := client.GetNode(nodeID)
if err != nil { if err != nil {
t.Fatalf("error getting node %d: %s", i, err) t.Fatalf("error getting node %d: %s", i, err)
} }
@ -215,13 +193,13 @@ func TestHTTPNetwork(t *testing.T) {
// start both nodes // start both nodes
for _, nodeID := range nodeIDs { for _, nodeID := range nodeIDs {
if err := client.StartNode(network.ID, nodeID); err != nil { if err := client.StartNode(nodeID); err != nil {
t.Fatalf("error starting node %q: %s", nodeID, err) t.Fatalf("error starting node %q: %s", nodeID, err)
} }
} }
// connect the nodes // connect the nodes
if err := client.ConnectNode(network.ID, nodeIDs[0], nodeIDs[1]); err != nil { if err := client.ConnectNode(nodeIDs[0], nodeIDs[1]); err != nil {
t.Fatalf("error connecting nodes: %s", err) t.Fatalf("error connecting nodes: %s", err)
} }
@ -319,31 +297,27 @@ func (t *expectEvents) expect(events ...*Event) {
// TestHTTPNodeRPC tests calling RPC methods on nodes via the HTTP API // TestHTTPNodeRPC tests calling RPC methods on nodes via the HTTP API
func TestHTTPNodeRPC(t *testing.T) { func TestHTTPNodeRPC(t *testing.T) {
// start the server // start the server
s := testHTTPServer(t) _, s := testHTTPServer(t)
defer s.Close() defer s.Close()
// start a node in a network // start a node in the network
client := NewClient(s.URL) client := NewClient(s.URL)
network, err := client.CreateNetwork(&NetworkConfig{DefaultService: "test"}) node, err := client.CreateNode(nil)
if err != nil {
t.Fatalf("error creating network: %s", err)
}
node, err := client.CreateNode(network.ID, nil)
if err != nil { if err != nil {
t.Fatalf("error creating node: %s", err) t.Fatalf("error creating node: %s", err)
} }
if err := client.StartNode(network.ID, node.ID); err != nil { if err := client.StartNode(node.ID); err != nil {
t.Fatalf("error starting node: %s", err) t.Fatalf("error starting node: %s", err)
} }
// create two RPC clients // create two RPC clients
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer cancel()
rpcClient1, err := client.RPCClient(ctx, network.ID, node.ID) rpcClient1, err := client.RPCClient(ctx, node.ID)
if err != nil { if err != nil {
t.Fatalf("error getting node RPC client: %s", err) t.Fatalf("error getting node RPC client: %s", err)
} }
rpcClient2, err := client.RPCClient(ctx, network.ID, node.ID) rpcClient2, err := client.RPCClient(ctx, node.ID)
if err != nil { if err != nil {
t.Fatalf("error getting node RPC client: %s", err) t.Fatalf("error getting node RPC client: %s", err)
} }
@ -382,35 +356,31 @@ func TestHTTPNodeRPC(t *testing.T) {
// TestHTTPSnapshot tests creating and loading network snapshots // TestHTTPSnapshot tests creating and loading network snapshots
func TestHTTPSnapshot(t *testing.T) { func TestHTTPSnapshot(t *testing.T) {
// start the server // start the server
s := testHTTPServer(t) _, s := testHTTPServer(t)
defer s.Close() defer s.Close()
// create a two-node network // create a two-node network
client := NewClient(s.URL) client := NewClient(s.URL)
network, err := client.CreateNetwork(&NetworkConfig{DefaultService: "test"})
if err != nil {
t.Fatalf("error creating network: %s", err)
}
nodeCount := 2 nodeCount := 2
nodes := make([]*p2p.NodeInfo, nodeCount) nodes := make([]*p2p.NodeInfo, nodeCount)
for i := 0; i < nodeCount; i++ { for i := 0; i < nodeCount; i++ {
node, err := client.CreateNode(network.ID, nil) node, err := client.CreateNode(nil)
if err != nil { if err != nil {
t.Fatalf("error creating node: %s", err) t.Fatalf("error creating node: %s", err)
} }
if err := client.StartNode(network.ID, node.ID); err != nil { if err := client.StartNode(node.ID); err != nil {
t.Fatalf("error starting node: %s", err) t.Fatalf("error starting node: %s", err)
} }
nodes[i] = node nodes[i] = node
} }
if err := client.ConnectNode(network.ID, nodes[0].ID, nodes[1].ID); err != nil { if err := client.ConnectNode(nodes[0].ID, nodes[1].ID); err != nil {
t.Fatalf("error connecting nodes: %s", err) t.Fatalf("error connecting nodes: %s", err)
} }
// store some state in the test services // store some state in the test services
states := make([]string, nodeCount) states := make([]string, nodeCount)
for i, node := range nodes { for i, node := range nodes {
rpc, err := client.RPCClient(context.Background(), network.ID, node.ID) rpc, err := client.RPCClient(context.Background(), node.ID)
if err != nil { if err != nil {
t.Fatalf("error getting RPC client: %s", err) t.Fatalf("error getting RPC client: %s", err)
} }
@ -423,7 +393,7 @@ func TestHTTPSnapshot(t *testing.T) {
} }
// create a snapshot // create a snapshot
snap, err := client.CreateSnapshot(network.ID) snap, err := client.CreateSnapshot()
if err != nil { if err != nil {
t.Fatalf("error creating snapshot: %s", err) t.Fatalf("error creating snapshot: %s", err)
} }
@ -435,26 +405,25 @@ func TestHTTPSnapshot(t *testing.T) {
} }
// create another network // create another network
network, err = client.CreateNetwork(&NetworkConfig{DefaultService: "test"}) _, s = testHTTPServer(t)
if err != nil { defer s.Close()
t.Fatalf("error creating network: %s", err) client = NewClient(s.URL)
}
// subscribe to events so we can check them later // subscribe to events so we can check them later
events := make(chan *Event, 100) events := make(chan *Event, 100)
sub, err := client.SubscribeNetwork(network.ID, events) sub, err := client.SubscribeNetwork(events)
if err != nil { if err != nil {
t.Fatalf("error subscribing to network events: %s", err) t.Fatalf("error subscribing to network events: %s", err)
} }
defer sub.Unsubscribe() defer sub.Unsubscribe()
// load the snapshot // load the snapshot
if err := client.LoadSnapshot(network.ID, snap); err != nil { if err := client.LoadSnapshot(snap); err != nil {
t.Fatalf("error loading snapshot: %s", err) t.Fatalf("error loading snapshot: %s", err)
} }
// check the nodes and connection exists // check the nodes and connection exists
net, err := client.GetNetwork(network.ID) net, err := client.GetNetwork()
if err != nil { if err != nil {
t.Fatalf("error getting network: %s", err) t.Fatalf("error getting network: %s", err)
} }
@ -480,7 +449,7 @@ func TestHTTPSnapshot(t *testing.T) {
// check the node states were restored // check the node states were restored
for i, node := range nodes { for i, node := range nodes {
rpc, err := client.RPCClient(context.Background(), network.ID, node.ID) rpc, err := client.RPCClient(context.Background(), node.ID)
if err != nil { if err != nil {
t.Fatalf("error getting RPC client: %s", err) t.Fatalf("error getting RPC client: %s", err)
} }

View file

@ -6,6 +6,7 @@
package main package main
import ( import (
"flag"
"fmt" "fmt"
"math/rand" "math/rand"
"net/http" "net/http"
@ -22,6 +23,8 @@ import (
"github.com/ethereum/go-ethereum/swarm/network" "github.com/ethereum/go-ethereum/swarm/network"
) )
var noDiscovery = flag.Bool("no-discovery", false, "disable discovery (useful if you want to load a snapshot)")
type Simulation struct { type Simulation struct {
mtx sync.Mutex mtx sync.Mutex
stores map[discover.NodeID]*adapters.SimStateStore stores map[discover.NodeID]*adapters.SimStateStore
@ -57,6 +60,7 @@ func (s *Simulation) NewService(ctx *adapters.ServiceContext) (node.Service, err
ticker := time.NewTicker(time.Duration(kad.PruneInterval) * time.Millisecond) ticker := time.NewTicker(time.Duration(kad.PruneInterval) * time.Millisecond)
kad.Prune(ticker.C) kad.Prune(ticker.C)
hp := network.NewHiveParams() hp := network.NewHiveParams()
hp.Discovery = !*noDiscovery
hp.KeepAliveInterval = 3 * time.Second hp.KeepAliveInterval = 3 * time.Second
config := &network.BzzConfig{ config := &network.BzzConfig{
@ -94,9 +98,6 @@ func createMockers() map[string]*simulations.MockerConfig {
} }
func setupMocker(net *simulations.Network) []discover.NodeID { func setupMocker(net *simulations.Network) []discover.NodeID {
conf := net.Config()
conf.DefaultService = "overlay"
nodeCount := 30 nodeCount := 30
ids := make([]discover.NodeID, nodeCount) ids := make([]discover.NodeID, nodeCount)
for i := 0; i < nodeCount; i++ { for i := 0; i < nodeCount; i++ {
@ -195,6 +196,8 @@ func startStopMocker(net *simulations.Network) {
// var server // var server
func main() { func main() {
flag.Parse()
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
log.Root().SetHandler(log.LvlFilterHandler(log.LvlDebug, log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) log.Root().SetHandler(log.LvlFilterHandler(log.LvlDebug, log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
@ -203,17 +206,20 @@ func main() {
services := adapters.Services{ services := adapters.Services{
"overlay": s.NewService, "overlay": s.NewService,
} }
adapters.RegisterServices(services) adapter := adapters.NewSimAdapter(services)
network := simulations.NewNetwork(adapter, &simulations.NetworkConfig{
DefaultService: "overlay",
})
mockers := createMockers() mockers := createMockers()
config := &simulations.ServerConfig{ config := simulations.ServerConfig{
NewAdapter: func() adapters.NodeAdapter { return adapters.NewSimAdapter(services) },
DefaultMockerID: "randomNodes", DefaultMockerID: "randomNodes",
// DefaultMockerID: "bootNet", // DefaultMockerID: "bootNet",
Mockers: mockers, Mockers: mockers,
} }
log.Info("starting simulation server on 0.0.0.0:8888...") log.Info("starting simulation server on 0.0.0.0:8888...")
http.ListenAndServe(":8888", simulations.NewServer(config)) http.ListenAndServe(":8888", simulations.NewServer(network, config))
} }