chore: script for creating local test fleet

This commit is contained in:
Richard Ramos 2022-08-25 15:32:37 -04:00 committed by Iuri Matias
parent a8eed304c0
commit d985c4b0b9
6 changed files with 422 additions and 0 deletions

View File

@ -0,0 +1,42 @@
# Create a local fleet for tests
## Installation
The following software is required:
- `jq`
- `docker`
- `docker-compose`
- `qrencode`
Be sure that NAT is configured in the router you're connected to, since this uses the external IP address to connect the peers
## Usage
```
go run main.go
```
By default it will attempt to create 1 bootnode, 1 mailserver and 1 whisper node. You can control the number
of mailservers and whisper nodes by using the flags `--mailservers N` and `--whisper N`, where `N` is the
number of nodes to create.
**WARNING** this will overwrite the following files:
- `fleets.json`
- `vendor/status-go/services/mailservers/fleet.go`
The program will create the required nodes, and modify the fleet files. Afterwards you need to rebuild
`status-go` and `status-desktop`, so it uses the local fleet nodes instead of the default `eth.prod` nodes.
Once you're done with the fleet, press `CTRL+C` to shutdown the fleet
## Simulating network conditions
With https://github.com/tylertreat/comcast, you can test common network problems. Use the following command to setup some rules:
```
comcast --device=wlp2s0 --latency=250 --target-bw=1000 --default-bw=10000 --packet-loss=10% --target-addr=your_ip_address --target-proto=tcp,udp --target-port=30310:30320
```
`latency` is specified in milliseconds, `target-bw` and `default-bw` are in Kbit
Replace the `device` flag with a valid value from `ifconfig` and `target-addr` with your IP address. After you're done with testing, use:
```
comcast --device=wlp2s0 --stop
```

View File

@ -0,0 +1,4 @@
version: "3"
services:
mailserver:
network_mode: host

View File

@ -0,0 +1,8 @@
package mailservers
func DefaultMailservers() []Mailserver {
return []Mailserver{
%MAILSERVER_LIST%
}
}

View File

@ -0,0 +1,23 @@
{
"fleets": {
"eth.prod": {
"boot": {
"test-bootnode": "%BOOTNODE%"
},
"mail": {
%MAILSERVER_LIST%
},
"rendezvous": {
"fake": "/ip4/127.0.0.1/tcp/12345/ethv4/16Uiu2HAmV8Hq9e3zm9TMVP4zrVHo3BjqW5D6bDVV6VQntQd687e4"
},
"whisper": {
%WHISPER_LIST%,
}
}
},
"meta": {
"hostname": "localhost",
"timestamp": "2022-08-25T00:00:23.181054",
"warning": "Local test fleet configuration"
}
}

View File

@ -0,0 +1,52 @@
#!/usr/bin/env bash
set -euo pipefail
GIT_ROOT=$(cd "${BASH_SOURCE%/*}" && git rev-parse --show-toplevel)
# Settings & defaults
RPC_HOST="${RPC_HOST:-localhost}"
RPC_PORT="${RPC_PORT:-8545}"
LISTEN_PORT="${LISTEN_PORT:-30303}"
API_MODULES="${API_MODULES:-eth,web3,admin}"
MAX_PEERS="${MAX_PEERS:-50}"
DAYS_KEPT="${DAYS_KEPT-30}"
FLEET_NAME="${FLEET_NAME:-eth.prod}"
REGISTER_TOPIC="${REGISTER_TOPIC:-whispermail}"
MAIL_PASSWORD="${MAIL_PASSWORD:-status-offline-inbox}"
DATA_PATH="${DATA_PATH:-/var/tmp/status-go-mail}"
MAILSERVER_ENABLED="${MAILSERVER_ENABLED:-true}"
CONFIG_PATH="${CONFIG_PATH:-${DATA_PATH}/config.json}"
if ! [[ -x $(command -v jq) ]]; then
echo "Cannot generate config. jq utility is not installed." >&2
exit 1
fi
# Assemble the filter for changing the config JSON
JQ_FILTER_ARRAY=(
".ListenAddr = \"0.0.0.0:${LISTEN_PORT}\""
".HTTPEnabled = true"
".HTTPHost = \"${RPC_HOST}\""
".HTTPPort= ${RPC_PORT}"
".MaxPeers = ${MAX_PEERS}"
".DataDir = \"${DATA_PATH}\""
".APIModules = \"${API_MODULES}\""
".ClusterConfig.Fleet = \"${FLEET_NAME}\""
".ClusterConfig.BootNodes = [\"${BOOTNODE}\"]"
".RegisterTopics = [\"${REGISTER_TOPIC}\"]"
".WakuConfig.Enabled = true"
".WakuConfig.EnableMailServer = ${MAILSERVER_ENABLED}"
".WakuConfig.DataDir = \"${DATA_PATH}/waku\""
".WakuConfig.MailServerPassword = \"${MAIL_PASSWORD}\""
".WakuConfig.MailServerDataRetention = ${DAYS_KEPT}"
)
JQ_FILTER=$(printf " | %s" "${JQ_FILTER_ARRAY[@]}")
# make sure config destination exists
mkdir -p "${DATA_PATH}"
echo "Generating config at: ${CONFIG_PATH}"
cat "./config.json" \
| jq "${JQ_FILTER:3}" > "${CONFIG_PATH}"

293
scripts/localfleet/main.go Normal file
View File

@ -0,0 +1,293 @@
package main
import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"os"
"os/exec"
"os/signal"
"strings"
"syscall"
)
type Node struct {
enr string
name string
rpcPort int
}
type ClusterConfig struct {
Enabled bool
Fleet string
StaticNodes []string
BootNodes []string
TrustedMailServers []string
PushNotificationsServers []string
}
type Mailserver struct {
ID string
Address string
Fleet string
Version int
}
func main() {
// Only supports 1 boot node
mailserverNum := flag.Int("mailservers", 1, "number of mailservers")
whisperNum := flag.Int("whisper", 1, "number of whisper nodes")
flag.Parse()
fmt.Println("Starting a bootnode...")
bootnodeENR, err := startBootnode()
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Bootnode enr: ", bootnodeENR)
defer stopBootnode()
nodeCnt := 0
var mailservers []Node
for i := 0; i < *mailserverNum; i++ {
fmt.Println(fmt.Sprintf("Starting mailserver #%d...", i+1))
node, err := startNode(nodeCnt, bootnodeENR, true)
if err != nil {
stopNodes(mailservers)
fmt.Println("Could not start node", err)
return
}
fmt.Println(fmt.Sprintf("Mailserver #%d enr: %s", i+1, node.enr))
mailservers = append(mailservers, node)
nodeCnt++
}
var whisperNodes []Node
for i := 0; i < *whisperNum; i++ {
fmt.Println(fmt.Sprintf("Starting whisperNode #%d...", i+1))
node, err := startNode(nodeCnt, bootnodeENR, false)
if err != nil {
stopNodes(whisperNodes)
return
}
fmt.Println(fmt.Sprintf("Whisper #%d enr: %s", i+1, node.enr))
whisperNodes = append(whisperNodes, node)
nodeCnt++
}
for _, node := range append(mailservers, whisperNodes...) {
fmt.Println("Adding peers to ", node.name)
for _, peer := range append(mailservers, whisperNodes...) {
addPeer(peer.enr, node.rpcPort)
}
}
// Output config
cluster := ClusterConfig{
Enabled: true,
Fleet: "eth.prod",
BootNodes: []string{bootnodeENR},
}
for _, node := range mailservers {
cluster.TrustedMailServers = append(cluster.TrustedMailServers, node.enr)
}
for _, node := range whisperNodes {
cluster.StaticNodes = append(cluster.StaticNodes, node.enr)
}
clusterJSON, _ := json.Marshal(cluster)
fmt.Println("\nNew cluster config:\n", string(clusterJSON))
// ===============================================================================
// Replacing status-go fleets.go file
statusGoMailserverFleet := ""
mailserverStrTemplate := ` Mailserver {
ID: "%s",
Address: "%s",
Fleet: "eth.prod",
Version: 1,
},
`
for _, m := range mailservers {
statusGoMailserverFleet += fmt.Sprintf(mailserverStrTemplate, m.name, m.enr)
}
b, _ := os.ReadFile("./fleet.go.template")
statusGoMailserverFleet = strings.Replace(string(b), "%MAILSERVER_LIST%", statusGoMailserverFleet, -1)
err = os.WriteFile("../../vendor/status-go/services/mailservers/fleet.go", []byte(statusGoMailserverFleet), 0600)
if err != nil {
fmt.Println("Could not write fleet in status-go")
stopNodes(mailservers)
stopNodes(whisperNodes)
return
}
fmt.Println("\nvendor/status-go/services/mailservers/fleet.go was updated")
// =====================================================================================================
// Replacing status-desktop fleets.go file
b, _ = os.ReadFile("./fleets.json.template")
fleetsJSON := strings.Replace(string(b), "%BOOTNODE%", bootnodeENR, -1)
desktopMailserverFleet := ""
for _, m := range mailservers {
desktopMailserverFleet += fmt.Sprintf("\"%s\": \"%s\",", m.name, m.enr)
}
desktopMailserverFleet = strings.TrimSuffix(desktopMailserverFleet, ",")
fleetsJSON = strings.Replace(fleetsJSON, "%MAILSERVER_LIST%", desktopMailserverFleet, -1)
desktopWhisperFleet := ""
for _, m := range whisperNodes {
desktopWhisperFleet += fmt.Sprintf("\"%s\": \"%s\",", m.name, m.enr)
}
desktopWhisperFleet = strings.TrimSuffix(desktopWhisperFleet, ",")
fleetsJSON = strings.Replace(fleetsJSON, "%WHISPER_LIST%", desktopWhisperFleet, -1)
err = os.WriteFile("../../fleets.json", []byte(fleetsJSON), 0600)
if err != nil {
fmt.Println("Could not write fleet in status-desktop")
stopNodes(mailservers)
stopNodes(whisperNodes)
return
}
fmt.Println("fleets.json was updated")
fmt.Println("\nDONE! rebuild status-go and desktop to use this new fleet")
// Wait for a SIGINT or SIGTERM signal
fmt.Println("\n\nPress Crtl+C to shutdown nodes")
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
fmt.Println("\nReceived signal, shutting down...")
stopNodes(mailservers)
stopNodes(whisperNodes)
}
func startBootnode() (string, error) {
envVars := os.Environ()
envVars = append(envVars, "CONTAINER_NAME=test-bootnode")
envVars = append(envVars, "COMPOSE_UP_FLAGS=-f ./docker-compose.yml -f ../../../../../scripts/localfleet/docker-compose-host.yml")
e := exec.Command("make", "-C", "../../vendor/status-go", "run-bootnode-docker")
var stderr bytes.Buffer
e.Stderr = &stderr
e.Env = envVars
if err := e.Run(); err != nil {
return "", fmt.Errorf("could not start bootnode: %w, %s", err, stderr.String())
}
e = exec.Command("make", "-s", "-C", "../../vendor/status-go/_assets/compose/bootnode", "enode")
e.Env = envVars
var out bytes.Buffer
e.Stdout = &out
err := e.Run()
if err != nil {
return "", fmt.Errorf("could not obtain bootnode enr: %w", err)
}
return strings.TrimSpace(out.String()), nil
}
func stopBootnode() {
fmt.Println("Stopping bootnode...")
envVars := os.Environ()
envVars = append(envVars, "CONTAINER_NAME=test-bootnode")
e := exec.Command("make", "-C", "../../vendor/status-go/_assets/compose/bootnode", "stop")
e.Env = envVars
err := e.Run()
if err != nil {
fmt.Println(fmt.Errorf("could not stop bootnode: %w", err))
}
}
func startNode(i int, bootnodeENR string, mailserver bool) (Node, error) {
envVars := os.Environ()
name := fmt.Sprintf("%s-%d", "test-mailserver", i)
if !mailserver {
name = fmt.Sprintf("%s-%d", "test-whisper", i)
envVars = append(envVars, "MAILSERVER_ENABLED=false")
}
rpcPort := 8656 + i
envVars = append(envVars, fmt.Sprintf("RPC_HOST=%s", "0.0.0.0"))
envVars = append(envVars, fmt.Sprintf("LISTEN_PORT=%d", 30310+i))
envVars = append(envVars, fmt.Sprintf("METRICS_PORT=%d", 9191+i))
envVars = append(envVars, fmt.Sprintf("RPC_PORT=%d", rpcPort))
envVars = append(envVars, fmt.Sprintf("CONTAINER_NAME=%s", name))
envVars = append(envVars, fmt.Sprintf("DATA_PATH=/var/tmp/%s", name))
envVars = append(envVars, fmt.Sprintf("BOOTNODE=%s", strings.TrimSpace(bootnodeENR)))
envVars = append(envVars, "CONTAINER_IMG=statusteam/status-go")
envVars = append(envVars, "LOG_LEVEL=DEBUG")
envVars = append(envVars, "CONTAINER_TAG=v0.84.0")
envVars = append(envVars, "API_MODULES=eth,web3,admin,waku,wakuext")
envVars = append(envVars, "REGISTER_TOPIC=whispermail")
envVars = append(envVars, "MAIL_PASSWORD=status-offline-inbox")
e := exec.Command("./gen-config.sh")
e.Env = envVars
if err := e.Run(); err != nil {
return Node{}, fmt.Errorf("could not generate config: %w", err)
}
e = exec.Command("docker-compose", "-p", name, "-f", "../../vendor/status-go/_assets/compose/mailserver/docker-compose.yml", "-f", "./docker-compose-host.yml", "up", "-d")
e.Env = envVars
var stderr bytes.Buffer
e.Stderr = &stderr
if err := e.Run(); err != nil {
return Node{}, fmt.Errorf("could not start mailserver: %w", errors.New(stderr.String()))
}
e = exec.Command("make", "-s", "-C", "../../vendor/status-go/_assets/compose/mailserver", "enode")
e.Env = envVars
var out bytes.Buffer
e.Stdout = &out
err := e.Run()
if err != nil {
return Node{}, fmt.Errorf("could not obtain mailserver #%d enr: %w", i, err)
}
return Node{
enr: strings.Replace(strings.TrimSpace(out.String()), ":status-offline-inbox", "", -1),
name: name,
rpcPort: 8656 + i,
}, nil
}
func stopNodes(nodes []Node) {
for _, node := range nodes {
fmt.Println(fmt.Sprintf("Stopping node %s...", node.name))
envVars := os.Environ()
envVars = append(envVars, fmt.Sprintf("CONTAINER_NAME=%s", node.name))
e := exec.Command("docker-compose", "-p", node.name, "down")
e.Env = envVars
if err := e.Run(); err != nil {
fmt.Println(fmt.Errorf("could not stop node #%s: %w", node.name, err))
}
}
}
func addPeer(peerENR string, port int) {
envVars := os.Environ()
envVars = append(envVars, fmt.Sprintf("RPC_HOST=%s", "0.0.0.0"))
envVars = append(envVars, fmt.Sprintf("RPC_PORT=%d", port))
e := exec.Command("../../vendor/status-go/_assets/scripts/rpc.sh", "admin_addPeer", peerENR)
e.Env = envVars
var out bytes.Buffer
e.Stdout = &out
err := e.Run()
if err != nil {
fmt.Println("could not add peer: ", err)
}
}