Comparing version 0.2.5 to 0.3.0
@@ -41,2 +41,4 @@ /* | ||
ecv: { | ||
path: '/ecv', // Send GET to this for a heartbeat | ||
control: true, // send POST to /ecv/disable to disable the heartbeat, and to /ecv/enable to enable again | ||
monitor: '/', | ||
@@ -43,0 +45,0 @@ validator: function() { |
@@ -40,4 +40,3 @@ /* | ||
port: [3000, 3003], | ||
cluster: true, | ||
ecv: false | ||
cluster: true | ||
}); | ||
@@ -44,0 +43,0 @@ c.on('died', function(pid) { |
@@ -40,4 +40,3 @@ /* | ||
port: 3000, | ||
cluster: true, | ||
ecv: false | ||
cluster: true | ||
}); | ||
@@ -44,0 +43,0 @@ c.on('died', function(pid) { |
@@ -28,11 +28,21 @@ /* | ||
exports.enable = function(app, port, path, monitor, validator) { | ||
app.get(path || '/ecv', function(req, res) { | ||
exports.enable = function(app, options, emitter, validator) { | ||
var path = options.ecv.ecvPath || '/ecv'; | ||
var monitor = options.ecv.monitor || undefined; | ||
var control = options.ecv.control || false; | ||
var root = path || '/ecv'; | ||
var disabled; | ||
app.get(root, function(req, res) { | ||
var tosend = { | ||
date : new Date , | ||
port : port | ||
port : options.port | ||
}; | ||
var options = options || { | ||
if(disabled) { | ||
// Drop the ball | ||
away(req, res, tosend); | ||
return; | ||
} | ||
var coptions = { | ||
host: 'localhost', | ||
port: port, | ||
port: options.port, | ||
path: monitor || '/', | ||
@@ -44,6 +54,5 @@ method: 'GET', | ||
accept: 'application/json' | ||
} | ||
}; | ||
var creq = http.request(options, function(cres) { | ||
var creq = http.request(coptions, function(cres) { | ||
cres.setEncoding('utf8'); | ||
@@ -79,2 +88,43 @@ var data = ''; | ||
}); | ||
if(control === true) { | ||
app.post(root + '/disable', function (req, res) { | ||
disabled = true; | ||
emitter.emit('warning', { | ||
message: 'Disable request received' | ||
}); | ||
if(process.send) { | ||
process.send({ | ||
command: 'disable' | ||
}); | ||
} | ||
res.writeHead(204, { | ||
'since': new Date(Date.now() - process.uptime()*1000), | ||
'cache-control': 'no-cache', | ||
'X-Powered-By': 'Cluster2', | ||
'Connection': 'close' | ||
}); | ||
res.end() | ||
}); | ||
app.post(root + '/enable', function (req, res) { | ||
disabled = false; | ||
emitter.emit('warning', { | ||
message: 'Enable request received' | ||
}); | ||
if(process.send) { | ||
process.send({ | ||
command: 'enable' | ||
}); | ||
} | ||
res.writeHead(204); | ||
res.end() | ||
}); | ||
process.on('message', function (message) { | ||
if(message && message.command) { | ||
disabled = message.command === 'disable'; | ||
} | ||
}); | ||
} | ||
}; | ||
@@ -86,3 +136,5 @@ | ||
'since': new Date(Date.now() - process.uptime()*1000), | ||
'cache-control': 'no-cache' | ||
'cache-control': 'no-cache', | ||
'X-Powered-By': 'Cluster2', | ||
'Connection': 'close' | ||
}); | ||
@@ -97,3 +149,5 @@ res.write('status=AVAILABLE&ServeTraffic=true&ip='+ req.connection.address()['address'] +'&hostname='+ hostname +'&port=' + tosend.port+ '&time=' + tosend.date.toString()); | ||
'since': new Date(Date.now() - process.uptime()*1000), | ||
'cache-control': 'no-cache' | ||
'cache-control': 'no-cache', | ||
'X-Powered-By': 'Cluster2', | ||
'Connection': 'close' | ||
}); | ||
@@ -103,1 +157,13 @@ res.write('status=WARNING&ServeTraffic=false&ip='+ req.connection.address()['address'] +'&hostname='+ hostname +'&port=' + tosend.port + '&time=' + tosend.date.toString()); | ||
} | ||
function away(req, res, tosend) { | ||
res.writeHead(400, { | ||
'content-type': 'text/plain', | ||
'since': new Date(Date.now() - process.uptime()*1000), | ||
'cache-control': 'no-cache', | ||
'X-Powered-By': 'Cluster2', | ||
'Connection': 'close' | ||
}); | ||
res.write('status=DISABLED&ServeTraffic=false&ip='+ req.connection.address()['address'] +'&hostname='+ hostname +'&port=' + tosend.port + '&time=' + tosend.date.toString()); | ||
res.end(); | ||
} |
@@ -43,3 +43,5 @@ /* | ||
this.options.monPort = this.options.monPort || 3001; | ||
this.options.ecvPath = this.options.ecvPath || '/ecv'; | ||
this.options.ecv = this.options.ecv || { | ||
path: '/ecv' | ||
} | ||
this.options.monPath = this.options.monPath || '/'; | ||
@@ -66,3 +68,2 @@ this.options.noWorkers = this.options.noWorkers || os.cpus().length; | ||
ecv: self.options.ecv, | ||
ecvPath: self.options.ecvPath || '/ecv', | ||
noWorkers: self.options.noWorkers, | ||
@@ -84,3 +85,3 @@ timeout: self.options.timeout || 30 * 1000, // idle socket timeout | ||
if(self.options.ecv) { | ||
ecv.enable(app, self.options.port, self.options.ecvPath, self.options.ecv.monitor, function (data) { | ||
ecv.enable(app, self.options, self, function (data) { | ||
return true; | ||
@@ -100,3 +101,3 @@ }); | ||
if(self.options.ecv) { | ||
ecv.enable(app, self.options.port, self.options.ecvPath, self.options.ecv.monitor, function (data) { | ||
ecv.enable(app, self.options, self, function (data) { | ||
return true; | ||
@@ -103,0 +104,0 @@ }); |
@@ -146,3 +146,17 @@ /* | ||
this.stats.noWorkers++; | ||
worker.on('message', function(message) { | ||
if(message && message.command) { | ||
self.notifyWorkers(message); | ||
} | ||
}); | ||
return worker; | ||
} | ||
this.notifyWorkers = function(message) { | ||
_.each(self.workers, function(worker) { | ||
worker.send(message) | ||
}); | ||
} | ||
} | ||
@@ -157,4 +171,5 @@ | ||
this.stats.freemem = os.freemem(); | ||
this.workers = []; | ||
// Monitor to server ecv checks | ||
// Monitor to serve log files and other stats - typically on an internal port | ||
var monitor = new Monitor({ | ||
@@ -174,3 +189,4 @@ stats: self.stats, | ||
for(var i = 0; i < self.options.noWorkers; i++) { | ||
self.createWorker(); | ||
var worker = self.createWorker(); | ||
self.workers[worker.pid + ''] = worker; | ||
} | ||
@@ -182,3 +198,5 @@ | ||
self.stats.noWorkers--; | ||
self.createWorker(); | ||
var worker = self.createWorker(); | ||
self.workers[worker.pid + ''] = worker; | ||
delete self.workers[worker.pid + '']; | ||
delete self.stats.workers[worker.pid]; | ||
@@ -258,3 +276,3 @@ }; | ||
if(self.options.ecv) { | ||
ecv.enable(app, port, self.options.ecvPath, self.options.ecv.monitor, function(data) { | ||
ecv.enable(app, self.options, self.emitter, function(data) { | ||
return true; | ||
@@ -261,0 +279,0 @@ }); |
@@ -8,3 +8,3 @@ { | ||
"name": "cluster2", | ||
"version": "0.2.5", | ||
"version": "0.3.0", | ||
"repository": { | ||
@@ -11,0 +11,0 @@ "type": "git", |
@@ -17,2 +17,4 @@ ## What is cluster2 | ||
* Events for logging cluster activities | ||
* Exit with error code when the port is busy to fail start scripts | ||
* Disable monitor | ||
* and more coming soon | ||
@@ -97,4 +99,8 @@ | ||
view application logs (whatever is written to a `/logs` dir), and npm dependencies. | ||
* `ecv`: A validator to validate the runtime health of the app. If found unhealthy, emits a disable | ||
traffic signal at path `/ecv`. ECV stands for "extended content verification". | ||
* `ecv`: ECV stands for "extended content verification". This is an object with the following | ||
additional properties: | ||
* `path`: A path to serve a heart beat. See below. | ||
* `monitor`: A URI to check before emitting a valid heart beat signal | ||
* `control`: When true, allows clients to enable or disable the signal. See below. | ||
validator to validate the runtime health of the app. If found unhealthy, emits a disable | ||
* `noWorkers`: Defaults to `os.cpus().length`. | ||
@@ -132,11 +138,5 @@ * `timeout`: Idle socket timeout. Automatically ends incoming sockets if found idle for this | ||
<<<<<<< HEAD | ||
Completion of `shutdown()` does not necessarily mean that all worker processes are dead immediately. The workers | ||
may take a while to complete processing of current requests and exit. The `shutdown` flow only | ||
guarantees that the server takes no new connections. | ||
======= | ||
Completion of `shutdown()` does not necessarily mean that all worker processes are dead immediately. | ||
The workers may take a while to complete processing of current requests and exit. The `shutdown()` | ||
flow only guarantees that the server takes no new connections. | ||
>>>>>>> 70b11cd6a93ae49ff959e60d52a5ba91b012690f | ||
@@ -184,1 +184,56 @@ ## Cluster2 Events | ||
## Routing Traffic | ||
It is fairly common for proxies or load balancers deployed in front of node clusters, and those | ||
proxies to use monitor URLs to detect the health of the cluster. Cluster2 includes a monitor | ||
at `http://<host>:<port>/ecv`. You can change this by setting the `path` property when initializing | ||
the cluster. | ||
In case you want to take the node cluster out of rotation from the proxy/load balancer, you can do | ||
so by setting `control` to `true` when initializing the cluster. At runtime, you can send a `POST` | ||
request to `http://<host>:<port>/ecv/disable`. Once this is done, further requests to | ||
`http://<host>:<port>/ecv` will get a network error. You can bring the cluster back to rotation by | ||
sending a `POST` request to `http://<host>:<port>/ecv/enable`. | ||
Since it will be potentially disastrous to let artibrary clients enable/disable traffic, you should | ||
configure your proxy/load balancer to prevent external traffic to `/ecv*`. | ||
To test this, bring up an example | ||
node examples/express/express-server.js | ||
and send a `GET` request to `http://localhost:3000/ecv` and notice the response. | ||
HTTP/1.1 200 OK | ||
X-Powered-By: Cluster2 | ||
content-type: text/plain | ||
since: Fri May 18 2012 09:49:32 GMT-0700 (PDT) | ||
cache-control: no-cache | ||
Connection: keep-alive | ||
Transfer-Encoding: chunked | ||
status=AVAILABLE&ServeTraffic=true&ip=127.0.0.1&hostname=somehost&port=3000&time=Fri May 18 2012 09:49:49 GMT-0700 (PDT) | ||
To flip the monitor into a disabled state, send a `POST` request to `http://localhost:3000/disable`. | ||
HTTP/1.1 204 No Content | ||
X-Powered-By: Cluster2 | ||
since: Fri May 18 2012 09:54:25 GMT-0700 (PDT) | ||
cache-control: no-cache | ||
Connection: close | ||
Subsequent `GET` requests to `http://localhost:3000/ecv` will return a response similar to the one | ||
below. | ||
HTTP/1.1 400 Bad Request | ||
X-Powered-By: Cluster2 | ||
content-type: text/plain | ||
since: Fri May 18 2012 09:54:25 GMT-0700 (PDT) | ||
cache-control: no-cache | ||
Connection: close | ||
Transfer-Encoding: chunked | ||
status=DISABLED&ServeTraffic=false&ip=127.0.0.1&hostname=somehost&port=3000&time=Fri May 18 2012 09:55:17 GMT-0700 (PDT) | ||
To flip the monitor back into an enabled state, send a `POST` request to `http://localhost:3000/enable`. | ||
@@ -18,6 +18,6 @@ * <del>Basic cluster</del> | ||
* ws connection causing workers to live | ||
* Check for open port and and exit when busy with an error exit code | ||
* <del>Check for open port and and exit when busy with an error exit code</del> | ||
* Write start/shutdown/stop to log | ||
* Send counters in bulk | ||
* Traffic in and out - continue connection listening but update ecv | ||
* <del>Traffic in and out - continue connection listening but update ecv</del> | ||
* Raise heartbeats thru logEmitter |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
80514
1280
236