Implement creating and deleting instances
This commit is contained in:
1 parent
ac759e1d04
commit
0a5697f06a
2 files changed
+294
-31
No files matched your search
@@ -0,0 +1,124 @@
|
|||||||
|
/**
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceSet {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.set = new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
add(instance) {
|
||||||
|
this.set.add(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(instance) {
|
||||||
|
this.set.delete(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
superset(instance) {
|
||||||
|
let res = [];
|
||||||
|
for (let inst of this.set) {
|
||||||
|
let match = true;
|
||||||
|
for (let k in instance) {
|
||||||
|
if (inst[k] !== instance[k]) {
|
||||||
|
match = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match)
|
||||||
|
res.push(inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.sort(function(a, b) {
|
||||||
|
let keysA = Object.keys(a);
|
||||||
|
let keysB = Object.keys(b);
|
||||||
|
|
||||||
|
if (keysA.length != keysB.length)
|
||||||
|
return keysB.length - keysA.length;
|
||||||
|
|
||||||
|
keysA.sort();
|
||||||
|
keysB.sort();
|
||||||
|
|
||||||
|
for (let i = 0; i < keysA.length; ++ i) {
|
||||||
|
if (keysA[i] > keysB[i])
|
||||||
|
return 1;
|
||||||
|
else if (keysA[i] < keysB[i])
|
||||||
|
return -1;
|
||||||
|
else if (a[keysA[i]] > b[keysB[i]])
|
||||||
|
return 1;
|
||||||
|
else if (a[keysA[i]] < b[keysB[i]])
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
subset(instance) {
|
||||||
|
let res = [];
|
||||||
|
|
||||||
|
for (let inst of this.set) {
|
||||||
|
let match = true;
|
||||||
|
for (let k in inst) {
|
||||||
|
if (inst[k] !== instance[k]) {
|
||||||
|
match = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match)
|
||||||
|
res.push(inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.sort(function(a, b) {
|
||||||
|
let keysA = Object.keys(a);
|
||||||
|
let keysB = Object.keys(b);
|
||||||
|
|
||||||
|
if (keysA.length != keysB.length)
|
||||||
|
return keysA.length - keysB.length;
|
||||||
|
|
||||||
|
keysA.sort();
|
||||||
|
keysB.sort();
|
||||||
|
|
||||||
|
for (let i = 0; i < keysA.length; ++ i) {
|
||||||
|
if (keysA[i] > keysB[i])
|
||||||
|
return 1;
|
||||||
|
else if (keysA[i] < keysB[i])
|
||||||
|
return -1;
|
||||||
|
else if (a[keysA[i]] > b[keysB[i]])
|
||||||
|
return 1;
|
||||||
|
else if (a[keysA[i]] < b[keysB[i]])
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
forEach(callback) {
|
||||||
|
return this.set.forEach(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = InstanceSet;
|
||||||
+170
-31
@@ -26,6 +26,7 @@ cache = require './cache'
|
|||||||
extensions = require './extensions'
|
extensions = require './extensions'
|
||||||
PathSet = require './path-set'
|
PathSet = require './path-set'
|
||||||
VersionedMap = require './versioned-map.js'
|
VersionedMap = require './versioned-map.js'
|
||||||
|
InstanceSet = require './instance-set'
|
||||||
|
|
||||||
|
|
||||||
initDeviceData = () ->
|
initDeviceData = () ->
|
||||||
@@ -326,6 +327,9 @@ runVirtualParameters = (sessionData, inception, callback) ->
|
|||||||
|
|
||||||
|
|
||||||
rpcRequest = (sessionData, declarations, callback) ->
|
rpcRequest = (sessionData, declarations, callback) ->
|
||||||
|
if sessionData.rpcRequest?
|
||||||
|
return callback(null, generateRpcId(sessionData), sessionData.rpcRequest)
|
||||||
|
|
||||||
revision = sessionData.revisions.reduce(((a, b) -> a + (b >> 1)), 0) + 1
|
revision = sessionData.revisions.reduce(((a, b) -> a + (b >> 1)), 0) + 1
|
||||||
|
|
||||||
v.revision = revision for k, v of sessionData.deviceData.timestamps
|
v.revision = revision for k, v of sessionData.deviceData.timestamps
|
||||||
@@ -448,26 +452,57 @@ generateRpcRequest = (sessionData) ->
|
|||||||
rpcReq = null
|
rpcReq = null
|
||||||
completed = true
|
completed = true
|
||||||
|
|
||||||
|
# Delete instance
|
||||||
|
syncState.instancesToDelete.forEach((instances, parent) ->
|
||||||
|
instances.forEach((instance) ->
|
||||||
|
if rpcReq?
|
||||||
|
completed = false
|
||||||
|
else
|
||||||
|
rpcReq = {
|
||||||
|
type: 'DeleteObject'
|
||||||
|
objectName: instance.join('.') + '.'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create instance
|
||||||
|
syncState.instancesToCreate.forEach((instances, parent) ->
|
||||||
|
instances.forEach((instance) ->
|
||||||
|
if rpcReq?
|
||||||
|
completed = false
|
||||||
|
else
|
||||||
|
rpcReq = {
|
||||||
|
type: 'AddObject'
|
||||||
|
objectName: parent.join('.') + '.'
|
||||||
|
next: 'getInstanceKeys'
|
||||||
|
instanceValues: instance
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if syncState.refreshGpn.size
|
if syncState.refreshGpn.size
|
||||||
GET_PARAMETER_NAMES_DEPTH_THRESHOLD =
|
if rpcReq?
|
||||||
config.get('GET_PARAMETER_NAMES_DEPTH_THRESHOLD', sessionData.deviceId)
|
|
||||||
|
|
||||||
pair = syncState.refreshGpn.entries().next().value
|
|
||||||
|
|
||||||
nextLevel = true
|
|
||||||
if pair[0].length >= GET_PARAMETER_NAMES_DEPTH_THRESHOLD
|
|
||||||
nextLevel = false
|
|
||||||
else if common.hammingWeight(pair[1] >> 3) >= 3
|
|
||||||
nextLevel = false
|
|
||||||
|
|
||||||
if syncState.refreshGpn.size > 1 or (pair[1] > 1 and nextLevel)
|
|
||||||
completed = false
|
completed = false
|
||||||
|
else
|
||||||
|
GET_PARAMETER_NAMES_DEPTH_THRESHOLD =
|
||||||
|
config.get('GET_PARAMETER_NAMES_DEPTH_THRESHOLD', sessionData.deviceId)
|
||||||
|
|
||||||
rpcReq = {
|
pair = syncState.refreshGpn.entries().next().value
|
||||||
type: 'GetParameterNames'
|
|
||||||
parameterPath: pair[0].join('.')
|
nextLevel = true
|
||||||
nextLevel: nextLevel
|
if pair[0].length >= GET_PARAMETER_NAMES_DEPTH_THRESHOLD
|
||||||
}
|
nextLevel = false
|
||||||
|
else if common.hammingWeight(pair[1] >> 3) >= 3
|
||||||
|
nextLevel = false
|
||||||
|
|
||||||
|
if syncState.refreshGpn.size > 1 or (pair[1] > 1 and nextLevel)
|
||||||
|
completed = false
|
||||||
|
|
||||||
|
rpcReq = {
|
||||||
|
type: 'GetParameterNames'
|
||||||
|
parameterPath: pair[0].join('.')
|
||||||
|
nextLevel: nextLevel
|
||||||
|
}
|
||||||
|
|
||||||
if syncState.refreshAttributes.value.size
|
if syncState.refreshAttributes.value.size
|
||||||
if rpcReq?
|
if rpcReq?
|
||||||
@@ -560,19 +595,6 @@ processVirtualparameterDeclaration = (sessionData, path, timestamps, values) ->
|
|||||||
|
|
||||||
|
|
||||||
processDeclaration = (sessionData, path, _timestamps, values) ->
|
processDeclaration = (sessionData, path, _timestamps, values) ->
|
||||||
sessionData.syncState ?= {
|
|
||||||
paths: new PathSet()
|
|
||||||
refreshAttributes: {
|
|
||||||
exist: new Set()
|
|
||||||
object: new Set()
|
|
||||||
writable: new Set()
|
|
||||||
value: new Set()
|
|
||||||
}
|
|
||||||
setValues: new Map()
|
|
||||||
refreshGpn: new Map()
|
|
||||||
virtualParameters: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
syncState = sessionData.syncState
|
syncState = sessionData.syncState
|
||||||
deviceData = sessionData.deviceData
|
deviceData = sessionData.deviceData
|
||||||
|
|
||||||
@@ -747,12 +769,66 @@ loadPath = (sessionData, path) ->
|
|||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
processInstances = (sessionData, parent, parameters, keys, minInstances, maxInstances) ->
|
||||||
|
instancesToDelete = sessionData.syncState.instancesToDelete.get(parent)
|
||||||
|
if not instancesToDelete?
|
||||||
|
instancesToDelete = new Set()
|
||||||
|
sessionData.syncState.instancesToDelete.set(parent, instancesToDelete)
|
||||||
|
|
||||||
|
instancesToCreate = sessionData.syncState.instancesToCreate.get(parent)
|
||||||
|
if not instancesToCreate?
|
||||||
|
instancesToCreate = new InstanceSet()
|
||||||
|
sessionData.syncState.instancesToCreate.set(parent, instancesToCreate)
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
for p in parameters
|
||||||
|
++ counter
|
||||||
|
if counter > maxInstances
|
||||||
|
instancesToDelete.add(p)
|
||||||
|
else if counter <= minInstances
|
||||||
|
instancesToDelete.delete(p)
|
||||||
|
|
||||||
|
superset = instancesToCreate.superset(keys)
|
||||||
|
for inst in superset
|
||||||
|
++ counter
|
||||||
|
if counter > maxInstances
|
||||||
|
instancesToCreate.delete(inst)
|
||||||
|
|
||||||
|
subset = instancesToCreate.subset(keys)
|
||||||
|
for inst in subset
|
||||||
|
++ counter
|
||||||
|
if counter <= minInstances
|
||||||
|
instancesToCreate.delete(inst)
|
||||||
|
instancesToCreate.add(JSON.parse(JSON.stringify(keys)))
|
||||||
|
|
||||||
|
while counter < minInstances
|
||||||
|
++ counter
|
||||||
|
instancesToCreate.add(JSON.parse(JSON.stringify(keys)))
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
applyDeclaration = (sessionData, path, state, timestamps, values) ->
|
applyDeclaration = (sessionData, path, state, timestamps, values) ->
|
||||||
if state == 100
|
if state == 100
|
||||||
if not values? or Object.keys(values).length == 0
|
if not values? or Object.keys(values).length == 0
|
||||||
return state
|
return state
|
||||||
timestamps = {}
|
timestamps = {}
|
||||||
|
|
||||||
|
sessionData.syncState ?= {
|
||||||
|
paths: new PathSet()
|
||||||
|
refreshAttributes: {
|
||||||
|
exist: new Set()
|
||||||
|
object: new Set()
|
||||||
|
writable: new Set()
|
||||||
|
value: new Set()
|
||||||
|
}
|
||||||
|
setValues: new Map()
|
||||||
|
refreshGpn: new Map()
|
||||||
|
virtualParameters: {}
|
||||||
|
instancesToDelete: new Map()
|
||||||
|
instancesToCreate: new Map()
|
||||||
|
}
|
||||||
|
|
||||||
decs = device.getAliasDeclarations(path, timestamps.exist ? 1)
|
decs = device.getAliasDeclarations(path, timestamps.exist ? 1)
|
||||||
|
|
||||||
if state <= 1
|
if state <= 1
|
||||||
@@ -777,6 +853,30 @@ applyDeclaration = (sessionData, path, state, timestamps, values) ->
|
|||||||
return 2 if not satisfied
|
return 2 if not satisfied
|
||||||
|
|
||||||
unpacked = device.unpack(sessionData.deviceData, path)
|
unpacked = device.unpack(sessionData.deviceData, path)
|
||||||
|
|
||||||
|
if values.exist?
|
||||||
|
if Array.isArray(values.exist)
|
||||||
|
minInstances = values.exist[0]
|
||||||
|
maxInstances = values.exist[1]
|
||||||
|
else
|
||||||
|
minInstances = maxInstances = values.exist
|
||||||
|
|
||||||
|
parent = path.slice(0, -1)
|
||||||
|
|
||||||
|
if Array.isArray(path[path.length - 1])
|
||||||
|
keys = {}
|
||||||
|
for p, i in path[path.length - 1] by 2
|
||||||
|
keys[p] = path[path.length - 1][i + 1]
|
||||||
|
else if path[path.length - 1] == '*'
|
||||||
|
keys = {}
|
||||||
|
|
||||||
|
if (path.wildcard | path.alias) & ((1 << (path.length - 1)) - 1) == 0
|
||||||
|
processInstances(sessionData, parent, unpacked, keys, minInstances, maxInstances)
|
||||||
|
else
|
||||||
|
parentsUnpacked = device.unpack(sessionData.deviceData, parent)
|
||||||
|
for parent in parentsUnpacked
|
||||||
|
processInstances(sessionData, parent, device.unpack(sessionData.deviceData, parent.concat([path[parent.length]])), keys, minInstances, maxInstances)
|
||||||
|
|
||||||
satisfied = true
|
satisfied = true
|
||||||
for up in unpacked
|
for up in unpacked
|
||||||
satisfied &&= processDeclaration(sessionData, path, timestamps, values)
|
satisfied &&= processDeclaration(sessionData, path, timestamps, values)
|
||||||
@@ -848,7 +948,37 @@ rpcResponse = (sessionData, id, rpcRes, callback) ->
|
|||||||
++ sessionData.rpcCount
|
++ sessionData.rpcCount
|
||||||
|
|
||||||
rpcReq = sessionData.rpcRequest
|
rpcReq = sessionData.rpcRequest
|
||||||
sessionData.rpcRequest = null
|
if not rpcReq.next?
|
||||||
|
sessionData.rpcRequest = null
|
||||||
|
else if rpcReq.next == 'getInstanceKeys'
|
||||||
|
instanceNumber = rpcRes.instanceNumber
|
||||||
|
parameterNames = []
|
||||||
|
instanceValues = {}
|
||||||
|
for k, v of rpcReq.instanceValues
|
||||||
|
n = "#{rpcReq.objectName}#{rpcRes.instanceNumber}.#{k}"
|
||||||
|
parameterNames.push(n)
|
||||||
|
instanceValues[n] = v
|
||||||
|
|
||||||
|
if parameterNames.length == 0
|
||||||
|
sessionData.rpcRequest = null
|
||||||
|
else
|
||||||
|
sessionData.rpcRequest = {
|
||||||
|
type: 'GetParameterValues'
|
||||||
|
parameterNames: parameterNames
|
||||||
|
next: 'setInstanceKeys'
|
||||||
|
instanceValues: instanceValues
|
||||||
|
}
|
||||||
|
else if rpcReq.next == 'setInstanceKeys'
|
||||||
|
parameterList = []
|
||||||
|
for p in rpcRes.parameterList
|
||||||
|
if p[1] != rpcReq.instanceValues[p[0]]
|
||||||
|
parameterList.push(
|
||||||
|
[p[0]].concat(device.sanitizeParameterValue([rpcReq.instanceValues[p[0]], p[2]])))
|
||||||
|
|
||||||
|
sessionData.rpcRequest = {
|
||||||
|
type: 'SetParameterValues'
|
||||||
|
parameterList: parameterList
|
||||||
|
}
|
||||||
|
|
||||||
timestamp = sessionData.timestamp
|
timestamp = sessionData.timestamp
|
||||||
revision = sessionData.revisions.reduce(((a, b) -> a + (b >> 1)), 0) + 1
|
revision = sessionData.revisions.reduce(((a, b) -> a + (b >> 1)), 0) + 1
|
||||||
@@ -918,6 +1048,15 @@ rpcResponse = (sessionData, id, rpcRes, callback) ->
|
|||||||
{exist: timestamp, object: timestamp, writable: timestamp, value: timestamp},
|
{exist: timestamp, object: timestamp, writable: timestamp, value: timestamp},
|
||||||
{exist: 1, object: 0, writable: 1, value: p.slice(1)})
|
{exist: 1, object: 0, writable: 1, value: p.slice(1)})
|
||||||
|
|
||||||
|
when 'AddObjectResponse'
|
||||||
|
device.set(sessionData.deviceData, common.parsePath(rpcReq.objectName + rpcRes.instanceNumber),
|
||||||
|
{exist: timestamp, object: timestamp},
|
||||||
|
{exist: 1, object: 1})
|
||||||
|
|
||||||
|
when 'DeleteObjectResponse'
|
||||||
|
device.set(sessionData.deviceData, common.parsePath(rpcReq.objectName.slice(0, -1)),
|
||||||
|
{exist: timestamp})
|
||||||
|
|
||||||
else
|
else
|
||||||
return callback(new Error('Response type not recognized'))
|
return callback(new Error('Response type not recognized'))
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user