Initial implementation of user extensions

This commit is contained in:
Zaid Abdulla committed 2016-04-19 03:18:48 +03:00
1 parent 8ac94474aa
commit 27e9882071
4 files changed
+158 -9

No files matched your search

+27
View File
@@ -0,0 +1,27 @@
###
# Copyright 2013-2016 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 <http://www.gnu.org/licenses/>.
###
process.chdir(process.argv[2])
script = require("#{process.argv[2]}/#{process.argv[3]}")
process.on('message', (message) ->
script[message[1]](message.slice(2), (err, res) ->
throw err if err
process.send([message[0], res])
)
)
+55
View File
@@ -0,0 +1,55 @@
###
# Copyright 2013-2016 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 <http://www.gnu.org/licenses/>.
###
childProcess = require 'child_process'
crypto = require 'crypto'
path = require 'path'
config = require './config'
TIMEOUT = 5000
processes = {}
queue = {}
messageHandler = (message) ->
func = queue[message[0]]
delete queue[message[0]]
func(null, message[1])
run = (args, callback) ->
scriptName = args[0]
if not processes[scriptName]?
cwd = path.resolve(config.get('CONFIG_DIR'), 'extensions')
p = childProcess.fork('lib/extension-wrapper', [cwd, scriptName])
p.on('message', messageHandler)
processes[scriptName] = p
id = crypto.randomBytes(8).toString('hex')
queue[id] = callback
setTimeout(() ->
if id of queue
delete queue[id]
return callback(new Error('Extension timeout'))
, TIMEOUT)
processes[scriptName].send([id, args.slice(1)])
exports.run = run
+23 -5
View File
@@ -22,7 +22,7 @@ device = require './device'
# Used for throwing to exit user script # Used for throwing to exit user script
class Declare; class Exit;
sandbox = { sandbox = {
@@ -31,9 +31,25 @@ sandbox = {
revision: null revision: null
maxRevision: null maxRevision: null
declarations: null declarations: null
extensionsCache: null
extensions: null
context: vm.createContext() context: vm.createContext()
} }
sandbox.context.ext = () ->
if sandbox.extensions?.length
throw new Error('Ext function should not be called from within a try/catch block')
extCall = (String(a) for a in arguments)
key = JSON.stringify(extCall)
if not sandbox.extensionsCache[sandbox.revision]?[key]?
sandbox.extensions = [extCall]
throw new Exit()
return sandbox.extensionsCache[sandbox.revision][key]
sandbox.context.declare = (decs) -> sandbox.context.declare = (decs) ->
if ++ sandbox.revision > sandbox.maxRevision + 1 if ++ sandbox.revision > sandbox.maxRevision + 1
throw new Error('Declare function should not be called from within a try/catch block') throw new Error('Declare function should not be called from within a try/catch block')
@@ -42,7 +58,7 @@ sandbox.context.declare = (decs) ->
for k, v of decs for k, v of decs
sandbox.declarations.push([common.parsePath(k)].concat(toIndexDeclaration(v))) sandbox.declarations.push([common.parsePath(k)].concat(toIndexDeclaration(v)))
throw new Declare() throw new Exit()
res = {} res = {}
for k, v of decs for k, v of decs
@@ -99,20 +115,22 @@ toIndexDeclaration = (keyDeclaraiton) ->
return indexDeclaraiton return indexDeclaraiton
run = (script, args, deviceData, startRevision, maxRevision) -> run = (script, args, deviceData, extensionsCache, startRevision, maxRevision) ->
sandbox.deviceData = deviceData sandbox.deviceData = deviceData
sandbox.extensionsCache = extensionsCache
sandbox.args = args sandbox.args = args
sandbox.revision = startRevision sandbox.revision = startRevision
sandbox.maxRevision = maxRevision sandbox.maxRevision = maxRevision
sandbox.declarations = [] sandbox.declarations = []
sandbox.extensions = null
try try
ret = script.runInNewContext(sandbox.context) ret = script.runInNewContext(sandbox.context)
return {done: true, declarations: sandbox.declarations, returnValue: ret} return {done: true, declarations: sandbox.declarations, returnValue: ret}
catch err catch err
throw err if err not instanceof Declare throw err if err not instanceof Exit
return {done: false, declarations: sandbox.declarations} return {done: false, declarations: sandbox.declarations, extensions: sandbox.extensions}
exports.run = run exports.run = run
+53 -4
View File
@@ -23,6 +23,7 @@ db = require './db'
device = require './device' device = require './device'
sandbox = require './sandbox' sandbox = require './sandbox'
cache = require './cache' cache = require './cache'
extensions = require './extensions'
init = (deviceId, cwmpVersion, timeout) -> init = (deviceId, cwmpVersion, timeout) ->
@@ -40,6 +41,7 @@ init = (deviceId, cwmpVersion, timeout) ->
revisions: [0] revisions: [0]
rpcCount: 0 rpcCount: 0
cycle: 0 cycle: 0
extensionsCache: []
} }
return sessionData return sessionData
@@ -100,6 +102,7 @@ addProvisions = (sessionData, provisions) ->
sessionData.rpcCount = 0 sessionData.rpcCount = 0
sessionData.revisions = [0] sessionData.revisions = [0]
device.collapse(sessionData.deviceData, 1) device.collapse(sessionData.deviceData, 1)
sessionData.extensionsCache.length = 0
clearProvisions = (sessionData) -> clearProvisions = (sessionData) ->
@@ -109,6 +112,7 @@ clearProvisions = (sessionData) ->
sessionData.rpcCount = 0 sessionData.rpcCount = 0
sessionData.revisions = [0] sessionData.revisions = [0]
device.collapse(sessionData.deviceData, 1) device.collapse(sessionData.deviceData, 1)
sessionData.extensionsCache.length = 0
generateRpcRequest = (sessionData) -> generateRpcRequest = (sessionData) ->
@@ -334,6 +338,28 @@ commitVirtualParameter = (sessionData, parameterDeclaration, revision, update) -
device.set(sessionData.deviceData, parameterDeclaration[0], revision, v) device.set(sessionData.deviceData, parameterDeclaration[0], revision, v)
runExtensions = (sessionData, revision, _extensions, callback) ->
sessionData.extensionsCache[revision] ?= {}
obj = {}
for e in _extensions
obj[JSON.stringify(e)] = e
counter = 1
for k, v of obj
++ counter
do (k, v) ->
extensions.run(v, (err, res) ->
sessionData.extensionsCache[revision][k] = res
if -- counter == 0 or err
counter = 0
return callback(err)
)
if -- counter == 0
return callback()
rpcRequest = (sessionData, declarations, callback) -> rpcRequest = (sessionData, declarations, callback) ->
allDeclarations = declarations?.slice() ? [] allDeclarations = declarations?.slice() ? []
@@ -341,6 +367,7 @@ rpcRequest = (sessionData, declarations, callback) ->
return callback(err) if err or presetsHash != sessionData.presetsHash return callback(err) if err or presetsHash != sessionData.presetsHash
done = true done = true
_extensions = []
for provision in sessionData.provisions for provision in sessionData.provisions
if not provisions[provision[0]]? if not provisions[provision[0]]?
switch provision[0] switch provision[0]
@@ -355,13 +382,22 @@ rpcRequest = (sessionData, declarations, callback) ->
allDeclarations.push([[['Tags', provision[1]], null, null, null, null, null, null, null, [provision[2], 'xsd:boolean']]]) allDeclarations.push([[['Tags', provision[1]], null, null, null, null, null, null, null, [provision[2], 'xsd:boolean']]])
continue continue
ret = sandbox.run(provisions[provision[0]].script, provision.slice(1), sessionData.deviceData, 0, sessionData.revisions[0] >> 1) ret = sandbox.run(provisions[provision[0]].script, provision.slice(1), sessionData.deviceData, sessionData.extensionsCache, 0, sessionData.revisions[0] >> 1)
if ret.extensions?.length
_extensions = _extensions.concat(ret.extensions)
done &&= ret.done done &&= ret.done
allDeclarations = allDeclarations.concat(ret.declarations) allDeclarations = allDeclarations.concat(ret.declarations)
if done and (not allDeclarations?.length or sessionData.revisions[0] > 1) if done and (not allDeclarations?.length or sessionData.revisions[0] > 1)
return callback() return callback()
if _extensions.length
runExtensions(sessionData, (sessionData.revisions[0] >> 1), _extensions, (err) ->
return callback(err) if err
return rpcRequest(sessionData, declarations, callback)
)
return
doVirtualParameters = (iter, virtualParameterDeclarations, cb) -> doVirtualParameters = (iter, virtualParameterDeclarations, cb) ->
if not virtualParameterDeclarations? if not virtualParameterDeclarations?
virtualParameterDeclarations = extractVirtualParameterDeclarations(sessionData) virtualParameterDeclarations = extractVirtualParameterDeclarations(sessionData)
@@ -371,16 +407,27 @@ rpcRequest = (sessionData, declarations, callback) ->
sessionData.revisions[iter] ?= 0 sessionData.revisions[iter] ?= 0
decs = [] decs = []
virtualParameterUpdates = [] virtualParameterUpdates = []
_extensions = []
lastRevision = sessionData.revisions.reduce((a, b) -> (a >> 1) + (b >> 1))
firstRevision = lastRevision - (sessionData.revisions[sessionData.revisions.length - 1] >> 1)
for vpd, i in virtualParameterDeclarations for vpd, i in virtualParameterDeclarations
vpName = vpd[0][1] vpName = vpd[0][1]
continue if not virtualParameters[vpName]? continue if not virtualParameters[vpName]?
lastRevision = sessionData.revisions.reduce((a, b) -> (a >> 1) + (b >> 1)) ret = sandbox.run(virtualParameters[vpName].script, [vpd.slice(1)], sessionData.deviceData, sessionData.extensionsCache, firstRevision, lastRevision)
firstRevision = lastRevision - (sessionData.revisions[sessionData.revisions.length - 1] >> 1) if ret.extensions?.length
ret = sandbox.run(virtualParameters[vpName].script, [vpd.slice(1)], sessionData.deviceData, firstRevision, lastRevision) _extensions = _extensions.concat(ret.extensions)
decs = decs.concat(ret.declarations) decs = decs.concat(ret.declarations)
if ret.done if ret.done
virtualParameterUpdates.push(ret.returnValue) virtualParameterUpdates.push(ret.returnValue)
if _extensions.length
runExtensions(sessionData, lastRevision, _extensions, (err) ->
return callback(err) if err
return doVirtualParameters(iter, virtualParameterDeclarations, cb)
)
return
if virtualParameterUpdates.length == virtualParameterDeclarations.length if virtualParameterUpdates.length == virtualParameterDeclarations.length
rev = sessionData.revisions.reduce((a, b) -> (a >> 1) + (b >> 1)) rev = sessionData.revisions.reduce((a, b) -> (a >> 1) + (b >> 1))
for vpd, i in virtualParameterDeclarations for vpd, i in virtualParameterDeclarations
@@ -392,6 +439,8 @@ rpcRequest = (sessionData, declarations, callback) ->
sessionData.revisions.length = iter sessionData.revisions.length = iter
rev = sessionData.revisions.reduce((a, b) -> (a >> 1) + (b >> 1)) rev = sessionData.revisions.reduce((a, b) -> (a >> 1) + (b >> 1))
device.collapse(sessionData.deviceData, rev + 1) device.collapse(sessionData.deviceData, rev + 1)
if sessionData.extensionsCache.length > rev + 1
sessionData.extensionsCache.length = rev + 1
return cb(true) return cb(true)
applyDeclarations(iter, decs, () -> applyDeclarations(iter, decs, () ->