Skip to content
This repository was archived by the owner on Oct 30, 2018. It is now read-only.

Commit b493d54

Browse files
author
Gordon Hall
committed
reworking daemon interface, cli, and process management
1 parent 99383b6 commit b493d54

20 files changed

+367
-926
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
node_modules/
22
coverage/
3+
*.swp

bin/storjshare-daemon.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env node
2+
3+
'use strict';
4+
5+
const daemonize = require('daemon');
6+
const dnode = require('dnode');
7+
const config = require('../lib/config');
8+
const RPC = require('../lib/api');
9+
const api = new RPC({ logVerbosity: config.daemonLogVerbosity });
10+
const {createWriteStream} = require('fs');
11+
12+
daemonize();
13+
api.logger.pipe(createWriteStream(config.daemonLogFilePath, { flags: 'a' }));
14+
dnode(api.methods).listen(config.daemonRpcPort, config.daemonRpcAddress);

bin/storjshare-destroy.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env node
2+
3+
'use strict';
4+
5+
const config = require('../lib/config');
6+
const dnode = require('dnode');
7+
8+
if (!process.argv[2]) {
9+
console.error('\n you must supply a node ID to destroy');
10+
process.exit(1);
11+
}
12+
13+
const sock = dnode.connect(config.daemonRpcPort);
14+
15+
sock.on('remote', function(rpc) {
16+
rpc.destroy(process.argv[2], (err) => {
17+
if (err) {
18+
console.error(`\n cannot destroy node, reason: ${err.message}`);
19+
process.exit(1);
20+
}
21+
console.info(`\n * share ${process.argv[2]} destroyed`);
22+
process.exit(0);
23+
});
24+
});

bin/storjshare-killall.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env node
2+
3+
'use strict';
4+
5+
const config = require('../lib/config');
6+
const dnode = require('dnode');
7+
const sock = dnode.connect(config.daemonRpcPort);
8+
9+
sock.on('end', function() {
10+
console.info('\n * daemon has stopped');
11+
});
12+
13+
sock.on('remote', function(rpc) {
14+
rpc.killall();
15+
});

bin/storjshare-logs.js

Whitespace-only changes.

bin/storjshare-restart.js

Whitespace-only changes.

bin/storjshare-start.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env node
2+
3+
'use strict';
4+
5+
const storj = require('storj-lib');
6+
const dnode = require('dnode');
7+
const path = require('path');
8+
const config = require('../lib/config');
9+
const storjshare_start = require('commander');
10+
11+
storjshare_start
12+
.option('-c, --config <path>', 'specify the configuration path')
13+
.parse(process.argv);
14+
15+
if (!storjshare_start.config) {
16+
console.error('\n no config file was given, try --help');
17+
process.exit(1);
18+
}
19+
20+
const configPath = path.join(process.cwd(), storjshare_start.config);
21+
const sock = dnode.connect(config.daemonRpcPort);
22+
23+
sock.on('remote', function(rpc) {
24+
rpc.start(configPath, (err) => {
25+
if (err) {
26+
console.error(`\n failed to start share, reason: ${err.message}`);
27+
process.exit(1);
28+
}
29+
console.info(`\n * starting share with config at ${configPath}`);
30+
process.exit(0);
31+
});
32+
});

bin/storjshare-status.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env node
2+
3+
'use strict';
4+
5+
const config = require('../lib/config');
6+
const dnode = require('dnode');
7+
const sock = dnode.connect(config.daemonRpcPort);
8+
9+
sock.on('remote', function(rpc) {
10+
rpc.status(function(err, shares) {
11+
// TODO: Format the shares data...
12+
console.log(shares);
13+
sock.end();
14+
});
15+
});

bin/storjshare-stop.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env node
2+
3+
'use strict';
4+
5+
const config = require('../lib/config');
6+
const dnode = require('dnode');
7+
8+
if (!process.argv[2]) {
9+
console.error('\n you must supply a node ID to stop');
10+
process.exit(1);
11+
}
12+
13+
const sock = dnode.connect(config.daemonRpcPort);
14+
15+
sock.on('remote', function(rpc) {
16+
rpc.stop(process.argv[2], (err) => {
17+
if (err) {
18+
console.error(`\n cannot stop node, reason: ${err.message}`);
19+
process.exit(1);
20+
}
21+
console.info(`\n * share ${process.argv[2]} stopped`);
22+
process.exit(0);
23+
});
24+
});

bin/storjshare.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env node
2+
3+
'use strict';
4+
5+
const net = require('net');
6+
const config = require('../lib/config');
7+
const storjshare = require('commander');
8+
const {spawn} = require('child_process');
9+
const path = require('path');
10+
11+
storjshare
12+
.version(require('../package').version)
13+
.command('start <config>', 'start a farming node')
14+
.command('stop <nodeid>', 'stop a farming node')
15+
.command('restart <nodeid>', 'restart a farming node')
16+
.command('status [nodeid]', 'check status of node(s)', { isDefault: true })
17+
.command('logs <nodeid>', 'tail the logs for a node')
18+
.command('destroy <nodeid>', 'kills the farming node')
19+
.command('killall', 'kills all shares and stops the daemon');
20+
21+
const sock = net.connect(config.daemonRpcPort);
22+
23+
sock.once('error', function() {
24+
console.info('\n * daemon is not running, starting...');
25+
spawn(path.join(__dirname, 'storjshare-daemon.js'), [], { detached: true });
26+
setTimeout(() => storjshare.parse(process.argv), 500);
27+
});
28+
29+
sock.once('connect', function() {
30+
sock.end();
31+
setTimeout(() => storjshare.parse(process.argv), 500);
32+
});

lib/api.js

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
'use strict';
2+
3+
const {statSync, readFileSync} = require('fs');
4+
const stripJsonComments = require('strip-json-comments');
5+
const JsonLogger = require('kad-logger-json');
6+
const {fork} = require('child_process');
7+
8+
/** Class representing a local RPC API's handlers */
9+
class RPC {
10+
11+
/**
12+
* Creates a environment to manage share processes
13+
* @param {Object} options
14+
* @param {Number} options.logVerbosity
15+
*/
16+
constructor(options) {
17+
this.logger = new JsonLogger(options.logVerbosity);
18+
this.shares = new Map();
19+
}
20+
21+
/**
22+
* Logs the message by pushing it out the stream
23+
* @param {String} message
24+
* @param {String} level
25+
*/
26+
_log(msg, level='info') {
27+
this.logger[level](msg);
28+
}
29+
30+
/**
31+
* Starts a share process with the given configuration
32+
* @param {String} configPath
33+
* @param {RPC~startCallback}
34+
* @see https://storj.github.io/core/FarmerInterface.html
35+
*/
36+
start(configPath, callback) {
37+
this._log(`attempting to start share with config at path ${configPath}`);
38+
39+
try {
40+
statSync(configPath);
41+
} catch (err) {
42+
return callback(new Error(`failed to read config at ${configPath}`));
43+
}
44+
45+
try {
46+
JSON.parse(stripJsonComments(readFileSync(configPath).toString()));
47+
} catch (err) {
48+
return callback(new Error(`failed to parse config at ${configPath}`));
49+
}
50+
51+
// TODO: Fork the actual farmer process, passing it the configuration
52+
// TODO: Pipe the stdio to the configured log file
53+
// TODO: Listen for state changes to update the shares record
54+
//
55+
56+
callback(null);
57+
}
58+
/**
59+
* @callback RPC~startCallback
60+
* @param {Error|null} error
61+
*/
62+
63+
/**
64+
* Stops the share process for the given node ID
65+
* @param {String} nodeId
66+
* @param {RPC~stopCallback}
67+
*/
68+
stop(nodeId, callback) {
69+
this._log(`attempting to stop share with node id ${nodeId}`);
70+
71+
if (!this.shares.has(nodeId) || !this.shares.get(nodeId).process) {
72+
return callback(new Error(`share ${nodeId} is not running`));
73+
}
74+
75+
this.shares.get(nodeId).process.kill('SIGINT');
76+
callback(null);
77+
}
78+
/**
79+
* @callback RPC~stopCallback
80+
* @param {Error|null} error
81+
*/
82+
83+
/**
84+
* Restarts the share process for the given node ID
85+
* @param {String} nodeId
86+
* @param {RPC~restartCallback}
87+
*/
88+
restart(nodeId, callback) {
89+
this._log(`attempting to restart share with node id ${nodeId}`);
90+
this.stop(nodeId, (err) => {
91+
if (err) {
92+
return callback(err);
93+
}
94+
95+
this.start(this.shares.get(nodeId).config, callback);
96+
});
97+
}
98+
/**
99+
* @callback RPC~restartCallback
100+
* @param {Error|null} error
101+
*/
102+
103+
/**
104+
* Returns status information about the running shares
105+
* @param {RPC~statusCallback}
106+
*/
107+
status(callback) {
108+
this._log(`got status query`);
109+
110+
let statuses = [];
111+
112+
this.shares.forEach((share, nodeId) => {
113+
statuses.push({
114+
nodeId: nodeId,
115+
parsedConfig: share.config,
116+
readyState: share.process ? 1 : 0
117+
});
118+
});
119+
120+
callback(null, statuses);
121+
}
122+
/**
123+
* @callback RPC~statusCallback
124+
* @param {Error|null} error
125+
* @param {Object} status
126+
*/
127+
128+
/**
129+
* Simply kills the daemon and all managed proccesses
130+
*/
131+
killall() {
132+
this._log(`received kill signal, destroying running shares`);
133+
134+
for (let nodeId of this.shares.keys()) {
135+
this.destroy(nodeId, () => null);
136+
}
137+
138+
process.exit(0);
139+
}
140+
141+
/**
142+
* Kills the share with the given node ID
143+
* @param {String} nodeId
144+
* @param {RPC~destroyCallback}
145+
*/
146+
destroy(nodeId, callback) {
147+
this._log(`received destroy command for ${nodeId}`);
148+
149+
if (!this.shares.has(nodeId)) {
150+
return callback(new Error(`share ${nodeId} is not running`));
151+
}
152+
153+
share.process.kill('SIGINT');
154+
this.shares.delete(nodeId);
155+
callback(null);
156+
}
157+
/**
158+
* @callback RPC~destroyCallback
159+
* @param {Error|null} error
160+
*/
161+
162+
get methods() {
163+
return {
164+
start: this.start.bind(this),
165+
stop: this.stop.bind(this),
166+
restart: this.restart.bind(this),
167+
status: this.status.bind(this),
168+
killall: this.killall.bind(this),
169+
destroy: this.destroy.bind(this)
170+
};
171+
}
172+
173+
}
174+
175+
module.exports = RPC;

0 commit comments

Comments
 (0)