diff --git a/bin/genieacs-cwmp b/bin/genieacs-cwmp index a135c9e..7dd92d9 100755 --- a/bin/genieacs-cwmp +++ b/bin/genieacs-cwmp @@ -2,9 +2,8 @@ var path = require('path'); var fs = require('fs'); -var dir = path.join(path.dirname(fs.realpathSync(__filename)), '../lib/'); -process.title = 'genieacs-cwmp'; +var dir = path.resolve(path.dirname(fs.realpathSync(__filename)), '..'); process.chdir(dir); -require(path.join(dir, './cwmp')); - +cluster = require(path.resolve(dir, 'lib/cluster')); +cluster.start('cwmp'); diff --git a/bin/genieacs-fs b/bin/genieacs-fs index 67d4571..5d0253b 100755 --- a/bin/genieacs-fs +++ b/bin/genieacs-fs @@ -2,9 +2,8 @@ var path = require('path'); var fs = require('fs'); -var dir = path.join(path.dirname(fs.realpathSync(__filename)), '../lib/'); -process.title = 'genieacs-fs'; +var dir = path.resolve(path.dirname(fs.realpathSync(__filename)), '..'); process.chdir(dir); -require(path.join(dir, './fs')); - +cluster = require(path.resolve(dir, 'lib/cluster')); +cluster.start('fs'); diff --git a/bin/genieacs-nbi b/bin/genieacs-nbi index 6bf4f72..5069105 100755 --- a/bin/genieacs-nbi +++ b/bin/genieacs-nbi @@ -2,9 +2,8 @@ var path = require('path'); var fs = require('fs'); -var dir = path.join(path.dirname(fs.realpathSync(__filename)), '../lib/'); -process.title = 'genieacs-nbi'; +var dir = path.resolve(path.dirname(fs.realpathSync(__filename)), '..'); process.chdir(dir); -require(path.join(dir, './nbi')); - +cluster = require(path.resolve(dir, 'lib/cluster')); +cluster.start('nbi'); diff --git a/config/config-sample.json b/config/config-sample.json index 0105624..5e5b1b9 100644 --- a/config/config-sample.json +++ b/config/config-sample.json @@ -1,15 +1,15 @@ { - "MONGODB_CONNECTION_URL" : "mongodb://localhost/genieacs", - "REDIS_SOCKET" : "6379", - "ACS_INTERFACE" : "0.0.0.0", - "ACS_PORT" : 7547, - "ACS_HTTPS_INTERFACE" : "0.0.0.0", - "ACS_HTTPS_PORT" : 7548, - "API_INTERFACE" : "0.0.0.0", - "API_PORT" : 7557, - "FILES_INTERFACE" : "0.0.0.0", - "FILES_PORT" : 7567, + "MONGODB_CONNECTION_URL" : "mongodb://127.0.0.1/genieacs", + "REDIS_PORT" : "6379", + "REDIS_HOST" : "127.0.0.1", + "CWMP_INTERFACE" : "0.0.0.0", + "CWMP_PORT" : 7547, + "CWMP_SSL" : false, + "NBI_INTERFACE" : "0.0.0.0", + "NBI_PORT" : 7557, + "FS_INTERFACE" : "0.0.0.0", + "FS_PORT" : 7567, "FILES_IP" : "192.168.0.1", "LOG_INFORMS" : true, - "DEBUG_DEVICES" : {} + "DEBUG" : false } diff --git a/config/httpscert-sample.crt b/config/httpscert-sample.crt deleted file mode 100644 index 77af0ba..0000000 --- a/config/httpscert-sample.crt +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICqTCCAhICCQDSrolRw1qPBzANBgkqhkiG9w0BAQUFADCBmDELMAkGA1UEBhMC -SVExFTATBgNVBAgMDFN1bGF5bWFuaXlhaDEVMBMGA1UEBwwMU3VsYXltYW5peWFo -MQ8wDQYDVQQKDAZGYW5vb3MxEzARBgNVBAsMClRlY2hub2xvZ3kxDjAMBgNVBAMM -BWdlbmllMSUwIwYJKoZIhvcNAQkBFhZ6YWlkLmFiZHVsbGFAZmFub29zLmlxMB4X -DTEzMDIxNzEyNTYzN1oXDTE0MDIxNzEyNTYzN1owgZgxCzAJBgNVBAYTAklRMRUw -EwYDVQQIDAxTdWxheW1hbml5YWgxFTATBgNVBAcMDFN1bGF5bWFuaXlhaDEPMA0G -A1UECgwGRmFub29zMRMwEQYDVQQLDApUZWNobm9sb2d5MQ4wDAYDVQQDDAVnZW5p -ZTElMCMGCSqGSIb3DQEJARYWemFpZC5hYmR1bGxhQGZhbm9vcy5pcTCBnzANBgkq -hkiG9w0BAQEFAAOBjQAwgYkCgYEAxtHC3jlOoVsZQ9I8Wgd+8NkEaRfc01lRIAzj -eWuUO/xNY54EUDHecRxwolHtoGkkZAp4kKR3uUVdAo9uXmNT2CeeZ5lfRoa8WmgG -Yd6PExsdc4ZeKcw20XrKtQICqmU6rGlLERMFX97ejQh3eUmqwbzNaXiURxcKGdeK -2C/NxoUCAwEAATANBgkqhkiG9w0BAQUFAAOBgQAYFw6dy/z74ahnA5QpuJOjjTrt -AJaL9iuL1xS6TFfzne9UMIT+fFJsgSy6xx3TGkvbZJTnCzJN7I2uqmAhMoGFy7Ea -svCCkj8U5qkjqgwwi7IbwQ7fXxTVdPqUlkIdNB2YLDELksxCX+ZzGQPiFB8WrSoO -OBvB+hhPFpgp3aO2AQ== ------END CERTIFICATE----- diff --git a/config/httpscert-sample.key b/config/httpscert-sample.key deleted file mode 100644 index 6493262..0000000 --- a/config/httpscert-sample.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXgIBAAKBgQDG0cLeOU6hWxlD0jxaB37w2QRpF9zTWVEgDON5a5Q7/E1jngRQ -Md5xHHCiUe2gaSRkCniQpHe5RV0Cj25eY1PYJ55nmV9GhrxaaAZh3o8TGx1zhl4p -zDbResq1AgKqZTqsaUsREwVf3t6NCHd5SarBvM1peJRHFwoZ14rYL83GhQIDAQAB -AoGAR4NpuW8ZhlZzNv7m8Gcj1NPqjzajGm5VDNc19xWoAw+UcpGC8amOXlcheHFJ -rNpE2asehMBzw/NcKjCXPWIy5LnGmttJMN2EbT2h/+uxBIM5IUc+joF744xF4C0t -5zrtqQYJxC7sBLX2vf7nY6iuovUM7fHEwACyV3Rpw3zdZf0CQQDiqW+ugLKcfS/c -6hkGTxeYWF4gUJTf3ke3au8nyMmUhyBwOuP4kpO72mK8bMLtjZRk3g6YChA4qH41 -nfXUXIh7AkEA4I2/zj4GDjjspDR+/tX0iUHQk1Rm2CRmRXXFJV1f688OLODCcOFK -Ms/+qnbe41wXo1NUxCqVrnPwEh3MVl88/wJBANUTpXqtX7s+8ETECTxmE+9Se0Hz -ntCJXG/v5d1Ho0Cueu8mXXMrEBiTpygtMl4xL2RXX+7L3w30T5lF4nh9eIsCQQDY -DmsbdL/81TaTc9MxPQlxOzsFVrMxiixA7uTGY8ZULD/2XDeZxprOsyPUPlWMquIv -ipFHwbWNSg4WUEd4X2BfAkEAytze+0mCd0WCsKbKwfTQnmJ3bK7JQywtC03llWyA -Om3Vl61Syaa7M2hl5rLvitZMvU1Wd0s8raOXN8RUuRXZBw== ------END RSA PRIVATE KEY----- diff --git a/lib/cluster.coffee b/lib/cluster.coffee new file mode 100644 index 0000000..9ea3d98 --- /dev/null +++ b/lib/cluster.coffee @@ -0,0 +1,45 @@ +### +# Copyright 2013, 2014 Zaid Abdulla +# +# GenieACS is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# GenieACS is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with GenieACS. If not, see . +### + +config = require './config' +cluster = require 'cluster' +util = require 'util' + + +cluster.on('listening', (worker, address) -> + util.log("Worker #{worker.process.pid} listening to #{address.address}:#{address.port}") +) + +cluster.on('exit', (worker, code, signal) -> + util.log("Worker #{worker.process.pid} died (#{worker.process.exitCode})") + cluster.fork() +) + + +start = (service) -> + workerProcesses = config["#{service.toUpperCase()}_WORKER_PROCESSES"] + + cluster.setupMaster({ + exec : 'lib/server', + args : [service] + }) + + for [0 ... workerProcesses] + cluster.fork() + + +exports.start = start diff --git a/lib/config.coffee b/lib/config.coffee index 64ce694..2b5155e 100644 --- a/lib/config.coffee +++ b/lib/config.coffee @@ -15,43 +15,95 @@ # along with GenieACS. If not, see . ### -exports.MONGODB_CONNECTION_URL = 'mongodb://localhost/genieacs' -exports.REDIS_DB = 0 -exports.ACS_PORT = 7547 # CWMP port as assigned by IANA -exports.ACS_HTTPS_PORT = 7548 -exports.ACS_INTERFACE = '0.0.0.0' -exports.ACS_HTTPS_INTERFACE = '0.0.0.0' -exports.API_PORT = 7557 -exports.API_INTERFACE = '0.0.0.0' -exports.FILES_PORT = 7567 -exports.FILES_INTERFACE = '0.0.0.0' -exports.FILES_IP = '172.240.21.2' # Used when sending download requests to devices -exports.CACHE_DURATION = 60 # in seconds -exports.PRESETS_CACHE_DURATION = 86400 -exports.PRESETS_TIME_PADDING = 1 -exports.MONGODB_SOCKET = '/tmp/mongodb-27017.sock' -exports.REDIS_SOCKET = '/tmp/redis.sock' -exports.WORKER_RESPAWN_TIME = 60000 -exports.LOG_INFORMS = false -exports.DEBUG_DEVICES = {} # {'202BC1-BM632w-8KA8WA1151100043' : true} -exports.DEVICE_ONLINE_THRESHOLD = 4000 -exports.RETRY_DELAY = 300 -exports.IGNORE_XML_NAMESPACES = false # Traverse XML using element's local name only -exports.LIBXMLJS_OPTIONS = {} # Options passed directly to libxmljs -exports.SESSION_TIMEOUT = 30 -exports.GET_PARAMETER_NAMES_DEPTH_THRESHOLD = 0 -exports.TASK_PARAMETERS_BATCH_SIZE = 32 +path = require 'path' +common = require './common' + + +defaults = { + CONFIG_DIR : './config', + + MONGODB_CONNECTION_URL : 'mongodb://127.0.0.1/genieacs', + REDIS_PORT : 6379, + REDIS_HOST : '127.0.0.1', + REDIS_DB : 0, + + CWMP_WORKER_PROCESSES : 4, + CWMP_PORT : 7547, # CWMP port as assigned by IANA + CWMP_INTERFACE : '0.0.0.0', + CWMP_SSL : false, + NBI_WORKER_PROCESSES : 2, + NBI_PORT : 7557, + NBI_INTERFACE : '0.0.0.0', + NBI_SSL : false, + FS_WORKER_PROCESSES : 2, + FS_PORT : 7567, + FS_INTERFACE : '0.0.0.0', + FS_SSL : false, + FILES_IP : '192.168.0.1', # Used when sending download requests to devices + + CACHE_DURATION : 60, # in seconds + PRESETS_CACHE_DURATION : 86400, + PRESETS_TIME_PADDING : 1, + + LOG_INFORMS : true, + DEBUG : false, + DEVICE_ONLINE_THRESHOLD : 4000, + RETRY_DELAY : 300, + IGNORE_XML_NAMESPACES : false, # Traverse XML using element's local name only + SESSION_TIMEOUT : 30, + GET_PARAMETER_NAMES_DEPTH_THRESHOLD : 0, + TASK_PARAMETERS_BATCH_SIZE : 32 +} + + +# Command line arguments +exports.argv = [] +argv = process.argv[2..] +while argv.length + arg = argv.shift() + if arg[0] == '-' + v = argv.shift() + exports.argv[arg] = v + n = arg[2..].toUpperCase().replace(/-/g, '_') + if defaults[n]? + exports[n] ?= common.matchType(defaults[n], v) + # Save as environmnet variables to pass on to any child processes + process.env["GENIEACS_#{n}"] = v + else + exports.argv.push(arg) + + +# Environment variable +for k, v of process.env + k = k[9..] # remove "GENIEACS_" prefix + if defaults[k]? + exports[k] ?= common.matchType(defaults[k], v) + + +# Find config dir +if exports.argv['--config-dir']? + exports.CONFIG_DIR = exports.argv['--config-dir'] +else if process.env['GENIEACS_CONFIG_DIR']? + exports.CONFIG_DIR = process.env['GENIEACS_CONFIG_DIR'] +else + exports.CONFIG_DIR = defaults.CONFIG_DIR + + +# Configuration file +for k, v of require(path.resolve(exports.CONFIG_DIR, 'config')) + if defaults[k]? + exports[k] ?= common.matchType(defaults[k], v) + + +# Defaults +for k, v of defaults + exports[k] ?= defaults[k] -# load configuration -c = require('../config/config') -for k, v of c - exports[k] = v # load parameter configurations -exports.PARAMETERS = require('../config/parameters') +exports.PARAMETERS = require(path.resolve(exports.CONFIG_DIR, 'parameters')) # load authentication scripts -exports.auth = require('../config/auth') - -exports.CUSTOM_COMMANDS = require('../config/custom_commands') +exports.auth = require(path.resolve(exports.CONFIG_DIR, 'auth')) +exports.CUSTOM_COMMANDS = require(path.resolve(exports.CONFIG_DIR, 'custom_commands')) diff --git a/lib/cwmp.coffee b/lib/cwmp.coffee index be25bef..aa74bbb 100644 --- a/lib/cwmp.coffee +++ b/lib/cwmp.coffee @@ -41,14 +41,11 @@ config = require './config' common = require './common' util = require 'util' -http = require 'http' -https = require 'https' soap = require './soap' tasks = require './tasks' db = require './db' presets = require './presets' mongodb = require 'mongodb' -fs = require 'fs' apiFunctions = require './api-functions' customCommands = require './custom-commands' crypto = require 'crypto' @@ -61,9 +58,9 @@ holdUntil = Date.now() writeResponse = (currentRequest, res) -> - if config.DEBUG_DEVICES[currentRequest.session.deviceId] + if config.DEBUG dump = "# RESPONSE #{new Date(Date.now())}\n" + JSON.stringify(res.headers) + "\n#{res.data}\n\n" - fs = require('fs').appendFile("../debug/#{currentRequest.session.deviceId}.dump", dump, (err) -> + require('fs').appendFile("../debug/#{currentRequest.session.deviceId}.dump", dump, (err) -> throw err if err ) @@ -454,7 +451,7 @@ listener = (httpRequest, httpResponse) -> session : session } - if config.DEBUG_DEVICES[currentRequest.session.deviceId] + if config.DEBUG dump = "# REQUEST #{new Date(Date.now())}\n" + JSON.stringify(httpRequest.headers) + "\n#{httpRequest.getBody()}\n\n" require('fs').appendFile("../debug/#{currentRequest.session.deviceId}.dump", dump, (err) -> throw err if err @@ -562,39 +559,5 @@ listener = (httpRequest, httpResponse) -> ) ) -cluster = require 'cluster' -numCPUs = require('os').cpus().length -if cluster.isMaster - db.connect((err) -> - throw err if err - db.redisClient.flushdb() - db.disconnect() - cluster.on('listening', (worker, address) -> - util.log("Worker #{worker.process.pid} listening to #{address.address}:#{address.port}") - ) - - cluster.on('exit', (worker, code, signal) -> - util.log("Worker #{worker.process.pid} died (#{worker.process.exitCode})") - setTimeout(()-> - cluster.fork() - , config.WORKER_RESPAWN_TIME) - ) - - for [1 .. numCPUs] - cluster.fork() - ) -else - options = { - key: fs.readFileSync('../config/httpscert.key'), - cert: fs.readFileSync('../config/httpscert.crt') - } - - httpServer = http.createServer(listener) - httpsServer = https.createServer(options, listener) - - db.connect((err) -> - throw err if err - httpServer.listen(config.ACS_PORT, config.ACS_INTERFACE) - httpsServer.listen(config.ACS_HTTPS_PORT, config.ACS_HTTPS_INTERFACE) - ) +exports.listener = listener diff --git a/lib/db.coffee b/lib/db.coffee index 24fbe71..f3c0633 100644 --- a/lib/db.coffee +++ b/lib/db.coffee @@ -70,7 +70,7 @@ connect = (callback) -> return callback(err) ) - exports.redisClient = redisClient = redis.createClient(config.REDIS_SOCKET) + exports.redisClient = redisClient = redis.createClient(config.REDIS_PORT, config.REDIS_HOST) redisClient.select(config.REDIS_DB, (err) -> if --callbackCounter == 0 or err callbackCounter = 0 diff --git a/lib/fs.coffee b/lib/fs.coffee index a377f51..26f368a 100644 --- a/lib/fs.coffee +++ b/lib/fs.coffee @@ -21,53 +21,29 @@ ### config = require './config' -util = require 'util' -http = require 'http' url = require 'url' mongodb = require 'mongodb' querystring = require 'querystring' +db = require './db' -db = null -cluster = require 'cluster' -numCPUs = require('os').cpus().length +listener = (request, response) -> + urlParts = url.parse(request.url, true) + if request.method == 'GET' + filename = querystring.unescape(urlParts.pathname.substring(1)) + gs = new mongodb.GridStore(db.mongoDb, filename, 'r', {}) + gs.open((err, gs) -> + if err + response.writeHead(404) + response.end() + return + stream = gs.stream(true) + response.writeHead(200, {'Content-Type' : 'application/octet-stream', 'Content-Length' : gs.length}) + stream.pipe(response) + ) + else + response.writeHead(405, {'Allow': 'GET'}) + response.end('405 Method Not Allowed') -if cluster.isMaster - cluster.on('listening', (worker, address) -> - util.log("Worker #{worker.process.pid} listening to #{address.address}:#{address.port}") - ) - cluster.on('exit', (worker, code, signal) -> - util.log("Worker #{worker.process.pid} died (#{worker.process.exitCode})") - setTimeout(()-> - cluster.fork() - , config.WORKER_RESPAWN_TIME) - ) - - for i in [1 .. numCPUs] - cluster.fork() -else - server = http.createServer((request, response) -> - urlParts = url.parse(request.url, true) - if request.method == 'GET' - filename = querystring.unescape(urlParts.pathname.substring(1)) - gs = new mongodb.GridStore(db, filename, 'r', {}) - gs.open((err, gs) -> - if err - response.writeHead(404) - response.end() - return - stream = gs.stream(true) - response.writeHead(200, {'Content-Type' : 'application/octet-stream', 'Content-Length' : gs.length}) - stream.pipe(response) - ) - else - response.writeHead(405, {'Allow': 'GET'}) - response.end('405 Method Not Allowed') - ) - - mongodb.MongoClient.connect("mongodb://#{config.MONGODB_SOCKET}/#{config.DATABASE_NAME}", config.MONGODB_OPTIONS, (err, _db) -> - throw err if err - db = _db - server.listen config.FILES_PORT, config.FILES_INTERFACE - ) +exports.listener = listener diff --git a/lib/nbi.coffee b/lib/nbi.coffee index 40facfa..bdecbe9 100644 --- a/lib/nbi.coffee +++ b/lib/nbi.coffee @@ -39,8 +39,6 @@ ### config = require './config' -util = require 'util' -http = require 'http' url = require 'url' common = require './common' db = require './db' @@ -62,343 +60,323 @@ QUERY_REGEX = /^\/([a-zA-Z0-9_]+s)\/?$/ DEVICE_PRESET_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/preset\/?$/ -cluster = require 'cluster' -numCPUs = require('os').cpus().length +listener = (request, response) -> + chunks = [] + bytes = 0 -if cluster.isMaster - cluster.on('listening', (worker, address) -> - util.log("Worker #{worker.process.pid} listening to #{address.address}:#{address.port}") - ) + request.addListener 'data', (chunk) -> + chunks.push(chunk) + bytes += chunk.length - cluster.on('exit', (worker, code, signal) -> - util.log("Worker #{worker.process.pid} died (#{worker.process.exitCode})") - setTimeout(()-> - cluster.fork() - , config.WORKER_RESPAWN_TIME) - ) + request.getBody = () -> + # Write all chunks into a Buffer + body = new Buffer(bytes) + offset = 0 + chunks.forEach((chunk) -> + chunk.copy(body, offset, 0, chunk.length) + offset += chunk.length + ) + return body - for [1 .. numCPUs] - cluster.fork() -else - server = http.createServer (request, response) -> - chunks = [] - bytes = 0 + request.addListener 'end', () -> + body = request.getBody() + urlParts = url.parse(request.url, true) + if PRESETS_REGEX.test(urlParts.pathname) + presetName = querystring.unescape(PRESETS_REGEX.exec(urlParts.pathname)[1]) + if request.method == 'PUT' + preset = JSON.parse(body) + preset._id = presetName - request.addListener 'data', (chunk) -> - chunks.push(chunk) - bytes += chunk.length - - request.getBody = () -> - # Write all chunks into a Buffer - body = new Buffer(bytes) - offset = 0 - chunks.forEach((chunk) -> - chunk.copy(body, offset, 0, chunk.length) - offset += chunk.length - ) - return body - - request.addListener 'end', () -> - body = request.getBody() - urlParts = url.parse(request.url, true) - if PRESETS_REGEX.test(urlParts.pathname) - presetName = querystring.unescape(PRESETS_REGEX.exec(urlParts.pathname)[1]) - if request.method == 'PUT' - preset = JSON.parse(body) - preset._id = presetName - - db.presetsCollection.save(preset, (err) -> - db.redisClient.del('presets', 'presets_hash', (err) -> - throw err if err - ) - if err - response.writeHead(500) - response.end(err) - return - response.writeHead(200) - response.end() + db.presetsCollection.save(preset, (err) -> + db.redisClient.del('presets', 'presets_hash', (err) -> + throw err if err ) - else if request.method == 'DELETE' - db.presetsCollection.remove({'_id' : presetName}, (err, removedCount) -> - db.redisClient.del('presets', 'presets_hash', (err) -> - throw err if err - ) - if err - response.writeHead(500) - response.end(err) - return - response.writeHead(200) - response.end() + if err + response.writeHead(500) + response.end(err) + return + response.writeHead(200) + response.end() + ) + else if request.method == 'DELETE' + db.presetsCollection.remove({'_id' : presetName}, (err, removedCount) -> + db.redisClient.del('presets', 'presets_hash', (err) -> + throw err if err ) - else - response.writeHead 405, {'Allow': 'PUT, DELETE'} - response.end('405 Method Not Allowed') - else if OBJECTS_REGEX.test(urlParts.pathname) - objectName = querystring.unescape(OBJECTS_REGEX.exec(urlParts.pathname)[1]) - if request.method == 'PUT' - object = JSON.parse(body) - object._id = objectName + if err + response.writeHead(500) + response.end(err) + return + response.writeHead(200) + response.end() + ) + else + response.writeHead 405, {'Allow': 'PUT, DELETE'} + response.end('405 Method Not Allowed') + else if OBJECTS_REGEX.test(urlParts.pathname) + objectName = querystring.unescape(OBJECTS_REGEX.exec(urlParts.pathname)[1]) + if request.method == 'PUT' + object = JSON.parse(body) + object._id = objectName - db.objectsCollection.save(object, (err) -> - db.redisClient.del('objects', 'presets_hash', (err) -> - throw err if err - ) - if err - response.writeHead(500) - response.end(err) - return - response.writeHead(200) - response.end() + db.objectsCollection.save(object, (err) -> + db.redisClient.del('objects', 'presets_hash', (err) -> + throw err if err ) - else if request.method == 'DELETE' - db.objectsCollection.remove({'_id' : objectName}, (err, removedCount) -> - db.redisClient.del('objects', 'presets_hash', (err) -> - throw err if err - ) - if err - response.writeHead(500) - response.end(err) - return - response.writeHead(200) - response.end() + if err + response.writeHead(500) + response.end(err) + return + response.writeHead(200) + response.end() + ) + else if request.method == 'DELETE' + db.objectsCollection.remove({'_id' : objectName}, (err, removedCount) -> + db.redisClient.del('objects', 'presets_hash', (err) -> + throw err if err ) - else - response.writeHead 405, {'Allow': 'PUT, DELETE'} - response.end('405 Method Not Allowed') - else if TAGS_REGEX.test(urlParts.pathname) - r = TAGS_REGEX.exec(urlParts.pathname) - deviceId = querystring.unescape(r[1]) - tag = querystring.unescape(r[2]) - if request.method == 'POST' - db.devicesCollection.update({'_id' : deviceId}, {'$addToSet' : {'_tags' : tag}}, {safe: true}, (err) -> - db.redisClient.del("#{deviceId}_presets_hash", (err) -> - throw err if err - ) - if err - response.writeHead(500) - response.end(err) - return - response.writeHead(200) - response.end() + if err + response.writeHead(500) + response.end(err) + return + response.writeHead(200) + response.end() + ) + else + response.writeHead 405, {'Allow': 'PUT, DELETE'} + response.end('405 Method Not Allowed') + else if TAGS_REGEX.test(urlParts.pathname) + r = TAGS_REGEX.exec(urlParts.pathname) + deviceId = querystring.unescape(r[1]) + tag = querystring.unescape(r[2]) + if request.method == 'POST' + db.devicesCollection.update({'_id' : deviceId}, {'$addToSet' : {'_tags' : tag}}, {safe: true}, (err) -> + db.redisClient.del("#{deviceId}_presets_hash", (err) -> + throw err if err ) - else if request.method == 'DELETE' - db.devicesCollection.update({'_id' : deviceId}, {'$pull' : {'_tags' : tag}}, {safe: true}, (err) -> - db.redisClient.del("#{deviceId}_presets_hash", (err) -> - throw err if err - ) - if err - response.writeHead(500) - response.end(err) - return - response.writeHead(200) - response.end() + if err + response.writeHead(500) + response.end(err) + return + response.writeHead(200) + response.end() + ) + else if request.method == 'DELETE' + db.devicesCollection.update({'_id' : deviceId}, {'$pull' : {'_tags' : tag}}, {safe: true}, (err) -> + db.redisClient.del("#{deviceId}_presets_hash", (err) -> + throw err if err ) - else - response.writeHead 405, {'Allow': 'POST, DELETE'} - response.end('405 Method Not Allowed') - else if DEVICE_TASKS_REGEX.test(urlParts.pathname) - if request.method == 'POST' - deviceId = querystring.unescape(DEVICE_TASKS_REGEX.exec(urlParts.pathname)[1]) - if body.length - task = JSON.parse(body) - task.device = deviceId - db.getAliases((aliases) -> - apiFunctions.insertTasks(task, aliases, (err) -> - db.redisClient.del("#{deviceId}_presets_hash", (err) -> - throw err if err - ) - if err - response.writeHead(500) - response.end(err) - return - - if urlParts.query.connection_request? - apiFunctions.connectionRequest(deviceId, (err) -> - if err - response.writeHead(202) - response.end() - else - apiFunctions.watchTask(task._id, config.DEVICE_ONLINE_THRESHOLD, (err) -> - if err - response.writeHead(202) - response.end() - return - response.writeHead(200) - response.end() - ) - ) - else - response.writeHead(202) - response.end() + if err + response.writeHead(500) + response.end(err) + return + response.writeHead(200) + response.end() + ) + else + response.writeHead 405, {'Allow': 'POST, DELETE'} + response.end('405 Method Not Allowed') + else if DEVICE_TASKS_REGEX.test(urlParts.pathname) + if request.method == 'POST' + deviceId = querystring.unescape(DEVICE_TASKS_REGEX.exec(urlParts.pathname)[1]) + if body.length + task = JSON.parse(body) + task.device = deviceId + db.getAliases((aliases) -> + apiFunctions.insertTasks(task, aliases, (err) -> + db.redisClient.del("#{deviceId}_presets_hash", (err) -> + throw err if err ) - ) - else if urlParts.query.connection_request? - # no task, send connection request only - apiFunctions.connectionRequest(deviceId, (err) -> - if err - response.writeHead 504 - response.end() - return - response.writeHead 200 - response.end() - ) - else - response.writeHead(400) - response.end() - else - response.writeHead 405, {'Allow': 'POST'} - response.end('405 Method Not Allowed') - else if DEVICE_PRESET_REGEX.test(urlParts.pathname) - deviceId = querystring.unescape(DEVICE_PRESET_REGEX.exec(urlParts.pathname)[1]) - if request.method is 'GET' - db.getPresetsObjectsAliases((allPresets, allObjects, allAliases) -> - presets.getDevicePreset(deviceId, allPresets, allObjects, allAliases, (devicePreset) -> - response.writeHead(200, {'Content-Type' : 'application/json'}) - response.end(JSON.stringify(devicePreset)) - ) - ) - else - response.writeHead 405, {'Allow': 'GET'} - response.end('405 Method Not Allowed') - else if TASKS_REGEX.test(urlParts.pathname) - r = TASKS_REGEX.exec(urlParts.pathname) - taskId = mongodb.ObjectID(querystring.unescape(r[1])) - action = try querystring.unescape(r[2]) - if not action? or action is '/' - if request.method == 'DELETE' - db.tasksCollection.remove({'_id' : taskId}, (err, removedCount) -> if err response.writeHead(500) response.end(err) return - response.writeHead(200) - response.end() - ) - else - response.writeHead 405, {'Allow': 'PUT DELETE'} - response.end('405 Method Not Allowed') - else if action is '/retry' - if request.method == 'POST' - db.tasksCollection.update({_id : taskId}, {$unset : {fault : 1}, $set : {timestamp : new Date()}}, (err, count) -> - # TODO need to invalidate presets hash for the device - response.writeHead(200) - response.end() - ) - else - response.writeHead 405, {'Allow': 'POST'} - response.end('405 Method Not Allowed') - else - response.writeHead(404) - response.end() - else if FILES_REGEX.test(urlParts.pathname) - filename = querystring.unescape(FILES_REGEX.exec(urlParts.pathname)[1]) - if request.method == 'PUT' - metadata = { - fileType : request.headers.filetype, - manufacturer : request.headers.manufacturer, - productClass : request.headers.productclass, - version : request.headers.version, - } - gs = new mongodb.GridStore(db.mongoDb, filename, 'w', {metadata : metadata}) - gs.open((err, gs) -> - gs.write(body, (err, res) -> - throw err if err - gs.close((err) -> - throw err if err - response.writeHead(201) + if urlParts.query.connection_request? + apiFunctions.connectionRequest(deviceId, (err) -> + if err + response.writeHead(202) + response.end() + else + apiFunctions.watchTask(task._id, config.DEVICE_ONLINE_THRESHOLD, (err) -> + if err + response.writeHead(202) + response.end() + return + response.writeHead(200) + response.end() + ) + ) + else + response.writeHead(202) response.end() - ) ) ) - else if request.method == 'DELETE' - mongodb.GridStore.unlink(db.mongoDb, filename, (err) -> + else if urlParts.query.connection_request? + # no task, send connection request only + apiFunctions.connectionRequest(deviceId, (err) -> + if err + response.writeHead 504 + response.end() + return + response.writeHead 200 + response.end() + ) + else + response.writeHead(400) + response.end() + else + response.writeHead 405, {'Allow': 'POST'} + response.end('405 Method Not Allowed') + else if DEVICE_PRESET_REGEX.test(urlParts.pathname) + deviceId = querystring.unescape(DEVICE_PRESET_REGEX.exec(urlParts.pathname)[1]) + if request.method is 'GET' + db.getPresetsObjectsAliases((allPresets, allObjects, allAliases) -> + presets.getDevicePreset(deviceId, allPresets, allObjects, allAliases, (devicePreset) -> + response.writeHead(200, {'Content-Type' : 'application/json'}) + response.end(JSON.stringify(devicePreset)) + ) + ) + else + response.writeHead 405, {'Allow': 'GET'} + response.end('405 Method Not Allowed') + else if TASKS_REGEX.test(urlParts.pathname) + r = TASKS_REGEX.exec(urlParts.pathname) + taskId = mongodb.ObjectID(querystring.unescape(r[1])) + action = try querystring.unescape(r[2]) + if not action? or action is '/' + if request.method == 'DELETE' + db.tasksCollection.remove({'_id' : taskId}, (err, removedCount) -> + if err + response.writeHead(500) + response.end(err) + return response.writeHead(200) response.end() ) else - response.writeHead 405, {'Allow': 'PUT, DELETE'} + response.writeHead 405, {'Allow': 'PUT DELETE'} response.end('405 Method Not Allowed') - else if PING_REGEX.test(urlParts.pathname) - host = querystring.unescape(PING_REGEX.exec(urlParts.pathname)[1]) - require('child_process').exec("ping -w 1 -i 0.2 -c 3 #{host}", (err, stdout, stderr) -> - if err? - response.writeHead(404, {'Cache-Control' : 'no-cache'}) + else if action is '/retry' + if request.method == 'POST' + db.tasksCollection.update({_id : taskId}, {$unset : {fault : 1}, $set : {timestamp : new Date()}}, (err, count) -> + # TODO need to invalidate presets hash for the device + response.writeHead(200) response.end() - return - response.writeHead(200, {'Content-Type' : 'text/plain', 'Cache-Control' : 'no-cache'}) - response.end(stdout) - ) - else if QUERY_REGEX.test(urlParts.pathname) - collectionName = QUERY_REGEX.exec(urlParts.pathname)[1] - if request.method not in ['GET', 'HEAD'] - response.writeHead 405, {'Allow' : 'GET, HEAD'} - response.end('405 Method Not Allowed') - return - collection = db["#{collectionName}Collection"] - if not collection? - response.writeHead 404 - response.end('404 Not Found') - return - - func = (aliases) -> - if urlParts.query.query? - q = JSON.parse(urlParts.query.query) - else - q = {} - q = query.expand(q, aliases) if collectionName is 'devices' - - if urlParts.query.projection? - projection = {} - for p in urlParts.query.projection.split(',') - p = p.trim() - projection[p] = 1 - if collectionName is 'devices' - for k,v of aliases - if k == p or common.startsWith(k, "#{p}.") - projection[a] = 1 for a in v - - cur = collection.find(q, projection, {batchSize : 50}) - if urlParts.query.sort? - s = JSON.parse(urlParts.query.sort) - sort = {} - for k,v of s - if aliases[k]? - sort[a] = v for a in aliases[k] - else - sort[k] = v - cur.sort(sort) - - cur.skip(parseInt(urlParts.query.skip)) if urlParts.query.skip? - cur.limit(parseInt(urlParts.query.limit)) if urlParts.query.limit? - cur.count((err, total) -> - response.writeHead(200, {'Content-Type' : 'application/json', 'total' : total}) - if request.method is 'HEAD' - response.end() - return - response.write("[\n") - i = 0 - cur.each((err, item) -> - if item is null - response.end("\n]") - else - response.write(",\n") if i++ - apiFunctions.addAliases(item, aliases) if collectionName is 'devices' - response.write(JSON.stringify(item)) - ) - ) - - if collectionName is 'devices' - db.getAliases((aliases) -> - func(aliases) ) else - func({}) + response.writeHead 405, {'Allow': 'POST'} + response.end('405 Method Not Allowed') else + response.writeHead(404) + response.end() + else if FILES_REGEX.test(urlParts.pathname) + filename = querystring.unescape(FILES_REGEX.exec(urlParts.pathname)[1]) + if request.method == 'PUT' + metadata = { + fileType : request.headers.filetype, + manufacturer : request.headers.manufacturer, + productClass : request.headers.productclass, + version : request.headers.version, + } + + gs = new mongodb.GridStore(db.mongoDb, filename, 'w', {metadata : metadata}) + gs.open((err, gs) -> + gs.write(body, (err, res) -> + throw err if err + gs.close((err) -> + throw err if err + response.writeHead(201) + response.end() + ) + ) + ) + else if request.method == 'DELETE' + mongodb.GridStore.unlink(db.mongoDb, filename, (err) -> + response.writeHead(200) + response.end() + ) + else + response.writeHead 405, {'Allow': 'PUT, DELETE'} + response.end('405 Method Not Allowed') + else if PING_REGEX.test(urlParts.pathname) + host = querystring.unescape(PING_REGEX.exec(urlParts.pathname)[1]) + require('child_process').exec("ping -w 1 -i 0.2 -c 3 #{host}", (err, stdout, stderr) -> + if err? + response.writeHead(404, {'Cache-Control' : 'no-cache'}) + response.end() + return + response.writeHead(200, {'Content-Type' : 'text/plain', 'Cache-Control' : 'no-cache'}) + response.end(stdout) + ) + else if QUERY_REGEX.test(urlParts.pathname) + collectionName = QUERY_REGEX.exec(urlParts.pathname)[1] + if request.method not in ['GET', 'HEAD'] + response.writeHead 405, {'Allow' : 'GET, HEAD'} + response.end('405 Method Not Allowed') + return + collection = db["#{collectionName}Collection"] + if not collection? response.writeHead 404 response.end('404 Not Found') + return - db.connect((err) -> - throw err if err - server.listen config.API_PORT, config.API_INTERFACE - ) + func = (aliases) -> + if urlParts.query.query? + q = JSON.parse(urlParts.query.query) + else + q = {} + q = query.expand(q, aliases) if collectionName is 'devices' + + if urlParts.query.projection? + projection = {} + for p in urlParts.query.projection.split(',') + p = p.trim() + projection[p] = 1 + if collectionName is 'devices' + for k,v of aliases + if k == p or common.startsWith(k, "#{p}.") + projection[a] = 1 for a in v + + cur = collection.find(q, projection, {batchSize : 50}) + if urlParts.query.sort? + s = JSON.parse(urlParts.query.sort) + sort = {} + for k,v of s + if aliases[k]? + sort[a] = v for a in aliases[k] + else + sort[k] = v + cur.sort(sort) + + cur.skip(parseInt(urlParts.query.skip)) if urlParts.query.skip? + cur.limit(parseInt(urlParts.query.limit)) if urlParts.query.limit? + cur.count((err, total) -> + response.writeHead(200, {'Content-Type' : 'application/json', 'total' : total}) + if request.method is 'HEAD' + response.end() + return + response.write("[\n") + i = 0 + cur.each((err, item) -> + if item is null + response.end("\n]") + else + response.write(",\n") if i++ + apiFunctions.addAliases(item, aliases) if collectionName is 'devices' + response.write(JSON.stringify(item)) + ) + ) + + if collectionName is 'devices' + db.getAliases((aliases) -> + func(aliases) + ) + else + func({}) + else + response.writeHead 404 + response.end('404 Not Found') + + +exports.listener = listener diff --git a/lib/server.coffee b/lib/server.coffee new file mode 100644 index 0000000..b80acd2 --- /dev/null +++ b/lib/server.coffee @@ -0,0 +1,47 @@ +### +# Copyright 2013, 2014 Zaid Abdulla +# +# GenieACS is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# GenieACS is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with GenieACS. If not, see . +### + +config = require './config' +db = require './db' + +service = config.argv[0] +if not service? + throw new Error('Missing argument cwmp, fs, or nbi') + +networkInterface = config["#{service.toUpperCase()}_INTERFACE"] +port = config["#{service.toUpperCase()}_PORT"] +useHttps = config["#{service.toUpperCase()}_SSL"] + +listener = require("./#{service}").listener + +if useHttps + path = require 'path' + fs = require 'fs' + httpsKey = path.resolve(config.CONFIG_DIR, "#{service}.key") + httpsCert = path.resolve(config.CONFIG_DIR, "#{service}.crt") + options = { + key: fs.readFileSync(httpsKey), + cert: fs.readFileSync(httpsCert) + } + server = require('https').createServer(options, listener) +else + server = require('http').createServer(listener) + +db.connect((err) -> + throw err if err + server.listen(port, networkInterface) +) diff --git a/package.json b/package.json index d155f7e..95ee9a7 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "scripts": { "prepublish": "npm run compile", "compile": "coffee -cb lib/*.coffee test/*.coffee tools/*.coffee", - "configure": "cp ./config/config-sample.json ./config/config.json; cp ./config/parameters-sample.json ./config/parameters.json; cp ./config/custom_commands-sample.json ./config/custom_commands.json; cp ./config/auth-sample.js ./config/auth.js; cp ./config/httpscert-sample.key ./config/httpscert.key; cp ./config/httpscert-sample.crt ./config/httpscert.crt", + "configure": "cp ./config/config-sample.json ./config/config.json; cp ./config/parameters-sample.json ./config/parameters.json; cp ./config/custom_commands-sample.json ./config/custom_commands.json; cp ./config/auth-sample.js ./config/auth.js", "install": "npm run configure", "test": "node test" }