Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check if app is bound #8

Merged
merged 2 commits into from
Dec 12, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 44 additions & 0 deletions rukorun/checkBind.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
var fs = require('fs');
var _ = require('underscore');

module.exports = function(wantedPort, cb){

// $ cat /proc/net/tcp
// sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
// 0: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 78603 1 0000000000000000 100 0 0 10 -1
fs.readFile('/proc/net/tcp', function(err, data){
if(err){
console.error(err);
// swallow the error
return cb();
}
var lines = data.toString().split('\n').splice(1);
lines.forEach(function(line){
var arr = _.compact(line.split(' '));
if(arr[2] != "00000000:0000") return; // only looking for listening TCP sockets
if(arr[7] != "1666") return; // only looking sockets created by ruko user

var localAddress = arr[1].split(':');
var host = humanizeHostname(localAddress[0]);
var port = parseInt(localAddress[1], 16);
if(host !== '0.0.0.0') {
return cb(new Error('Error R11 (Bad bind) -> Process bound to host ' + host + ', should be 0.0.0.0'));
}
if(port !== wantedPort){
return cb(new Error('Error R11 (Bad bind) -> Process bound to port ' + port + ', should be ' + wantedPort + ' (see environment variable PORT)'));
}
cb(null, {
host: host,
port: port
});
});

// if nothing is found return
cb();
});
};

// trasnform 00000000 into 0.0.0.0
function humanizeHostname(str){
return str.match(/.{2}/g).map(function(hex){ return parseInt(hex, 16) }).join('.');
}
62 changes: 55 additions & 7 deletions rukorun/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ var cp = require('child_process');
var pty = require('pty.js');
var async = require('async');
var _ = require('underscore');
var checkBind = require('./checkBind');

var socketPath = process.argv[2];
var cwd = process.argv[3];
var killTimeout = process.argv[4];
var checkBindInterval = process.argv[5];
var bootTimeout = process.argv[6];

var ioSocket = net.createConnection(Path.join(socketPath, 'io.sock'));
var commandSocket = net.createConnection(Path.join(socketPath, 'command.sock'));
Expand Down Expand Up @@ -55,6 +58,47 @@ function processCommands() {
inst = spawn(payload, ioSocket, commandSocket);
}

if(payload.env_vars.PORT){
var isBound = false;

var bootTimeoutId = setTimeout(function(){
if(isBound) return;

sendToDynohost({
message: 'Error R10 (Boot timeout) -> Web process failed to bind to $PORT within 60 seconds of launch'
});
return kill('SIGKILL');
}, bootTimeout);

async.until(function(){
return isBound;
}, function(cb){
checkBind(payload.env_vars.PORT, function(err, host){
if(err) return cb(err);

if(host){
return isBound = true;
}

// check every 1s
setTimeout(cb, checkBindInterval)
});
}, function(err){
if(err){
sendToDynohost({
message: err.message
});
return kill('SIGKILL');
}

sendToDynohost({
type: 'bound'
});

clearTimeout(bootTimeoutId);
});
}

inst.on('exit', function(code) {
// pty.js does not forward the exit code
// https://github.com/chjj/pty.js/issues/28
Expand All @@ -73,24 +117,28 @@ function processCommands() {
throw new Error('WTF try to exit a non existing inst');
}

sendToDynohost({
message: 'Stopping all processes with SIGTERM'
});
inst.kill('SIGTERM');
kill('SIGTERM');

setTimeout(function(){
sendToDynohost({
message: ['Error R12 (Exit timeout) -> At least one process failed to exit within 10 seconds of SIGTERM',
'Stopping all processes with SIGKILL'].join('\n')
message: 'Error R12 (Exit timeout) -> At least one process failed to exit within 10 seconds of SIGTERM',
});
inst.kill('SIGKILL');

kill('SIGKILL');
}, killTimeout || 10000);
}
});

function sendToDynohost(object){
commandSocket.write(JSON.stringify(object) + '\n');
}

function kill(signal){
sendToDynohost({
message: 'Stopping all processes with ' + signal
});
inst.kill(signal);
}
}

// Used when `openruko run bash`
Expand Down
7 changes: 7 additions & 0 deletions test/fixture/badHost.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
var http = require('http');
var port = process.env.PORT;
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}).listen(port, '127.0.0.1');
console.log('Server running at http://127.0.0.1:' + port + '/');
6 changes: 6 additions & 0 deletions test/fixture/badPort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}).listen(6666);
console.log('Server running at http://127.0.0.1:6666/');
4 changes: 4 additions & 0 deletions test/fixture/setTimeout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
console.log('setTimeout started');
setTimeout(function(){
console.log('After a long long time');
}, 10000000);
185 changes: 134 additions & 51 deletions test/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ describe('rukorun', function(){
if(err) return done(err);
command = arr[0];
io = arr[1];

io.pipe(process.stdout, {end: false});
command.pipe(process.stdout, {end: false});
done();
});

Expand All @@ -31,16 +34,18 @@ describe('rukorun', function(){
Path.join(__dirname, '../rukorun/run.js'),
pathTest,
__dirname,
100,
10,
200
]);
child.stdout.pipe(process.stdout);
child.stderr.pipe(process.stderr);
child.stdout.pipe(process.stdout, {end: false});
child.stderr.pipe(process.stderr, {end: false});
}, 20);
});

afterEach(function(done){
child.kill('SIGTERM');
setTimeout(done, 200);
setTimeout(done, 20);
});

_({
Expand Down Expand Up @@ -123,68 +128,146 @@ describe('rukorun', function(){
});
});

describe('when launching long running process', function(done){
var commands = "";
beforeEach(function(done){
command.write(JSON.stringify(_({
command: 'node',
args: ['fixture/server.js'],
env_vars: {
PORT: 1234,
PATH: process.env.PATH
}
}).defaults(payload)));

command.on('data', function(data){ commands+= data; });
io.pipe(process.stdout, {end: false});

io.once('data', function(data){
done();
describe('with node process', function(){
var nodePayload = _({
command: 'node',
env_vars: {
PORT: 1337,
PATH: process.env.PATH
}
}).defaults(payload);

describe('when launching web process', function(done){
var commands = "";
beforeEach(function(done){
command.write(JSON.stringify(_({
args: ['fixture/server.js'],
}).defaults(nodePayload)));

command.on('data', function(data){
commands+= data;
if(/{"type":"bound"}/.test(data)) done();
});
});

it('should kill process with SIGTERM when sending `stop`', function(done){
command.write(JSON.stringify({
type: 'stop'
}));

child.on('exit', function(code){
expect(commands).to.include('Stopping all processes with SIGTERM');
expect(commands).to.include('Process exited with status ');
done();
});
});
});

it('should kill process with SIGTERM when sending `exit`', function(done){
command.write(JSON.stringify({
type: 'exit'
}));
describe('when launching long booting process', function(done){
var commands = "";
beforeEach(function(done){
command.write(JSON.stringify(_({
args: ['fixture/setTimeout.js'],
}).defaults(nodePayload)));

child.on('exit', function(code){
expect(commands).to.include('Stopping all processes with SIGTERM');
expect(commands).to.include('Process exited with status ');
done();
command.on('data', function(data){ commands+= data; });

io.on('data', function(data){
done();
});
});

it('should kill process with R10 if not started after bootTimeout', function(done){
beforeEach(function(done){
setTimeout(done, 20);
});

child.on('exit', function(code){
expect(commands).to.include('Error R10 (Boot timeout) -> Web process failed to bind to $PORT within 60 seconds of launch');
expect(commands).to.include('Stopping all processes with SIGKILL');
done();
});
});
});
});

describe('when launching catching signals processes', function(done){
var commands ="";
beforeEach(function(done){
command.write(JSON.stringify(_({
command: 'node',
args: ['fixture/chuck-norris.js'],
env_vars: {
PORT: 1234,
PATH: process.env.PATH
}
}).defaults(payload)));
describe('when launching a web process with a bad port', function(done){
var commands = "";
beforeEach(function(done){
command.write(JSON.stringify(_({
args: ['fixture/badPort.js'],
}).defaults(nodePayload)));

command.on('data', function(data){ commands+= data; });
command.on('data', function(data){ commands+= data; });

io.pipe(process.stdout, {end: false});
io.on('data', function(data){
done();
});
});

io.once('data', function(data){
done();
it('should kill process with R10 if not started after bootTimeout', function(done){
beforeEach(function(done){
setTimeout(done, 20);
});

child.on('exit', function(code){
expect(commands).to.include('Error R11 (Bad bind) -> Process bound to port 6666, should be 1337 (see environment variable PORT)');
expect(commands).to.include('Stopping all processes with SIGKILL');
done();
});
});
});

it('should kill process with SIGKILL when sending `exit`', function(done){
command.write(JSON.stringify({
type: 'exit'
}));
describe('when launching a web process with a bad host', function(done){
var commands = "";
beforeEach(function(done){
command.write(JSON.stringify(_({
args: ['fixture/badHost.js'],
}).defaults(nodePayload)));

child.on('exit', function(code){
expect(commands).to.include('Stopping all processes with SIGKILL');
done();
command.on('data', function(data){ commands+= data; });

io.on('data', function(data){
done();
});
});

it('should kill process with R10 if not started after bootTimeout', function(done){
beforeEach(function(done){
setTimeout(done, 20);
});

child.on('exit', function(code){
expect(commands).to.include('Error R11 (Bad bind) -> Process bound to host 1.0.0.127, should be 0.0.0.0');
expect(commands).to.include('Stopping all processes with SIGKILL');
done();
});
});
});


describe('when launching catching signals processes', function(done){
var commands ="";
beforeEach(function(done){
command.write(JSON.stringify(_({
args: ['fixture/chuck-norris.js'],
}).defaults(nodePayload)));

command.on('data', function(data){ commands+= data; });

command.on('data', function(data){
if(/{"type":"bound"}/.test(data)) done();
});
});

it('should kill process with SIGKILL when sending `exit`', function(done){
command.write(JSON.stringify({
type: 'stop'
}));

child.on('exit', function(code){
expect(commands).to.include('Stopping all processes with SIGKILL');
done();
});
});
});
});
Expand Down