Remove statusd-cli (#1231)

This commit is contained in:
Ivan Daniluk 2018-10-10 00:57:53 +03:00 committed by GitHub
parent fbd3c9ca80
commit 802197879a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 0 additions and 657 deletions

View File

@ -1,44 +0,0 @@
package main
import (
"flag"
"fmt"
"os"
)
const (
// Addr is the default statusd address to connect to.
Addr = "localhost:51515"
)
var (
addr = flag.String("addr", Addr, "set statusd address (default localhost:51515)")
)
// main is the entrypoint for the statusd command line interface.
func main() {
flag.Usage = printUsage
flag.Parse()
fmt.Printf("statusd-cli connecting statusd on '%s'\n", *addr)
// Running REPL.
repl := NewREPL(*addr)
err := repl.Run()
if err != nil {
fmt.Printf("stopped with error: %v\n", err)
os.Exit(-1)
}
}
// printUsage prints a little help for statusd-cli.
func printUsage() {
fmt.Fprintln(os.Stderr, "Usage: statusd-cli [options]")
fmt.Fprintf(os.Stderr, `
Examples:
statusd-cli -addr=<address> # connect statusd on <address>
Options:
`)
flag.PrintDefaults()
}

View File

@ -1,95 +0,0 @@
package main
import (
"bufio"
"fmt"
"net"
"os"
"strconv"
"strings"
)
// REPL implements the read-eval-print loop for the commands
// to be sent to statusd.
type REPL struct {
addr string
}
// NewREPL creates a REPL instance communicating with the
// addressed statusd.
func NewREPL(addr string) *REPL {
return &REPL{
addr: addr,
}
}
// Run operates the loop to read a command and its arguments,
// execute it via the client, and print the result.
func (r *REPL) Run() error {
var (
conn net.Conn
reader *bufio.Reader
writer *bufio.Writer
err error
)
input := bufio.NewReader(os.Stdin)
connect := true
for {
// Connect first time and after connection errors.
if connect {
conn, err = net.Dial("tcp", r.addr)
if err != nil {
return fmt.Errorf("error connecting to statusd: %v", err)
}
connect = false
reader = bufio.NewReader(conn)
writer = bufio.NewWriter(conn)
}
// Read command line.
fmt.Print(">>> ")
command, err := input.ReadString('\n')
if err != nil {
fmt.Printf("ERR %v\n", err)
continue
}
// Check for possible end.
if strings.ToLower(command) == "quit\n" {
return nil
}
// Execute on statusd.
_, err = writer.WriteString(command)
if err != nil {
fmt.Printf("ERR %v\n", err)
connect = true
continue
}
err = writer.Flush()
if err != nil {
fmt.Printf("ERR %v\n", err)
connect = true
continue
}
// Read number of expected result lines.
countStr, err := reader.ReadString('\n')
if err != nil {
fmt.Printf("ERR %v\n", err)
connect = true
continue
}
count, err := strconv.Atoi(strings.TrimSuffix(countStr, "\n"))
if err != nil {
fmt.Printf("ERR %v\n", err)
continue
}
// Read and print result lines.
for i := 0; i < count; i++ {
reply, err := reader.ReadString('\n')
if err != nil {
fmt.Printf("ERR %v\n", err)
continue
}
fmt.Print("<<< ")
fmt.Print(reply)
}
}
}

View File

@ -1,168 +0,0 @@
package debug
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"reflect"
"strconv"
"strings"
"github.com/status-im/status-go/api"
"github.com/status-im/status-go/params"
)
// command contains the result of a parsed command line and
// is able to execute it on the command set.
type command struct {
funcName string
args []interface{}
}
// newCommand parses a command line and returns the command
// for further usage.
func newCommand(commandLine string) (*command, error) {
expr, err := parser.ParseExpr(commandLine)
if err != nil {
return nil, err
}
switch expr := expr.(type) {
case *ast.CallExpr:
f, ok := expr.Fun.(*ast.Ident)
if !ok {
return nil, fmt.Errorf("invalid expression: %q", commandLine)
}
return &command{
funcName: f.Name,
args: exprsToArgs(expr.Args),
}, nil
default:
return nil, fmt.Errorf("invalid command line: %q", commandLine)
}
}
// execute calls the method on the passed command set value.
func (c *command) execute(commandSetValue reflect.Value) (replies []string, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("invalid API call: %v", r)
}
}()
method := commandSetValue.MethodByName(c.funcName)
if !method.IsValid() {
return nil, fmt.Errorf("command %q not found", c.funcName)
}
argsV := make([]reflect.Value, len(c.args))
for i, arg := range c.args {
argsV[i] = reflect.ValueOf(arg)
}
repliesV := method.Call(argsV)
replies = make([]string, len(repliesV))
for i, replyV := range repliesV {
replies[i] = fmt.Sprintf("%v", replyV)
}
return replies, nil
}
// exprsToArgs converts the argument expressions to arguments.
func exprsToArgs(exprs []ast.Expr) []interface{} {
args := make([]interface{}, len(exprs))
for i, expr := range exprs {
switch expr := expr.(type) {
case *ast.BasicLit:
switch expr.Kind {
case token.INT:
args[i], _ = strconv.ParseInt(expr.Value, 10, 64) // nolint: gas
case token.FLOAT:
args[i], _ = strconv.ParseFloat(expr.Value, 64) // nolint: gas
case token.CHAR:
args[i] = expr.Value[1]
case token.STRING:
r := strings.NewReplacer("\\n", "\n", "\\t", "\t", "\\\"", "\"")
args[i] = strings.Trim(r.Replace(expr.Value), `"`)
}
case *ast.Ident:
switch expr.Name {
case "true":
args[i] = true
case "false":
args[i] = false
default:
args[i] = expr.Name
}
default:
args[i] = fmt.Sprintf("[unknown: %#v]", expr)
}
}
return args
}
// commandSet implements the set of commands the debugger unterstands.
// In the beginning a subset of the Status API, may later grow to
// utility commands.
//
// Direct invocation of commands on the Status API sometimes sadly
// is not possible due to non-low-level arguments. Here this wrapper
// helps.
type commandSet struct {
statusBackend *api.StatusBackend
}
// newCommandSet creates the command set for the passed Status API
// instance.
func newCommandSet(statusBackend *api.StatusBackend) *commandSet {
return &commandSet{
statusBackend: statusBackend,
}
}
// StartNode loads the configuration out of the passed string and
// starts a node with it.
func (cs *commandSet) StartNode(config string) error {
nodeConfig, err := params.NewConfigFromJSON(config)
if err != nil {
return err
}
return cs.statusBackend.StartNode(nodeConfig)
}
// StopNode starts the stopped node.
func (cs *commandSet) StopNode() error {
return cs.statusBackend.StopNode()
}
// ResetChainData removes chain data from data directory.
func (cs *commandSet) ResetChainData() error {
return cs.statusBackend.ResetChainData()
}
// CallRPC calls status node via RPC.
func (cs *commandSet) CallRPC(inputJSON string) string {
return cs.statusBackend.CallRPC(inputJSON)
}
// CreateAccount creates an internal geth account.
func (cs *commandSet) CreateAccount(password string) (string, string, string, error) {
return cs.statusBackend.AccountManager().CreateAccount(password)
}
// CreateChildAccount creates a sub-account.
func (cs *commandSet) CreateChildAccount(parentAddress, password string) (string, string, error) {
return cs.statusBackend.AccountManager().CreateChildAccount(parentAddress, password)
}
// RecoverAccount re-creates the master key using the given details.
func (cs *commandSet) RecoverAccount(password, mnemonic string) (string, string, error) {
return cs.statusBackend.AccountManager().RecoverAccount(password, mnemonic)
}
// SelectAccount selects the addressed account.
func (cs *commandSet) SelectAccount(address, password string) error {
return cs.statusBackend.SelectAccount(address, password)
}
// Logout clears the Whisper identities.
func (cs *commandSet) Logout() error {
return cs.statusBackend.Logout()
}

View File

@ -1,112 +0,0 @@
package debug
import (
"bufio"
"fmt"
"net"
"reflect"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/api"
)
const (
// CLIPort is the CLI port.
CLIPort = "51515"
)
// Server provides a debug server receiving line based commands from
// a CLI via the debugging port and executing those on the Status API
// using reflection. The returned values will be rendered as
// string and returned to the CLI.
type Server struct {
commandSetValue reflect.Value
listener net.Listener
log log.Logger
}
// New creates a debug server using the passed Status API.
// It also starts the server.
func New(statusBackend *api.StatusBackend, port string) (*Server, error) {
listener, err := net.Listen("tcp", fmt.Sprintf(":%s", port)) // nolint
if err != nil {
return nil, err
}
s := Server{
commandSetValue: reflect.ValueOf(newCommandSet(statusBackend)),
listener: listener,
log: log.New("package", "status-go/cmd/statusd/debug.Server"),
}
go s.backend()
return &s, nil
}
// backend receives the commands and executes them on
// the Status API.
func (s *Server) backend() {
for {
conn, err := s.listener.Accept()
if err != nil {
s.log.Error("cannot establish debug connection", "error", err)
continue
}
go s.handleConnection(conn)
}
}
// handleConnection handles all commands of one connection.
func (s *Server) handleConnection(conn net.Conn) {
reader := bufio.NewReader(conn)
writer := bufio.NewWriter(conn)
defer func() {
if err := conn.Close(); err != nil {
s.log.Error("error while closing debug connection", "error", err)
}
}()
// Read, execute, and respond commands of a session.
for {
var (
replies []string
err error
)
command, err := s.readCommandLine(reader)
if err != nil {
replies = []string{fmt.Sprintf("cannot read command: %v", err)}
} else {
replies, err = command.execute(s.commandSetValue)
if err != nil {
replies = []string{fmt.Sprintf("cannot execute command: %v", err)}
}
}
err = s.writeReplies(writer, replies)
if err != nil {
s.log.Error("cannot write replies", "error", err)
return
}
}
}
// readCommandLine receives a command line via network and
// parses it into an executable command.
func (s *Server) readCommandLine(reader *bufio.Reader) (*command, error) {
commandLine, err := reader.ReadString('\n')
if err != nil {
return nil, err
}
return newCommand(commandLine)
}
// writeReplies sends the replies back to the CLI.
func (s *Server) writeReplies(writer *bufio.Writer, replies []string) error {
_, err := fmt.Fprintf(writer, "%d\n", len(replies))
if err != nil {
return err
}
for i, reply := range replies {
_, err = fmt.Fprintf(writer, "[%d] %s\n", i, reply)
if err != nil {
return err
}
}
return writer.Flush()
}

View File

@ -1,219 +0,0 @@
package debug_test
import (
"bufio"
"fmt"
"io/ioutil"
"net"
"os"
"strconv"
"strings"
"sync"
"testing"
"github.com/status-im/status-go/api"
"github.com/status-im/status-go/cmd/statusd/debug"
"github.com/status-im/status-go/params"
testifyAssert "github.com/stretchr/testify/assert"
)
const stopNodeCommandLine = "StopNode()"
// TestInvalidExpressions tests invalid expressions.
func TestInvalidExpressions(t *testing.T) {
assert := testifyAssert.New(t)
startDebugging(assert)
conn := connectDebug(assert)
tests := []struct {
commandLine string
replies []string
}{
{
commandLine: "",
replies: []string{"[0] cannot read command: 1:2: expected operand, found 'EOF'"},
}, {
commandLine: "1 + 1",
replies: []string{"[0] cannot read command: invalid command line: \"1 + 1\\n\""},
}, {
commandLine: "func() { panic(42) }",
replies: []string{"[0] cannot read command: invalid command line: \"func() { panic(42) }\\n\""},
}, {
commandLine: "DoesNotExist()",
replies: []string{"[0] cannot execute command: command \"DoesNotExist\" not found"},
}, {
commandLine: "node.Start()",
replies: []string{"[0] cannot read command: invalid expression: \"node.Start()\\n\""},
},
}
for _, test := range tests {
replies := sendCommandLine(assert, conn, test.commandLine)
assert.Equal(test.replies, replies)
}
}
// TestStartStopNode tests starting and stopping a node remotely.
func TestStartStopNode(t *testing.T) {
assert := testifyAssert.New(t)
configJSON, cleanup, err := mkConfigJSON("start-stop-node")
assert.NoError(err)
defer cleanup()
startDebugging(assert)
conn := connectDebug(assert)
commandLine := fmt.Sprintf("StartNode(%q)", configJSON)
replies := sendCommandLine(assert, conn, commandLine)
assert.Len(replies, 1)
assert.Equal("[0] <nil>", replies[0])
replies = sendCommandLine(assert, conn, stopNodeCommandLine)
assert.Len(replies, 1)
assert.Equal("[0] <nil>", replies[0])
}
// TestCreateAccount tests creating an account on the server.
func TestCreateAccount(t *testing.T) {
assert := testifyAssert.New(t)
configJSON, cleanup, err := mkConfigJSON("create-account")
assert.NoError(err)
defer cleanup()
startDebugging(assert)
conn := connectDebug(assert)
commandLine := fmt.Sprintf("StartNode(%q)", configJSON)
replies := sendCommandLine(assert, conn, commandLine)
assert.Len(replies, 1)
assert.Equal("[0] <nil>", replies[0])
commandLine = fmt.Sprintf("CreateAccount(%q)", "password")
replies = sendCommandLine(assert, conn, commandLine)
assert.Len(replies, 4)
assert.NotEqual("[0] <nil>", replies[0])
assert.NotEqual("[1] <nil>", replies[1])
assert.NotEqual("[2] <nil>", replies[2])
assert.Equal("[3] <nil>", replies[3])
replies = sendCommandLine(assert, conn, stopNodeCommandLine)
assert.Len(replies, 1)
assert.Equal("[0] <nil>", replies[0])
}
// TestSelectAccountLogout tests selecting an account on the server
// and logging out afterwards.
func TestSelectAccountLogout(t *testing.T) {
assert := testifyAssert.New(t)
configJSON, cleanup, err := mkConfigJSON("select-account")
assert.NoError(err)
defer cleanup()
startDebugging(assert)
conn := connectDebug(assert)
commandLine := fmt.Sprintf("StartNode(%q)", configJSON)
replies := sendCommandLine(assert, conn, commandLine)
assert.Len(replies, 1)
assert.Equal("[0] <nil>", replies[0])
commandLine = fmt.Sprintf("CreateAccount(%q)", "password")
replies = sendCommandLine(assert, conn, commandLine)
assert.Len(replies, 4)
assert.NotEqual("[0] <nil>", replies[0])
assert.NotEqual("[1] <nil>", replies[1])
assert.NotEqual("[2] <nil>", replies[2])
assert.Equal("[3] <nil>", replies[3])
address := replies[0][4:]
commandLine = fmt.Sprintf("SelectAccount(%q, %q)", address, "password")
replies = sendCommandLine(assert, conn, commandLine)
assert.Len(replies, 1)
assert.Equal("[0] <nil>", replies[0])
commandLine = "Logout()"
replies = sendCommandLine(assert, conn, commandLine)
assert.Len(replies, 1)
assert.Equal("[0] <nil>", replies[0])
replies = sendCommandLine(assert, conn, stopNodeCommandLine)
assert.Len(replies, 1)
assert.Equal("[0] <nil>", replies[0])
}
//-----
// HELPERS
//-----
var (
mu sync.Mutex
d *debug.Server
)
// startDebugging lazily creates or reuses a debug instance.
func startDebugging(assert *testifyAssert.Assertions) {
mu.Lock()
defer mu.Unlock()
if d == nil {
var err error
backend := api.NewStatusBackend()
d, err = debug.New(backend, debug.CLIPort)
assert.NoError(err)
}
}
// mkConfigJSON creates a configuration matching to
// a temporary directory and a cleanup for that directory.
func mkConfigJSON(name string) (string, func(), error) {
tmpDir, err := ioutil.TempDir(os.TempDir(), name)
if err != nil {
return "", nil, err
}
cleanup := func() {
os.RemoveAll(tmpDir) //nolint: errcheck
}
configJSON := `{
"NetworkId": ` + strconv.Itoa(params.RopstenNetworkID) + `,
"DataDir": "` + tmpDir + `",
"BackupDisabledDataDir": "` + tmpDir + `",
"KeyStoreDir": "` + tmpDir + `/keystore",
"LogLevel": "INFO",
"NoDiscovery": true,
"RPCEnabled": true
}`
return configJSON, cleanup, nil
}
// connectDebug connects to the debug instance.
func connectDebug(assert *testifyAssert.Assertions) net.Conn {
conn, err := net.Dial("tcp", ":51515")
assert.NoError(err)
return conn
}
// sendCommandLine sends a command line via the passed connection.
// nolint: interfacer
func sendCommandLine(assert *testifyAssert.Assertions, conn net.Conn, commandLine string) []string {
reader := bufio.NewReader(conn)
writer := bufio.NewWriter(conn)
_, err := writer.WriteString(commandLine + "\n")
assert.NoError(err)
err = writer.Flush()
assert.NoError(err)
countStr, err := reader.ReadString('\n')
assert.NoError(err)
count, err := strconv.Atoi(strings.TrimSuffix(countStr, "\n"))
assert.NoError(err)
replies := make([]string, count)
for i := 0; i < count; i++ {
reply, err := reader.ReadString('\n')
assert.NoError(err)
replies[i] = strings.TrimSuffix(reply, "\n")
}
return replies
}

View File

@ -16,7 +16,6 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
gethmetrics "github.com/ethereum/go-ethereum/metrics" gethmetrics "github.com/ethereum/go-ethereum/metrics"
"github.com/status-im/status-go/api" "github.com/status-im/status-go/api"
"github.com/status-im/status-go/cmd/statusd/debug"
"github.com/status-im/status-go/logutils" "github.com/status-im/status-go/logutils"
nodemetrics "github.com/status-im/status-go/metrics/node" nodemetrics "github.com/status-im/status-go/metrics/node"
"github.com/status-im/status-go/node" "github.com/status-im/status-go/node"
@ -37,8 +36,6 @@ var (
configFiles configFlags configFiles configFlags
logLevel = flag.String("log", "", `Log level, one of: "ERROR", "WARN", "INFO", "DEBUG", and "TRACE"`) logLevel = flag.String("log", "", `Log level, one of: "ERROR", "WARN", "INFO", "DEBUG", and "TRACE"`)
logWithoutColors = flag.Bool("log-without-color", false, "Disables log colors") logWithoutColors = flag.Bool("log-without-color", false, "Disables log colors")
cliEnabled = flag.Bool("cli", false, "Enable debugging CLI server")
cliPort = flag.String("cli-port", debug.CLIPort, "CLI server's listening port")
pprofEnabled = flag.Bool("pprof", false, "Enable runtime profiling via pprof") pprofEnabled = flag.Bool("pprof", false, "Enable runtime profiling via pprof")
pprofPort = flag.Int("pprof-port", 52525, "Port for runtime profiling via pprof") pprofPort = flag.Int("pprof-port", 52525, "Port for runtime profiling via pprof")
version = flag.Bool("version", false, "Print version and dump configuration") version = flag.Bool("version", false, "Print version and dump configuration")
@ -129,15 +126,6 @@ func main() {
// handle interrupt signals // handle interrupt signals
interruptCh := haltOnInterruptSignal(backend.StatusNode()) interruptCh := haltOnInterruptSignal(backend.StatusNode())
// Check if debugging CLI connection shall be enabled.
if *cliEnabled {
err := startDebug(backend)
if err != nil {
logger.Error("Starting debugging CLI server failed", "error", err)
return
}
}
// Check if profiling shall be enabled. // Check if profiling shall be enabled.
if *pprofEnabled { if *pprofEnabled {
profiling.NewProfiler(*pprofPort).Go() profiling.NewProfiler(*pprofPort).Go()
@ -193,12 +181,6 @@ func setupLogging(config *params.NodeConfig) {
} }
} }
// startDebug starts the debugging API server.
func startDebug(backend *api.StatusBackend) error {
_, err := debug.New(backend, *cliPort)
return err
}
// startCollectingStats collects various stats about the node and other protocols like Whisper. // startCollectingStats collects various stats about the node and other protocols like Whisper.
func startCollectingNodeMetrics(interruptCh <-chan struct{}, statusNode *node.StatusNode) { func startCollectingNodeMetrics(interruptCh <-chan struct{}, statusNode *node.StatusNode) {
logger.Info("Starting collecting node metrics") logger.Info("Starting collecting node metrics")
@ -278,7 +260,6 @@ Examples:
statusd -c ./default.json # run node with configuration specified in ./default.json file statusd -c ./default.json # run node with configuration specified in ./default.json file
statusd -c ./default.json -c ./standalone.json # run node with configuration specified in ./default.json file, after merging ./standalone.json file statusd -c ./default.json -c ./standalone.json # run node with configuration specified in ./default.json file, after merging ./standalone.json file
statusd -c ./default.json -metrics # run node with configuration specified in ./default.json file, and expose ethereum metrics with debug_metrics jsonrpc call statusd -c ./default.json -metrics # run node with configuration specified in ./default.json file, and expose ethereum metrics with debug_metrics jsonrpc call
statusd -c ./default.json -cli # run node with configuration specified in ./default.json file, and enable connection by statusd-cli on default port
Options: Options:
` `