cmd/puppeth: concurrent server dials and health checks

This commit is contained in:
Péter Szilágyi 2017-10-19 14:40:43 +03:00
parent 8c78449a9e
commit 7b258c9681
No known key found for this signature in database
GPG Key ID: E9AE538CEDF8293D
3 changed files with 150 additions and 101 deletions

@ -28,6 +28,7 @@ import (
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
@ -76,6 +77,7 @@ type wizard struct {
services map[string][]string // Ethereum services known to be running on servers services map[string][]string // Ethereum services known to be running on servers
in *bufio.Reader // Wrapper around stdin to allow reading user input in *bufio.Reader // Wrapper around stdin to allow reading user input
lock sync.Mutex // Lock to protect configs during concurrent service discovery
} }
// read reads a single line from stdin, trimming if from spaces. // read reads a single line from stdin, trimming if from spaces.

@ -24,6 +24,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"sync"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
@ -80,14 +81,25 @@ func (w *wizard) run() {
} else if err := json.Unmarshal(blob, &w.conf); err != nil { } else if err := json.Unmarshal(blob, &w.conf); err != nil {
log.Crit("Previous configuration corrupted", "path", w.conf.path, "err", err) log.Crit("Previous configuration corrupted", "path", w.conf.path, "err", err)
} else { } else {
// Dial all previously known servers concurrently
var pend sync.WaitGroup
for server, pubkey := range w.conf.Servers { for server, pubkey := range w.conf.Servers {
pend.Add(1)
go func(server string, pubkey []byte) {
defer pend.Done()
log.Info("Dialing previously configured server", "server", server) log.Info("Dialing previously configured server", "server", server)
client, err := dial(server, pubkey) client, err := dial(server, pubkey)
if err != nil { if err != nil {
log.Error("Previous server unreachable", "server", server, "err", err) log.Error("Previous server unreachable", "server", server, "err", err)
} }
w.lock.Lock()
w.servers[server] = client w.servers[server] = client
w.lock.Unlock()
}(server, pubkey)
} }
pend.Wait()
w.networkStats() w.networkStats()
} }
// Basics done, loop ad infinitum about what to do // Basics done, loop ad infinitum about what to do

@ -21,6 +21,7 @@ import (
"os" "os"
"sort" "sort"
"strings" "strings"
"sync"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
@ -34,13 +35,51 @@ func (w *wizard) networkStats() {
log.Error("No remote machines to gather stats from") log.Error("No remote machines to gather stats from")
return return
} }
protips := new(protips) // Clear out some previous configs to refill from current scan
w.conf.ethstats = ""
w.conf.bootFull = w.conf.bootFull[:0]
w.conf.bootLight = w.conf.bootLight[:0]
// Iterate over all the specified hosts and check their status // Iterate over all the specified hosts and check their status
stats := make(serverStats) var pend sync.WaitGroup
stats := make(serverStats)
for server, pubkey := range w.conf.Servers { for server, pubkey := range w.conf.Servers {
client := w.servers[server] pend.Add(1)
// Gather the service stats for each server concurrently
go func(server string, pubkey []byte) {
defer pend.Done()
stat := w.gatherStats(server, pubkey, w.servers[server])
// All status checks complete, report and check next server
w.lock.Lock()
defer w.lock.Unlock()
delete(w.services, server)
for service := range stat.services {
w.services[server] = append(w.services[server], service)
}
stats[server] = stat
}(server, pubkey)
}
pend.Wait()
// Print any collected stats and return
stats.render()
}
// gatherStats gathers service statistics for a particular remote server.
func (w *wizard) gatherStats(server string, pubkey []byte, client *sshClient) *serverStat {
// Gather some global stats to feed into the wizard
var (
genesis string
ethstats string
bootFull []string
bootLight []string
)
// Ensure a valid SSH connection to the remote server
logger := log.New("server", server) logger := log.New("server", server)
logger.Info("Starting remote server health-check") logger.Info("Starting remote server health-check")
@ -48,14 +87,12 @@ func (w *wizard) networkStats() {
address: client.address, address: client.address,
services: make(map[string]map[string]string), services: make(map[string]map[string]string),
} }
stats[client.server] = stat
if client == nil { if client == nil {
conn, err := dial(server, pubkey) conn, err := dial(server, pubkey)
if err != nil { if err != nil {
logger.Error("Failed to establish remote connection", "err", err) logger.Error("Failed to establish remote connection", "err", err)
stat.failure = err.Error() stat.failure = err.Error()
continue return stat
} }
client = conn client = conn
} }
@ -75,7 +112,7 @@ func (w *wizard) networkStats() {
} }
} else { } else {
stat.services["ethstats"] = infos.Report() stat.services["ethstats"] = infos.Report()
protips.ethstats = infos.config ethstats = infos.config
} }
logger.Debug("Checking for bootnode availability") logger.Debug("Checking for bootnode availability")
if infos, err := checkNode(client, w.network, true); err != nil { if infos, err := checkNode(client, w.network, true); err != nil {
@ -85,10 +122,10 @@ func (w *wizard) networkStats() {
} else { } else {
stat.services["bootnode"] = infos.Report() stat.services["bootnode"] = infos.Report()
protips.genesis = string(infos.genesis) genesis = string(infos.genesis)
protips.bootFull = append(protips.bootFull, infos.enodeFull) bootFull = append(bootFull, infos.enodeFull)
if infos.enodeLight != "" { if infos.enodeLight != "" {
protips.bootLight = append(protips.bootLight, infos.enodeLight) bootLight = append(bootLight, infos.enodeLight)
} }
} }
logger.Debug("Checking for sealnode availability") logger.Debug("Checking for sealnode availability")
@ -98,7 +135,7 @@ func (w *wizard) networkStats() {
} }
} else { } else {
stat.services["sealnode"] = infos.Report() stat.services["sealnode"] = infos.Report()
protips.genesis = string(infos.genesis) genesis = string(infos.genesis)
} }
logger.Debug("Checking for faucet availability") logger.Debug("Checking for faucet availability")
if infos, err := checkFaucet(client, w.network); err != nil { if infos, err := checkFaucet(client, w.network); err != nil {
@ -116,30 +153,25 @@ func (w *wizard) networkStats() {
} else { } else {
stat.services["dashboard"] = infos.Report() stat.services["dashboard"] = infos.Report()
} }
// All status checks complete, report and check next server // Feed and newly discovered information into the wizard
delete(w.services, server) w.lock.Lock()
for service := range stat.services { defer w.lock.Unlock()
w.services[server] = append(w.services[server], service)
} if genesis != "" && w.conf.genesis == nil {
} g := new(core.Genesis)
// If a genesis block was found, load it into our configs if err := json.Unmarshal([]byte(genesis), g); err != nil {
if protips.genesis != "" && w.conf.genesis == nil {
genesis := new(core.Genesis)
if err := json.Unmarshal([]byte(protips.genesis), genesis); err != nil {
log.Error("Failed to parse remote genesis", "err", err) log.Error("Failed to parse remote genesis", "err", err)
} else { } else {
w.conf.genesis = genesis w.conf.genesis = g
protips.network = genesis.Config.ChainId.Int64()
} }
} }
if protips.ethstats != "" { if ethstats != "" {
w.conf.ethstats = protips.ethstats w.conf.ethstats = ethstats
} }
w.conf.bootFull = protips.bootFull w.conf.bootFull = append(w.conf.bootFull, bootFull...)
w.conf.bootLight = protips.bootLight w.conf.bootLight = append(w.conf.bootLight, bootLight...)
// Print any collected stats and return return stat
stats.render()
} }
// serverStat is a collection of service configuration parameters and health // serverStat is a collection of service configuration parameters and health
@ -205,6 +237,9 @@ func (stats serverStats) render() {
} }
sort.Strings(services) sort.Strings(services)
if len(services) == 0 {
table.Append([]string{server, stats[server].address, "", "", ""})
}
for j, service := range services { for j, service := range services {
// Add an empty line between all services // Add an empty line between all services
if j > 0 { if j > 0 {