pond: Allow to stop full nodes

This commit is contained in:
Łukasz Magiera 2019-09-17 14:03:28 +02:00
parent fc5c455cf7
commit 76177907f2
5 changed files with 86 additions and 15 deletions

View File

@ -17,9 +17,17 @@ import (
"golang.org/x/xerrors" "golang.org/x/xerrors"
) )
type NodeState int
const (
NodeUnknown = iota
NodeRunning
NodeStopped
)
type api struct { type api struct {
cmds int32 cmds int32
running map[int32]runningNode running map[int32]*runningNode
runningLk sync.Mutex runningLk sync.Mutex
genesis string genesis string
} }
@ -28,6 +36,7 @@ type nodeInfo struct {
Repo string Repo string
ID int32 ID int32
ApiPort int32 ApiPort int32
State NodeState
FullNode string // only for storage nodes FullNode string // only for storage nodes
Storage bool Storage bool
@ -80,18 +89,27 @@ func (api *api) Spawn() (nodeInfo, error) {
Repo: dir, Repo: dir,
ID: id, ID: id,
ApiPort: 2500 + id, ApiPort: 2500 + id,
State: NodeRunning,
} }
api.runningLk.Lock() api.runningLk.Lock()
api.running[id] = runningNode{ api.running[id] = &runningNode{
cmd: cmd, cmd: cmd,
meta: info, meta: info,
mux: mux, mux: mux,
stop: func() { stop: func() {
defer close(mux.stop) cmd.Process.Signal(os.Interrupt)
defer errlogfile.Close() cmd.Process.Wait()
defer logfile.Close()
api.runningLk.Lock()
api.running[id].meta.State = NodeStopped
api.runningLk.Unlock()
logfile.Close()
errlogfile.Close()
close(mux.stop)
}, },
} }
api.runningLk.Unlock() api.runningLk.Unlock()
@ -180,21 +198,30 @@ func (api *api) SpawnStorage(fullNodeRepo string) (nodeInfo, error) {
Repo: dir, Repo: dir,
ID: id, ID: id,
ApiPort: 2500 + id, ApiPort: 2500 + id,
State: NodeRunning,
FullNode: fullNodeRepo, FullNode: fullNodeRepo,
Storage: true, Storage: true,
} }
api.runningLk.Lock() api.runningLk.Lock()
api.running[id] = runningNode{ api.running[id] = &runningNode{
cmd: cmd, cmd: cmd,
meta: info, meta: info,
mux: mux, mux: mux,
stop: func() { stop: func() {
defer close(mux.stop) cmd.Process.Signal(os.Interrupt)
defer errlogfile.Close() cmd.Process.Wait()
defer logfile.Close()
api.runningLk.Lock()
api.running[id].meta.State = NodeStopped
api.runningLk.Unlock()
logfile.Close()
errlogfile.Close()
close(mux.stop)
}, },
} }
api.runningLk.Unlock() api.runningLk.Unlock()
@ -243,6 +270,19 @@ func (api *api) CreateRandomFile(size int64) (string, error) {
return tf.Name(), nil return tf.Name(), nil
} }
func (api *api) Stop(node int32) error {
api.runningLk.Lock()
nd, ok := api.running[node]
api.runningLk.Unlock()
if !ok {
return nil
}
nd.stop()
return nil
}
type client struct { type client struct {
Nodes func() []nodeInfo Nodes func() []nodeInfo
} }

View File

@ -26,8 +26,10 @@ class Address extends React.Component {
componentDidMount() { componentDidMount() {
this.refresh() this.refresh()
if(!this.props.ts) if(!this.props.ts) {
setInterval(this.refresh, 2050) let updates = setInterval(this.refresh, 2050)
this.props.client.on('close', () => clearInterval(updates))
}
} }
async refresh() { async refresh() {

View File

@ -19,9 +19,11 @@ class FullNode extends React.Component {
this.add1k = this.add1k.bind(this) this.add1k = this.add1k.bind(this)
this.explorer = this.explorer.bind(this) this.explorer = this.explorer.bind(this)
this.client = this.client.bind(this) this.client = this.client.bind(this)
this.stop = this.stop.bind(this)
this.loadInfo() this.loadInfo()
setInterval(this.loadInfo, 2050) let updates = setInterval(this.loadInfo, 2050)
this.props.client.on('close', () => clearInterval(updates))
} }
async loadInfo() { async loadInfo() {
@ -95,6 +97,10 @@ class FullNode extends React.Component {
this.props.mountWindow((onClose) => <Client onClose={onClose} node={this.props.node} client={this.props.client} pondClient={this.props.pondClient} mountWindow={this.props.mountWindow}/>) this.props.mountWindow((onClose) => <Client onClose={onClose} node={this.props.node} client={this.props.client} pondClient={this.props.pondClient} mountWindow={this.props.mountWindow}/>)
} }
async stop() {
await this.props.stop()
}
render() { render() {
let runtime = <div></div> let runtime = <div></div>
@ -165,7 +171,8 @@ class FullNode extends React.Component {
<Cristal <Cristal
title={"Node " + this.props.node.ID} title={"Node " + this.props.node.ID}
initialPosition={{x: this.props.node.ID*30, y: this.props.node.ID * 30}} initialPosition={{x: this.props.node.ID*30, y: this.props.node.ID * 30}}
initialSize={{width: 690, height: 300}} > initialSize={{width: 690, height: 300}}
onClose={this.stop} >
<div className="CristalScroll"> <div className="CristalScroll">
<div className="FullNode"> <div className="FullNode">
{runtime} {runtime}

View File

@ -9,6 +9,8 @@ import pushMessage from "./chain/send";
import Logs from "./Logs"; import Logs from "./Logs";
import StorageNodeInit from "./StorageNodeInit"; import StorageNodeInit from "./StorageNodeInit";
const [NodeUnknown, NodeRunning, NodeStopped] = [0, 1, 2]
class NodeList extends React.Component { class NodeList extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props)
@ -53,6 +55,7 @@ class NodeList extends React.Component {
give1k={this.transfer1kFrom1} give1k={this.transfer1kFrom1}
mountWindow={this.props.mountWindow} mountWindow={this.props.mountWindow}
spawnStorageNode={this.spawnStorageNode} spawnStorageNode={this.spawnStorageNode}
stop={this.stopNode(node.ID, onClose)}
/>) />)
} else { } else {
const fullId = await this.props.client.call('Pond.FullID', [node.ID]) const fullId = await this.props.client.call('Pond.FullID', [node.ID])
@ -72,7 +75,7 @@ class NodeList extends React.Component {
const nodes = nds.reduce((o, i) => {o[i.ID] = i; return o}, {}) const nodes = nds.reduce((o, i) => {o[i.ID] = i; return o}, {})
console.log('nds', nodes) console.log('nds', nodes)
Object.keys(nodes).map(n => nodes[n]).forEach(n => this.mountNode(n)) Object.keys(nodes).map(n => nodes[n]).filter(n => n.State === NodeRunning).forEach(n => this.mountNode(n))
this.setState({existingLoaded: true, nodes: nodes}) this.setState({existingLoaded: true, nodes: nodes})
} }
@ -114,6 +117,22 @@ class NodeList extends React.Component {
//this.setState(state => ({nodes: {...state.nodes, [node.ID]: node}})) //this.setState(state => ({nodes: {...state.nodes, [node.ID]: node}}))
} }
stopNode = (id, closeWindow) => async () => {
this.state.nodes[id].conn.close()
await this.props.client.call('Pond.Stop', [id])
closeWindow()
this.setState(prev => ({
nodes: {
...prev.nodes,
[id]: {...(prev.nodes[id]), State: NodeStopped, conn: undefined}
}
}))
}
startNode = (id) => async () => {
}
connMgr() { connMgr() {
this.setState({showConnMgr: true}) this.setState({showConnMgr: true})
} }
@ -155,6 +174,9 @@ class NodeList extends React.Component {
info = <span>{nd.peerid}</span> info = <span>{nd.peerid}</span>
logs = <a href='#' onClick={() => this.props.mountWindow(cl => <Logs node={nd.ID} onClose={cl}/>)}>[logs]</a> logs = <a href='#' onClick={() => this.props.mountWindow(cl => <Logs node={nd.ID} onClose={cl}/>)}>[logs]</a>
} }
if (nd.State === NodeStopped) {
info = <span>[stopped] <a href="#" onClick={this.startNode(n)}>[START]</a></span>
}
return <div key={n}> return <div key={n}>
{n} {type} {logs} {info} {n} {type} {logs} {info}

View File

@ -130,7 +130,7 @@ var runCmd = &cli.Command{
Usage: "run lotuspond daemon", Usage: "run lotuspond daemon",
Action: func(cctx *cli.Context) error { Action: func(cctx *cli.Context) error {
rpcServer := jsonrpc.NewServer() rpcServer := jsonrpc.NewServer()
a := &api{running: map[int32]runningNode{}} a := &api{running: map[int32]*runningNode{}}
rpcServer.Register("Pond", a) rpcServer.Register("Pond", a)
http.Handle("/", http.FileServer(http.Dir("lotuspond/front/build"))) http.Handle("/", http.FileServer(http.Dir("lotuspond/front/build")))