diff --git a/application.coffee b/application.coffee new file mode 100644 index 0000000..d7408e2 --- /dev/null +++ b/application.coffee @@ -0,0 +1,69 @@ + +# $ requires npm install connect into +# require.paths [ '$HOME/.node_modules', '$HOME/.node_libraries', '/usr/lib/node' ] + +fs = require 'fs' +connect = require 'connect' + +process.on 'uncaughtException', (err) -> + console.log "Type: " + err.type + console.log "Message: " + err.message + console.log "Arguments: " + err.arguments + console.log err.stack + +neo4jconfig = require "./neo4jconfig.coffee" +neo4jserver = require "./neo4jserver.coffee" +proxy = require "./proxy.coffee" + +class ServerManager + constructor: -> + proxies = {} + auth = { user: "admin", pass: "admin"} + + bringUp = (id) -> + config = neo4jconfig.parse id + neo4j = neo4jserver.create config + proxies[id] = proxy.buildFor neo4j, config + + storeConfig = -> + config = + auth : auth + proxies: (key for key of proxies) + fs.writeFileSync "config.json", JSON.stringify(config) + + @loadConfig = -> + try + config = JSON.parse(fs.readFileSync "config.json") + auth = config['auth'] + for i in config['proxies'] + try bringUp i catch e + console.log "error during startup for "+ i + " " + e + catch error + console.log error + + @start = -> + connect( + connect.basicAuth (user, pass) -> auth.user == user && auth.pass == pass + connect.router (app) -> + app.get '/', (request, response) -> response.end JSON.stringify(key for key of proxies) + + app.post '/:id', (request, response) -> + id = request.params.id + if (proxies[id]) then throw new Error "instance " + id + " is already registered" + bringUp id + storeConfig() + response.end "add "+request.params.id + + app.delete '/:id', (request, response) -> + id = request.params.id + if (!proxies[id]) then throw new Error "instance " + id + " is not registered" + proxies[id].stop() + delete proxies[id] + storeConfig() + response.end "delete "+request.params.id + + ).listen(7999) + +serverManager = new ServerManager() +serverManager.loadConfig() +serverManager.start() diff --git a/iptables.coffee b/iptables.coffee new file mode 100644 index 0000000..aaf1853 --- /dev/null +++ b/iptables.coffee @@ -0,0 +1,22 @@ + +exec = require("child_process").exec + +class IptablesRule + constructor: (rule) -> + isPresent = (condition, callback) -> + exec "iptables -tnat -S PREROUTING", (x, o) -> + if ( (o.indexOf(rule) != -1) == condition) then callback() + + @addRule = -> + isPresent false, -> + exec "iptables -tnat -A PREROUTING " + rule + + @removeRule = -> + isPresent true, -> + exec "iptables -tnat -D PREROUTING " + rule + + +exports.rule = (rule) -> new IptablesRule rule + +exports.redirectRule = (port, proxyPort) -> + new IptablesRule "-i eth0 -p tcp -m tcp --dport " + port + " -j REDIRECT --to-ports " + proxyPort diff --git a/neo4jconfig.coffee b/neo4jconfig.coffee new file mode 100644 index 0000000..aced280 --- /dev/null +++ b/neo4jconfig.coffee @@ -0,0 +1,16 @@ +fs = require 'fs' + +class Neo4JConfiguration + constructor: (@instance) -> + home = "/mnt/" + @instance + configData = fs.readFileSync home + "/conf/neo4j-server.properties", "UTF-8" + console.log "parse config for " + @instance + + @port = Number /org.neo4j.server.webserver.port=(.+)/.exec(configData)[1] + @adminCredentials = /org.neo4j.server.credentials=(.+)/.exec(configData)[1] + @proxyPort = this.port + 1000 + @startCmd = home + "/bin/neo4j start" + @statusCmd = home + "/bin/neo4j status" + @stopCmd = home + "/bin/neo4j stop" + +exports.parse = (instance) -> new Neo4JConfiguration instance \ No newline at end of file diff --git a/neo4jserver.coffee b/neo4jserver.coffee new file mode 100644 index 0000000..eb87c30 --- /dev/null +++ b/neo4jserver.coffee @@ -0,0 +1,100 @@ +http = require "http" +iptables = require "./iptables.coffee" + +exec_org = require('child_process').exec + +exec = (log, cmd, callback) -> + log "exec:"+cmd + exec_org cmd, (exit, out, err)-> + if (exit) then log "exit-code: "+exit + if (out) then log " > " + line for line in out.split "\n" + if (err) then log "E> " + line for line in err.split "\n" + if (callback) then callback exit, out, err + + +class Neo4JServer + constructor: (@config) -> + iptRule = iptables.redirectRule config.port, config.proxyPort + running = false + starting = false + config = @config + + log = (message) -> console.log config.instance + " " + message + + setRunning = (r) -> + running = r + if (running) + log "remove firewall rule" + iptRule.removeRule() + else + log "add firewall rule" + iptRule.addRule() + + updateStatus = @updateStatus = -> + exec log, config.statusCmd, (error, stdout) -> + setRunning(stdout.indexOf("not running") == -1) + checkIdle() + + checkIdle = @checkIdle = -> + if (running) + request = http.request + port: config.port, method: 'GET', path: '/admin/statistic/', host: '127.0.0.1', + headers: + Host:'localhost', Accept:"application/json" + Authorization: 'Basic ' + new Buffer(config.adminCredentials).toString('base64'), + (response)-> + now = new Date().getTime() / 1000 + response.setEncoding 'utf8' + data = "" + response.on 'data', (chunk) -> data += chunk + response.on 'end', -> + requestCount = 0 + period = 0 + for i in JSON.parse(data) + if (i['timeStamp'] > (now - 7200)) + requestCount += i['requests'] + period += i['period'] + + log "request-count for "+ config.port + " == " + requestCount + "/" + period + + request.on 'error', (e) -> + log "problem with request: " + e.message + setRunning false + + request.end "\n" + + startServer = @startServer = -> + log "try to start server" + if (starting || running) + log "is running(=" + running + ") or starting(=" + starting + ")" + return + + starting = true + log "starting" + exec log, config.startCmd, (error, stdout, stderr) -> + starting = false + if (error == null) then setRunning true + else if (stdout.indexOf("already running with pid") != -1) then updateStatus() + + @stopServer = -> + log "try to stop server" + if (!running && !starting) + log "not running(=" + running + ") or starting(=" + starting + ")" + return + + starting = true + log "stopping" + exec log, config.stopCmd, (error, stdout, stderr) -> + starting = false + setRunning false + + waitForServer = @waitForServer = (msg, callback) -> + if (running) + callback() + else + log "wait " + msg + if (!starting) then startServer() + setTimeout waitForServer, 2000, msg, callback + + +exports.create = (config) -> new Neo4JServer(config) diff --git a/proxy.coffee b/proxy.coffee new file mode 100644 index 0000000..dc83f5b --- /dev/null +++ b/proxy.coffee @@ -0,0 +1,32 @@ + +net = require 'net' + +class Proxy + constructor: (neo4j, config) -> + log = (message) -> console.log config.instance + " " + message + + server = net.createServer (proxySocket) -> + info = proxySocket.remoteAddress + ":" + proxySocket.remotePort + log "handle connection " + info + + serverSocket = new net.Socket() + proxySocket.on "data", (data) -> neo4j.waitForServer ">" + info, -> serverSocket.write data + proxySocket.on "close", -> neo4j.waitForServer ">" + info, -> serverSocket.end() + + neo4j.waitForServer "<" + info, -> + serverSocket.connect config.port, "127.0.0.1" + serverSocket.on "data", (data) -> proxySocket.write data + serverSocket.on "close", (had_error) -> proxySocket.end() + + log "start " + config.proxyPort + " -> " + config.port + list = server.listen config.proxyPort + + updater = setInterval neo4j.checkIdle, 30000 + neo4j.updateStatus() + + @stop = -> + clearInterval updater + neo4j.startServer() + server.close() + +exports.buildFor = (neo4j, config) -> new Proxy neo4j, config \ No newline at end of file