From c3a956ad2770d119898420fb1c711d7cb3cbd5fa Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Sun, 6 Mar 2016 04:25:37 +0300 Subject: [PATCH] Remove obsolete code --- lib/config.coffee | 2 - lib/custom-commands.coffee | 61 ------ lib/db.coffee | 31 +-- lib/mongo-query.coffee | 141 -------------- lib/nbi.coffee | 23 +-- lib/presets.coffee | 336 -------------------------------- lib/tasks.coffee | 379 ------------------------------------- lib/update-device.coffee | 217 --------------------- 8 files changed, 6 insertions(+), 1184 deletions(-) delete mode 100644 lib/custom-commands.coffee delete mode 100644 lib/mongo-query.coffee delete mode 100644 lib/presets.coffee delete mode 100644 lib/tasks.coffee delete mode 100644 lib/update-device.coffee diff --git a/lib/config.coffee b/lib/config.coffee index d45a39a..5ba2b69 100644 --- a/lib/config.coffee +++ b/lib/config.coffee @@ -164,8 +164,6 @@ exports.PARAMETERS = require(path.resolve(allConfig.CONFIG_DIR, 'parameters')) # load authentication scripts exports.auth = require(path.resolve(allConfig.CONFIG_DIR, 'auth')) -exports.CUSTOM_COMMANDS = require(path.resolve(allConfig.CONFIG_DIR, 'custom_commands')) - exports.get = get exports.allConfig = allConfig diff --git a/lib/custom-commands.coffee b/lib/custom-commands.coffee deleted file mode 100644 index 711dea7..0000000 --- a/lib/custom-commands.coffee +++ /dev/null @@ -1,61 +0,0 @@ -### -# 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 . -### - -path = require 'path' -config = require './config' -common = require './common' - - -FILES = {} -getFile = (filename) -> - if filename not of FILES - FILES[filename] = require(path.resolve(config.get('CONFIG_DIR'), "custom_commands/#{filename}")) - - return FILES[filename] - - -exports.execute = (deviceId, command, callback) -> - args = command.split(/\s+/) - f = args.shift() - c = args.shift() - - file = getFile(f) - file[c](deviceId, args.join(' '), callback) - - -exports.getFileCommands = (filename) -> - commands = [] - f = getFile(filename) - for k,v of f - commands.push(k) - return commands - - -exports.getDeviceCustomCommands = (deviceId) -> - commands = {} - for k,v of config.CUSTOM_COMMANDS - if eval(v).test(deviceId) - commands[k] = exports.getFileCommands(k) - return commands - - -exports.getDeviceCustomCommandNames = (deviceId) -> - commands = [] - for k,v of config.CUSTOM_COMMANDS - if eval(v).test(deviceId) - commands.push(k) - return commands diff --git a/lib/db.coffee b/lib/db.coffee index 1b4b239..1735783 100644 --- a/lib/db.coffee +++ b/lib/db.coffee @@ -28,29 +28,6 @@ presetsCollection = null objectsCollection = null -# Only flush redis DB if aliases or custom commands config has changed -flushRedis = (callback) -> - redisClient.get('config_hash', (err, res) -> - return callback(err) if err - - hash = require('crypto') - .createHash('md5') - .update(JSON.stringify(config.PARAMETERS)) - .update(JSON.stringify(config.CUSTOM_COMMANDS)) - .digest('hex') - - if res == hash - return callback() - - redisClient.flushdb((err) -> - return callback(err) if err - redisClient.set('config_hash', hash, (err, res) -> - return callback(err) - ) - ) - ) - - connect = (callback) -> callbackCounter = 6 mongodb.MongoClient.connect(config.get('MONGODB_CONNECTION_URL'), {db:{w:1},server:{autoReconnect:true}}, (err, db) -> @@ -96,15 +73,9 @@ connect = (callback) -> exports.redisClient = redisClient = redis.createClient(config.get('REDIS_PORT'), config.get('REDIS_HOST')) redisClient.select(config.get('REDIS_DB'), (err) -> - if err + if --callbackCounter == 0 or err callbackCounter = 0 return callback(err) - - flushRedis((err) -> - if --callbackCounter == 0 or err - callbackCounter = 0 - return callback(err) - ) ) ) diff --git a/lib/mongo-query.coffee b/lib/mongo-query.coffee deleted file mode 100644 index 71d91f1..0000000 --- a/lib/mongo-query.coffee +++ /dev/null @@ -1,141 +0,0 @@ -### -# 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 . -# -# This file incorporates work covered by the following copyright and -# permission notice: -# -# Copyright 2013 Fanoos Telecom -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -### - -common = require './common' - - -test = (obj, query) -> - for k,v of query - if k.charAt(0) == '$' # this is a logical operator - res = testExpressions(obj, v, k) - else - value = common.getParamValueFromPath(obj, k) - - if common.typeOf(v) isnt common.OBJECT_TYPE - # TODO comparing array to regex, array to array, and object to object - if common.typeOf(value) is common.ARRAY_TYPE - res = value.indexOf(v) != -1 - else - if common.typeOf(v) is common.REGEXP_TYPE - res = v.test(value) - else - res = v == value - else - for k2,v2 of v - switch k2 - when '$ne' - if common.typeOf(value) is common.ARRAY_TYPE - res = value.indexOf(v2) == -1 - else - res = value != v2 - when '$lt' - res = value < v2 - when '$lte' - res = value <= v2 - when '$gt' - res = value > v2 - when '$gte' - res = value >= v2 - when '$regex' - res = v2.test(value) - when '$in' - throw new Error('Operator not supported') - when '$nin' - throw new Error('Operator not supported') - when '$all' - throw new Error('Operator not supported') - when '$exists' - throw new Error('Operator not supported') - else - throw new Error('Operator not supported') - - if not res - return false - - return true - - -testExpressions = (obj, expressions, lop) -> - for f in expressions - res = test(obj, f) - - switch lop - when '$and' - return false if not res - when '$or' - return true if res - when '$nor' - return false if res - - return switch lop - when '$and' then true - when '$or' then false - when '$nor' then true - else throw new Error('Unknown logical operator') - - -# generate field projection from a query -projection = (query, proj) -> - for k,v of query - if k.charAt(0) == '$' # this is a logical operator - for q in v - projection(q, proj) - else - proj[k] = 1 - -# optimize projection by removing overlaps -optimizeProjection = (obj) -> - keys = Object.keys(obj).sort() - return if keys.length <= 1 - i = 1 - while i < keys.length - a = keys[i-1] - b = keys[i] - if common.startsWith(b, a) - if b.charAt(a.length) == '.' or b.charAt(a.length - 1) == '.' - delete obj[b] - keys.splice(i--, 1) - ++ i - return - - -exports.test = test -exports.projection = projection -exports.optimizeProjection = optimizeProjection diff --git a/lib/nbi.coffee b/lib/nbi.coffee index 4aa1db0..a0eac4c 100644 --- a/lib/nbi.coffee +++ b/lib/nbi.coffee @@ -1,5 +1,5 @@ ### -# Copyright 2013, 2014 Zaid Abdulla +# 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 @@ -38,15 +38,15 @@ # THE SOFTWARE. ### -config = require './config' url = require 'url' -common = require './common' -db = require './db' mongodb = require 'mongodb' querystring = require 'querystring' + +config = require './config' +common = require './common' +db = require './db' query = require './query' apiFunctions = require './api-functions' -presets = require './presets' # regular expression objects DEVICE_TASKS_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/tasks\/?$/ @@ -57,7 +57,6 @@ OBJECTS_REGEX = /^\/objects\/([a-zA-Z0-9\-\_\%]+)\/?$/ FILES_REGEX = /^\/files\/([a-zA-Z0-9\%\!\*\'\(\)\;\:\@\&\=\+\$\,\?\#\[\]\-\_\.\~]+)\/?$/ PING_REGEX = /^\/ping\/([a-zA-Z0-9\-\_\.]+)\/?$/ QUERY_REGEX = /^\/([a-zA-Z0-9_]+s)\/?$/ -DEVICE_PRESET_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/preset\/?$/ DELETE_DEVICE_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/?$/ @@ -248,18 +247,6 @@ listener = (request, response) -> 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])) diff --git a/lib/presets.coffee b/lib/presets.coffee deleted file mode 100644 index 8e3293e..0000000 --- a/lib/presets.coffee +++ /dev/null @@ -1,336 +0,0 @@ -### -# 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 . -# -# This file incorporates work covered by the following copyright and -# permission notice: -# -# Copyright 2013 Fanoos Telecom -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -### - -config = require './config' -common = require './common' -db = require './db' -mongoQuery = require './mongo-query' -query = require './query' -log = require('util').log -customCommands = require './custom-commands' - - -exports.calculatePresetsHash = (presets, objects) -> - crypto = require('crypto') - hash = crypto.createHash('md5').update(JSON.stringify(presets) + JSON.stringify(objects)).digest('hex') - return hash - - -matchObject = (object, param) -> - flatObject = common.flattenObject(object) - if object._keys?.length - keys = object._keys - else - # Consider all parameters as keys if none are defined - keys = (k for k in Object.keys(flatObject) when k[0] != '_') - - for k in keys - p = common.getParamValueFromPath(param, k) - return false if not p? - v = common.matchType(p._value, flatObject[k]) - - if p._value != common.matchType(p._value, flatObject[k]) - return false - - return true - - -exports.getDevicePreset = (deviceId, presets, objects, aliases, callback) -> - deviceCustomCommands = customCommands.getDeviceCustomCommands(deviceId) - # only fetch relevant params - projection = {_id : 1} - for p in presets - if common.typeOf(p.precondition) is common.STRING_TYPE - p.precondition = query.expand(JSON.parse(p.precondition), aliases) - else - # Accept an object for backward compatiblity - p.precondition = query.expand(p.precondition ? {}, aliases) - mongoQuery.projection(p.precondition, projection) - - for c in p.configurations - switch c.type - when 'value', 'age' - projection[c.name] = 1 - when 'custom_command_value', 'custom_command_age' - commandfile = c.command.split(' ', 1) - if commandfile of deviceCustomCommands - projection["_customCommands.#{c.command.split(' ', 1)}"] = 1 - when 'software_version' - projection['Device.DeviceInfo.SoftwareVersion'] = 1 - projection['InternetGatewayDevice.DeviceInfo.SoftwareVersion'] = 1 - when 'add_tag', 'delete_tag' - projection['_tags'] = 1 - when 'add_object', 'delete_object' - projection[c.name] = 1 - else - throw new Error('Unknown configuration type') - - mongoQuery.optimizeProjection(projection) - - db.devicesCollection.findOne({'_id' : deviceId}, projection, (err, device) -> - throw err if err - devicePresets = [] - for p in presets - if mongoQuery.test(device, p.precondition) - devicePresets.push(p) - - configurations = accumulateConfigurations(devicePresets, objects) - - getParamDetails = (device, path) -> - p = common.getParamValueFromPath(device, path) - return undefined if not p? - details = {} - for k,v of p - details[k] = v if k[0] == '_' - return details - - devicePreset = {} - for c in configurations - switch c.type - when 'value' - devicePreset.parameters ?= {} - devicePreset.parameters[c.name] ?= {} - devicePreset.parameters[c.name].preset ?= {} - devicePreset.parameters[c.name].preset.value = c.value - devicePreset.parameters[c.name].current = getParamDetails(device, c.name) - - when 'age' - devicePreset.parameters ?= {} - devicePreset.parameters[c.name] ?= {} - devicePreset.parameters[c.name].preset ?= {} - devicePreset.parameters[c.name].preset.expiry = parseInt(c.age) - devicePreset.parameters[c.name].current = getParamDetails(device, c.name) - - when 'add_tag' - devicePreset.tags ?= {} - devicePreset.tags[c.tag] = 2 | c.tag in (device._tags ? []) - - when 'delete_tag' - devicePreset.tags ?= {} - devicePreset.tags[c.tag] = 0 | c.tag in (device._tags ? []) - - when 'add_object' - devicePreset.objects ?= {} - devicePreset.objects[c.name] ?= {} - devicePreset.objects[c.name][c.object] ?= {} - devicePreset.objects[c.name][c.object].preset = objects[c.object] - continue if not param = common.getParamValueFromPath(device, c.name) - devicePreset.objects[c.name][c.object].current ?= {} - for k,p of param - continue if k[0] == '_' - if matchObject(objects[c.object], p) - devicePreset.objects[c.name][c.object].current[k] = p - - when 'delete_object' - devicePreset.objects ?= {} - devicePreset.objects[c.name] ?= {} - devicePreset.objects[c.name][c.object] ?= {} - continue if not param = common.getParamValueFromPath(device, c.name) - devicePreset.objects[c.name][c.object].current ?= {} - for k,p of param - continue if k[0] == '_' - if matchObject(objects[c.object], p) - devicePreset.objects[c.name][c.object].current[k] = p - - when 'custom_command_value' - [filename, commandName] = c.command.split(' ', 2) - devicePreset.customCommands ?= {} - devicePreset.customCommands[filename] ?= {} - devicePreset.customCommands[filename].preset ?= {} - devicePreset.customCommands[filename].preset.value = c.value - devicePreset.customCommands[filename].preset.valueCommand = c.command - continue if not deviceCustomCommands[filename]? or commandName not in deviceCustomCommands[filename] - if cmd = common.getParamValueFromPath(device, "_customCommands.#{filename}") - devicePreset.customCommands[filename].current = cmd - else - devicePreset.customCommands[filename].current ?= {} - - when 'custom_command_age' - [filename, commandName] = c.command.split(' ', 2) - devicePreset.customCommands ?= {} - devicePreset.customCommands[filename] ?= {} - devicePreset.customCommands[filename].preset ?= {} - devicePreset.customCommands[filename].preset.expiry = parseInt(c.age) - devicePreset.customCommands[filename].preset.expiryCommand = c.command - continue if not deviceCustomCommands[filename]? or commandName not in deviceCustomCommands[filename] - if cmd = common.getParamValueFromPath(device, "_customCommands.#{filename}") - devicePreset.customCommands[filename].current = cmd - else - devicePreset.customCommands[filename].current ?= {} - - when 'software_version' - devicePreset.softwareVersion ?= {} - devicePreset.softwareVersion.preset = c.software_version - if device?.Device? # TR-181 data model - devicePreset.softwareVersion.current = common.getParamValueFromPath(device, 'Device.DeviceInfo.SoftwareVersion') - else # TR-098 data model - devicePreset.softwareVersion.current = common.getParamValueFromPath(device, 'InternetGatewayDevice.DeviceInfo.SoftwareVersion') - - else - throw new Error('Unknown configuration type') - - callback(devicePreset) - ) - - -exports.processDevicePreset = (deviceId, devicePreset, callback) -> - PRESETS_TIME_PADDING = config.get('PRESETS_TIME_PADDING') - now = new Date() - expiry = config.get('PRESETS_CACHE_DURATION', deviceId) - getParameterValues = [] - setParameterValues = [] - addTags = [] - deleteTags = [] - taskList = [] - - # Parameters - for parameterPath, parameterDetails of devicePreset.parameters ? {} - continue if not parameterDetails.current? - expiry = Math.min(expiry, parameterDetails.preset.expiry) if parameterDetails.preset.expiry? - presetValue = parameterDetails.preset.value - currentValue = parameterDetails.current._value - if presetValue? and currentValue != (presetValue = common.matchType(currentValue, presetValue)) - setParameterValues.push([parameterPath, presetValue, parameterDetails.current._type]) - else if parameterDetails.preset.expiry? - diff = parameterDetails.preset.expiry - (now - (parameterDetails.current._timestamp ? 0)) / 1000 - if diff <= PRESETS_TIME_PADDING - if parameterDetails.current._value? - getParameterValues.push(parameterPath) - else - taskList.push({device : deviceId, name : 'refreshObject', objectName : parameterPath}) - else - expiry = Math.min(expiry, diff) - - # Objects - for parameterPath, object of devicePreset.objects ? {} - for objectName, objectDetails of object - continue if not objectDetails.current? - if objectDetails.preset? - flatObject = common.flattenObject(objectDetails.preset) - if Object.keys(objectDetails.current).length > 0 - for i, obj of objectDetails.current - for paramName, paramValue of flatObject - continue if paramName[0] == '_' - currentValue = common.getParamValueFromPath(obj, paramName) - if currentValue? - presetValue = common.matchType(currentValue._value, flatObject[paramName]) - if currentValue._value != presetValue - setParameterValues.push(["#{parameterPath}.#{i}.#{paramName}", presetValue, currentValue._type]) - else - vals = [] - for k,v of flatObject - vals.push([k, v]) if k[0] != '_' - taskList.push({device : deviceId, name : 'addObject', objectName : parameterPath, parameterValues : vals}) - else if Object.keys(objectDetails.current).length > 0 - for i, obj of objectDetails.current - taskList.push({device : deviceId, name : 'deleteObject', objectName : "#{parameterPath}.#{i}"}) - - # Tags - for tag, v of devicePreset.tags ? {} - if v == 1 - deleteTags.push(tag) - else if v == 2 - addTags.push(tag) - - # Custom commands - for commandName, commandDetails of devicePreset.customCommands ? {} - continue if not commandDetails.current? - expiry = Math.min(expiry, commandDetails.preset.expiry) if commandDetails.preset.expiry? - currentValue = commandDetails.current._value - presetValue = common.matchType(currentValue, commandDetails.preset.value) - if commandDetails.preset.value? and currentValue != presetValue - taskList.push({device : deviceId, name : 'customCommand', command : commandDetails.preset.valueCommand}) - else if commandDetails.preset.expiry? - diff = commandDetails.preset.expiry - (now - (commandDetails.current._timestamp ? 0)) / 1000 - if diff <= PRESETS_TIME_PADDING - taskList.push({device : deviceId, name : 'customCommand', command : commandDetails.preset.expiryCommand}) - else - expiry = Math.min(expiry, diff) - - if setParameterValues.length - taskList.push {device : deviceId, name : 'setParameterValues', parameterValues: setParameterValues, timestamp : now} - - if getParameterValues.length - taskList.push {device : deviceId, name : 'getParameterValues', parameterNames: getParameterValues, timestamp : now} - - callback(taskList, addTags, deleteTags, expiry) - - -getObjectHash = (object) -> - if object._keys?.length - keys = object._keys - else - keys = (k for k in Object.keys(object) when k[0] != '_') - - keys.sort() - - hash = [] - for k in keys - hash.push(k) - hash.push(common.getParamValueFromPath(object, k)) - - return JSON.stringify(hash) - - -accumulateConfigurations = (presets, objects) -> - maxWeights = {} - configurations = {} - for p in presets - for c in p.configurations - configurationHash = switch c.type - when 'add_tag', 'delete_tag' - "tag_#{c.tag}" - when 'add_object', 'delete_object' - objectHash = getObjectHash(objects[c.object]) - "object_#{c.name}_#{objectHash}" - when 'software_version' - 'software_version' - when 'custom_command_age', 'custom_command_value' - [filename, commandName] = c.command.split(' ', 2) - "#{c.type}_#{filename}_#{commandName}" - else - "#{c.type}_#{c.name}" - - if not maxWeights[configurationHash]? or p.weight > maxWeights[configurationHash] - configurations[configurationHash] = c - maxWeights[configurationHash] = p.weight - - configurationsList = (configurations[c] for c of configurations) - return configurationsList diff --git a/lib/tasks.coffee b/lib/tasks.coffee deleted file mode 100644 index 568563a..0000000 --- a/lib/tasks.coffee +++ /dev/null @@ -1,379 +0,0 @@ -### -# 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 . -# -# This file incorporates work covered by the following copyright and -# permission notice: -# -# Copyright 2013 Fanoos Telecom -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -### - -config = require './config' -common = require './common' -db = require './db' -mongodb = require 'mongodb' -customCommands = require './custom-commands' -url = require 'url' - -exports.STATUS_OK = STATUS_OK = 1 -exports.STATUS_FAULT = STATUS_FAULT = 2 -exports.STATUS_COMPLETED = STATUS_COMPLETED = 4 -exports.STATUS_SAVE = STATUS_SAVE = 128 # not mutually exclusive with the rest - - -this.refreshObject = (task, methodResponse, callback) -> - task.session = {} if not task.session? - - if not task.session.subtask? - path = task.objectName - path += '.' if path != '' and not common.endsWith(path, '.') - task.session.subtask = {device : task.device, name : 'getParameterNames', parameterPath : path} - - if methodResponse.faultcode? - task.fault = methodResponse - callback(null, STATUS_FAULT) - return - - if task.session.subtask.name is 'getParameterNames' - this.getParameterNames(task.session.subtask, methodResponse, (err, status, methodRequest, deviceUpdates) => - task.session.parameterNames ?= [] - if deviceUpdates?.parameterNames? - for p in deviceUpdates.parameterNames - if not common.endsWith(p[0], '.') - task.session.parameterNames.push(p[0]) - - if status & STATUS_COMPLETED - task.session.subtask = {device : task.device, name : 'getParameterValues', parameterNames : task.session.parameterNames} - delete task.session.parameterNames - - this.getParameterValues(task.session.subtask, {}, (err, status, methodRequest) -> - # ignore deviceUpdates returned by first call to getParameterValues - callback(err, status, methodRequest, deviceUpdates) - ) - else if status & STATUS_OK - callback(err, STATUS_OK, methodRequest, deviceUpdates) - else - throw new Error('Unexpected subtask status') - ) - else if task.session.subtask.name is 'getParameterValues' - if methodResponse.faultcode? - task.fault = methodResponse - return callback(null, STATUS_FAULT) - - this.getParameterValues(task.session.subtask, methodResponse, (err, status, methodRequest, deviceUpdates) -> - callback(err, status, methodRequest, deviceUpdates) - ) - else - throw Error('Unexpected subtask name') - - -this.getParameterNames = (task, methodResponse, callback) -> - if methodResponse.faultcode? - task.fault = methodResponse - callback(null, STATUS_FAULT) - return - - GET_PARAMETER_NAMES_DEPTH_THRESHOLD = config.get('GET_PARAMETER_NAMES_DEPTH_THRESHOLD', task.device) - - getParameterDepth = (param) -> - return 0 if !param - return (param[...-1] + '.').split('.').length - 1 - - if methodResponse.type is 'GetParameterNamesResponse' - path = task.session.queue.pop() - - # If parameter depth higher than the threshold, nextLevel was set to false - if not task.nextLevel? and getParameterDepth(path) < GET_PARAMETER_NAMES_DEPTH_THRESHOLD - for p in methodResponse.parameterList - task.session.queue.push(p[0]) if p[0][-1..] == '.' - - deviceUpdates = {parameterNames : methodResponse.parameterList} - - # Make sure that for each parameter, all its parents are explicitly included - found = {} - found[path] = 0 if !!path - for p in deviceUpdates.parameterNames - param = p[0] - i = param.length - while (i = param.lastIndexOf('.', i-1)) > path.length - pp = param.slice(0, i + 1) - break if found[pp]? - found[pp] = 0 - found[p[0]] = 1 - - for k, v of found - deviceUpdates.parameterNames.push([k]) if v == 0 - else - task.session = {queue : [task.parameterPath]} - - if task.session.queue.length > 0 - path = task.session.queue[-1..][0] - methodRequest = { - type : 'GetParameterNames', - parameterPath : path, - nextLevel : task.nextLevel ? getParameterDepth(path) < GET_PARAMETER_NAMES_DEPTH_THRESHOLD - } - return callback(null, STATUS_OK, methodRequest, deviceUpdates) - else - return callback(null, STATUS_COMPLETED, null, deviceUpdates) - - -this.getParameterValues = (task, methodResponse, callback) -> - task.session = {} if not task.session? - - if methodResponse.faultcode? - task.fault = methodResponse - callback(null, STATUS_FAULT) - return - - if not task.session.currentIndex? - task.session.currentIndex = 0 - else if methodResponse.parameterList? - task.session.currentIndex = task.session.nextIndex - - task.session.nextIndex = Math.min(task.session.currentIndex + config.get('TASK_PARAMETERS_BATCH_SIZE', task.device), task.parameterNames.length) - names = task.parameterNames.slice(task.session.currentIndex, task.session.nextIndex) - - if methodResponse.type is 'GetParameterValuesResponse' - deviceUpdates = {parameterValues : methodResponse.parameterList} - - if names.length == 0 - callback(null, STATUS_COMPLETED, null, deviceUpdates) - else - methodRequest = { - type : 'GetParameterValues', - parameterNames : names - } - callback(null, STATUS_OK, methodRequest, deviceUpdates) - - -this.setParameterValues = (task, methodResponse, callback) -> - task.session = {} if not task.session? - - if methodResponse.faultcode? - task.fault = methodResponse - callback(null, STATUS_FAULT) - return - - if not task.session.currentIndex? - task.session.currentIndex = 0 - else if methodResponse.type is 'SetParameterValuesResponse' - prevValues = task.parameterValues.slice(task.session.currentIndex, task.session.nextIndex) - task.session.currentIndex = task.session.nextIndex - - task.session.nextIndex = Math.min(task.session.currentIndex + config.get('TASK_PARAMETERS_BATCH_SIZE', task.device), task.parameterValues.length) - values = task.parameterValues.slice(task.session.currentIndex, task.session.nextIndex) - - if prevValues? - deviceUpdates = {parameterValues : prevValues} - - if values.length == 0 - callback(null, STATUS_COMPLETED, null, deviceUpdates) - else - callback(null, STATUS_OK, {type : 'SetParameterValues', parameterList : values}, deviceUpdates) - - -this.addObject = (task, methodResponse, callback) -> - task.session = {} if not task.session? - - if methodResponse.faultcode? - task.fault = methodResponse - callback(null, STATUS_FAULT) - return - - allDeviceUpdates = {} - - if not task.session.instanceNumber? - if methodResponse.type is 'AddObjectResponse' - status_save = STATUS_SAVE - task.session.instanceNumber = methodResponse.instanceNumber - # TODO Don't specify nextLevel explicity. Also consider using refreshObject instead. - task.session.subtask = {device : task.device, name : 'getParameterNames', parameterPath : "#{task.objectName}.#{task.session.instanceNumber}.", nextLevel : false} - task.session.appliedParameterValues = [] - task.session.parameterNames = [] - else - callback(null, STATUS_OK, {type : 'AddObject', objectName : "#{task.objectName}."}) - return - - subtask = () => - switch task.session.subtask.name - when 'getParameterNames' - this.getParameterNames(task.session.subtask, methodResponse, (err, status, methodRequest, deviceUpdates) => - common.extend(allDeviceUpdates, deviceUpdates) - if deviceUpdates and deviceUpdates.parameterNames - for p in deviceUpdates.parameterNames - task.session.parameterNames.push(p[0]) if not common.endsWith(p[0], '.') - - if status & STATUS_COMPLETED - task.session.subtask = {device : task.device, name : 'getParameterValues', parameterNames : task.session.parameterNames} - subtask() - else if status & STATUS_OK - # Use STATUS_SAVE to avoid adding duplicate object in case of error - callback(err, STATUS_OK | status_save, methodRequest, allDeviceUpdates) - else - throw Error('Unexpected subtask status') - ) - when 'getParameterValues' - this.getParameterValues(task.session.subtask, methodResponse, (err, status, methodRequest, deviceUpdates) => - common.extend(allDeviceUpdates, deviceUpdates) - # if values are given, compare with default values - if task.parameterValues? - if deviceUpdates and deviceUpdates.parameterValues? - for p1 in deviceUpdates.parameterValues - for p2 in task.parameterValues - if p1[0] == "#{task.objectName}.#{task.session.instanceNumber}.#{p2[0]}" - t = if p2[2] then p2[2] else p1[2] - v = common.matchType(p1[1], p2[1]) - # TODO only include if writable - task.session.appliedParameterValues.push([p1[0], v, t]) - - if methodResponse.faultcode? - task.fault = methodResponse - return callback(null, STATUS_FAULT) - - if status & STATUS_COMPLETED and task.session.appliedParameterValues.length > 0 - task.session.subtask = {device : task.device, name : 'setParameterValues', parameterValues : task.session.appliedParameterValues} - subtask() - else - callback(err, status, methodRequest, allDeviceUpdates) - ) - when 'setParameterValues' - this.setParameterValues(task.session.subtask, methodResponse, (err, status, methodRequest, deviceUpdates) => - common.extend(allDeviceUpdates, deviceUpdates) - callback(err, status, methodRequest, allDeviceUpdates) - ) - subtask() - - -this.deleteObject = (task, methodResponse, callback) -> - if methodResponse.faultcode? - task.fault = methodResponse - callback(null, STATUS_FAULT) - return - - if methodResponse.type is 'DeleteObjectResponse' - callback(null, STATUS_COMPLETED, null, {deletedObjects : [task.objectName]}) - else - methodRequest = { - type : 'DeleteObject', - objectName : "#{task.objectName}." - } - callback(null, STATUS_OK, methodRequest) - - -this.reboot = (task, methodResponse, callback) -> - if methodResponse.faultcode? - task.fault = methodResponse - callback(null, STATUS_FAULT) - return - - if methodResponse.type isnt 'RebootResponse' - callback(null, STATUS_OK, {type : 'Reboot'}) - else - callback(null, STATUS_COMPLETED) - - -this.factoryReset = (task, methodResponse, callback) -> - if methodResponse.faultcode? - task.fault = methodResponse - callback(null, STATUS_FAULT) - return - - if methodResponse.type isnt 'FactoryResetResponse' - callback(null, STATUS_OK, {type : 'FactoryReset'}) - else - callback(null, STATUS_COMPLETED) - - -this.download = (task, methodResponse, callback) -> - if methodResponse.faultcode? - task.fault = methodResponse - callback(null, STATUS_FAULT) - return - - if methodResponse.type isnt 'DownloadResponse' - if task.url? - methodRequest = { - type : 'Download', - fileType : task.fileType or '1 Firmware Upgrade Image', - url : task.url, - username: task.username, - password: task.password, - successUrl : task.successUrl, - failureUrl : task.failureUrl - } - callback(null, STATUS_OK, methodRequest) - else - db.filesCollection.findOne({_id : mongodb.ObjectID(String(task.file))}, (err, file) -> - if not file? - callback('File not found') - return - else if err? - callback(err) - return - - l = { - protocol : if config.get('FS_SSL') then 'https' else 'http', - hostname : config.get('FS_IP'), - port : config.get('FS_PORT'), - pathname : encodeURIComponent(file.filename) - } - - methodRequest = { - type : 'Download', - fileType : file.metadata.fileType, - fileSize : file.length, - url : url.format(l), - successUrl : task.successUrl, - failureUrl : task.failureUrl - } - callback(null, STATUS_OK, methodRequest) - ) - else - callback(null, STATUS_COMPLETED) - - -this.customCommand = (task, methodResponse, callback) -> - # TODO implement timeout - customCommands.execute(task.device, task.command, (err, value) -> - if err - task.fault = {name : err.name, message : err.message} - callback(null, STATUS_FAULT) - else - commandName = task.command.split(' ', 2)[0] - callback(null, STATUS_COMPLETED, null, {customCommands : [[commandName, value]]}) - ) - - -exports.task = (task, methodResponse, callback) -> - this[task.name](task, methodResponse, callback) diff --git a/lib/update-device.coffee b/lib/update-device.coffee deleted file mode 100644 index 3f5ed41..0000000 --- a/lib/update-device.coffee +++ /dev/null @@ -1,217 +0,0 @@ -### -# 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 . -### - -common = require './common' -db = require './db' -normalize = require('./normalize').normalize -mongoQuery = require './mongo-query' -parameters = require './parameters' - -OBJECT_REGEX = /\.$/ -INSTANCE_REGEX = /\.[\d]+\.$/ - - -queueUpdates = (deviceId, deviceUpdates, callback) -> - return callback() if not deviceUpdates? or Object.keys(deviceUpdates).length == 0 - # TODO consider setting expiry - db.redisClient.rpush("#{deviceId}_updates", JSON.stringify(deviceUpdates), (err, res) -> - return callback(err) - ) - - -clearUpdatesQueue = (deviceId, callback) -> - db.redisClient.del("#{deviceId}_updates", (err, res) -> - return callback(err) - ) - - -commitUpdates = (deviceId, deviceUpdates, commitQueue, callback) -> - now = new Date(Date.now()) - update = {'$set' : {}, '$unset' : {}} - - # Used to find parameters that no longer exist in device - presentPaths = null - projection = null - - # Keep track of alias changes to clear cache if needed - newAliases = null - - processBatch = (updatesBatch, cb) -> - if updatesBatch.informEvents? - update['$set']['_lastInform'] = now - update['$set']['_lastBoot'] = now if '1 BOOT' in updatesBatch.informEvents - update['$set']['_lastBootstrap'] = now if '0 BOOTSTRAP' in updatesBatch.informEvents - - if updatesBatch.parameterValues? - for p in updatesBatch.parameterValues - origValue = p[1] - v = normalize(p[0], origValue) - - path = if common.endsWith(p[0], '.') then p[0] else "#{p[0]}." - if v == origValue - update['$unset']["#{path}_orig"] = 1 - else - update['$set']["#{path}_orig"] = origValue - update['$set']["#{path}_value"] = v - update['$set']["#{path}_timestamp"] = now - if p[2]? - update['$set']["#{path}_type"] = p[2] - - if updatesBatch.deletedObjects? - for p in updatesBatch.deletedObjects - update['$unset'][p] = 1 - - if updatesBatch.parameterNames? - presentPaths ?= {} - projection ?= {} - - for p in updatesBatch.parameterNames - if OBJECT_REGEX.test(p[0]) - path = p[0].slice(0, -1) - if INSTANCE_REGEX.test(p[0]) - update['$set']["#{path}._instance"] = true - else - update['$set']["#{path}._object"] = true - else - path = p[0] - update['$set']["#{path}._writable"] = p[1] if p[1]? - update['$set']["#{path}._timestamp"] = now - - # Add parameter (sans any dot suffix) if has an alias - if (a = parameters.getAlias(path))? - newAliases ?= {} - newAliases[path] = a - - projection[path] = 1 - - # Store this param path and all its parents for later look up - presentPaths[path] = true - i = p[0].indexOf('.') - while i != -1 - presentPaths[p[0][0...i]] = true - i = p[0].indexOf('.', i + 1) - - if updatesBatch.customCommands? - for p in updatesBatch.customCommands - commandName = p[0] - update['$set']["_customCommands.#{commandName}._value"] = p[1] - update['$set']["_customCommands.#{commandName}._timestamp"] = now - - processBatches = (cb) -> - if not commitQueue - processBatch(deviceUpdates) if deviceUpdates? - return cb() - - db.redisClient.lpop("#{deviceId}_updates", (err, res) -> - callback(err) if err - - if not res? - processBatch(deviceUpdates) if deviceUpdates? - return cb() - - processBatch(JSON.parse(res)) - return processBatches(cb) - ) - - commit = () -> - for k of update - delete update[k] if Object.keys(update[k]).length == 0 - - if Object.keys(update).length - db.devicesCollection.update({'_id' : deviceId}, update, {}, (err, count) -> - return callback(err) if err - - # Clear aliases cache if there's a new aliased parameter that has not been included in aliases cache - if newAliases? - newAliasFound = false - db.getAliases((aliases) -> - for p, a of newAliases - if not aliases[a]? or p not in aliases[a] - newAliasFound = true - break - - return callback() if not newAliasFound - db.redisClient.del('aliases', (err, res) -> - return callback(err) - ) - ) - else - return callback() - ) - else - return callback() - - processBatches(() -> - if projection? - mongoQuery.optimizeProjection(projection) - - # Fetch device from DB to discover and delete parameters that no longer exist - # While we're fetching the object, we might as well use the information - # to optimize our update query - db.devicesCollection.findOne({'_id' : deviceId}, projection, (err, device) -> - return callback(err) if err - - recursive = (obj, prefix) -> - for k, v of obj - p = prefix + k - - # Mark unset operations we want to keep - if update['$unset'][p]? - update['$unset'][p] = 2 - continue - - if k[0] == '_' - # Remove set operations for unmodified values for optimization - if update['$set'][p] == v - delete update['$set'][p] - continue - - if presentPaths[p] - recursive(v, "#{p}.") - else - update['$unset'][p] = 2 - - # Scan the DB object and remove parameters that no longer exists in device - for path of projection - root = device - rootPath = '' - for p in path.split('.') - rootPath += "#{p}." - root = root[p] - break if not root? - - if root - recursive(root, rootPath) - else - if not presentPaths[rootPath.slice(0, -1)]? - update['$unset'][rootPath.slice(0, -1)] = 2 - - # Remove unnecessary unset updates - for k, v of update['$unset'] - if common.startsWith(k, path + '.') and v != 2 - delete update['$unset'][k] - - commit() - ) - else - commit() - ) - - -exports.queueUpdates = queueUpdates -exports.clearUpdatesQueue = clearUpdatesQueue -exports.commitUpdates = commitUpdates