nanocyte-configuration-generator
Version:
Generate Nanocyte configuration from an Octoblu flow
419 lines (327 loc) • 13.1 kB
text/coffeescript
_ = require 'lodash'
debug = require('debug')('nanocyte-configuration-generator')
UUID = require 'uuid'
ChannelConfig = require './channel-config'
MeshbluHttp = require 'meshblu-http'
NodeRegistryDownloader = require './node-registry-downloader'
SimpleBenchmark = require 'simple-benchmark'
# outside the class so cache is maintained
Downloader = new NodeRegistryDownloader
DEFAULT_REGISTRY_URL = 'https://s3-us-west-2.amazonaws.com/nanocyte-registry/latest/registry.json'
METRICS_DEVICE_ID = 'f952aacb-5156-4072-bcae-f830334376b1'
VIRTUAL_NODES =
'engine-input':
config: {}
data: {}
'engine-output':
config: {}
data: {}
'engine-update':
config: {}
data: {}
'engine-data':
config: {}
data: {}
'engine-debug':
config: {}
data: {}
'engine-pulse':
config: {}
data: {}
'router':
config: {}
data: {}
'engine-start':
config: {}
data: {}
'engine-stop':
config: {}
data: {}
'subscribe-devices':
config: {}
data: {}
class ConfigurationGenerator
constructor: (options, dependencies={}) ->
{
} = options
?= {}
?= DEFAULT_REGISTRY_URL
?= METRICS_DEVICE_ID
{, } = dependencies
?= require 'request'
= new SimpleBenchmark label: "nanocyte-configuration-generator-#{@meshbluJSON?.uuid}"
?= new ChannelConfig
accessKeyId: options.accessKeyId
secretAccessKey: options.secretAccessKey
= new MeshbluHttp
Downloader.setOptions {}
configure: (options, callback=->) =>
{flowData, flowToken, deploymentUuid} = options
.update (error) =>
debug 'channelConfig.fetch', .toString()
return callback error if error?
(error, nodeRegistry) =>
debug 'getNodeRegistry', .toString()
return callback error if error?
flowMetricNode =
id:
category: 'flow-metrics'
flowUuid: flowData.flowId
deviceId:
deploymentUuid: deploymentUuid
flowData.nodes ?= []
flowData.nodes.push flowMetricNode
flowNodes = _.indexBy flowData.nodes, 'id'
flowConfig = _.mapValues flowNodes, (nodeConfig) =>
nodeConfig.nanocyte ?= {}
nodeConfig.nanocyte.nonce =
config: nodeConfig
data: {}
flowConfig = _.assign flowConfig, _.cloneDeep(VIRTUAL_NODES)
instanceMap = flowData.links, flowConfig, nodeRegistry
_.each instanceMap, (instanceConfig, instanceId) =>
{config,data} = flowConfig[instanceConfig.nodeUuid]
config = _.cloneDeep config # prevent accidental mutation
config.templateOriginalMessage = instanceConfig.templateOriginalMessage
getSetNodesConfig = uuid: flowData.flowId, token: flowToken, config
defaultConfig = {}
if config.category == 'channel'
channelApiMatch = .get config.type
return callback new Error "Missing channel config for: #{config.type}" unless channelApiMatch?
defaultConfig.channelApiMatch = channelApiMatch
config = _.defaultsDeep defaultConfig, config, getSetNodesConfig
flowConfig[instanceId] = {config: config, data: data}
debug 'instanceMap', .toString()
links =
debug 'buildLinks', .toString()
flowConfig.router.config = links
flowConfig['engine-data'].config = instanceMap
flowConfig['engine-pulse'].config = instanceMap
flowConfig['engine-debug'].config = instanceMap
flowConfig['engine-input'].config = flowConfig, instanceMap
flowConfig['subscribe-devices'].config = flowNodes
config = {flowData, flowToken}
flowConfig['engine-output'].config = config
flowConfig['engine-update'].config = config
flowConfig['engine-output'].config.nodeMap = instanceMap
flowStopConfig = _.cloneDeep flowConfig
engineStopLinks = flowConfig['router']['config']['engine-stop']?.linkedTo
engineStopLinks ?= []
stopRouterConfig = _.pick flowConfig['router']['config'], 'engine-stop', 'engine-output', engineStopLinks...
flowStopConfig['router']['config'] = stopRouterConfig
debug 'calling back', .toString()
callback null, flowConfig, flowStopConfig
_buildEngineOutputConfig: ({flowData, flowToken}, callback) =>
_.defaults {uuid: flowData.flowId, token: flowToken},
_buildNodeMap: (flowNodeMap) =>
_.mapValues flowNodeMap, (flowNode) =>
nodeId: flowNode.nodeUuid
_buildMeshblutoNodeMap: (flowConfig, instanceMap) =>
inputInstances = _.where instanceMap, linkedToInput: true
nodeMap = {}
_.each inputInstances, (instance) =>
nodeConfig = flowConfig[instance.nodeUuid]
nodeMap[nodeConfig.config.uuid] ?= []
alias = nodeConfig.config.alias
aNodeMap = nodeId: instance.nodeUuid
aNodeMap.alias = alias if alias?
nodeMap[nodeConfig.config.uuid].push aNodeMap
return nodeMap
_generateInstances: (links, flowNodes, nodeRegistry) =>
instanceMap = {}
_.each flowNodes, (nodeConfig, nodeUuid) =>
config = nodeConfig.config ? {}
nanocyteConfig = config.nanocyte ? {}
type = config.category
type = config.type.replace('operation:', '') if type == 'operation'
type = 'device' if config
type = 'device-configure' if config
nodeFromRegistry = nodeRegistry[type] ? {}
composedOf = _.cloneDeep(nodeFromRegistry.composedOf) ? {}
linkedToData = _.detect composedOf, (value, key) =>
value.linkedToData == true
composedOf = if config.debug?
transactionGroupId = if linkedToData?
_.each composedOf, (template, templateId) =>
instanceId =
composedConfig = _.cloneDeep template
composedConfig.nodeUuid = nodeUuid
composedConfig.templateId = templateId
composedConfig.transactionGroupId = transactionGroupId if linkedToData?
composedConfig.instanceId = instanceId
composedConfig.eventType = config.eventType
composedConfig.isDevice = config
instanceMap[instanceId] = composedConfig
return instanceMap
_addDebug: (composedOf) =>
composedOf = composedOf
composedOf = composedOf
return composedOf
_addInputDebug: (composedOf) =>
composedOf = _.cloneDeep composedOf
debugInput =
type: "nanocyte-component-pass-through"
debug: true
linkedTo: []
linkedToPrev: true
composedOf['debug-input'] = debugInput
composedOf
_addOutputDebug: (composedOf) =>
_.mapValues composedOf, (node) =>
node.debug = true if node.linkedToNext
node
_getNodeRegistry: (callback) =>
Downloader.update callback
_getForwardMetadataTo: (deviceUuids, callback) =>
query =
uuid: $in: deviceUuids
'octoblu.flow.forwardMetadata': true
projection =
uuid: true
.search query, {projection}, (error, devices) =>
return callback error if error?
return callback null, _.map(devices, 'uuid')
_getSubscribeDevices: (flowNodes) =>
flowNodesByEventType = _.groupBy flowNodes, (node) =>
return 'not-a-device' unless node
return node.eventType || 'message'
return {
'broadcast.sent':
'configure.sent':
}
_getDeviceUuids: (flowNodes) =>
# devices = _.where flowNodes, category: 'device'
devices = _.filter flowNodes,
_.pluck devices, 'uuid'
_isDevice: (item) =>
return true if item.meshblu?
return true if item.class == 'device-flow'
return true if item.class == 'device'
return false
_isDeviceConfigure: (item) =>
&& item.eventType == 'configure'
_buildLinks: (links, instanceMap) =>
result = {}
_.each instanceMap, (config, instanceId) =>
nodeLinks = _.filter links, from: config.nodeUuid
templateLinks = config.linkedTo
linkedTo = []
if config.linkedToInput
result[config.nodeUuid] ?=
type: 'engine-input'
linkedTo: []
result[config.nodeUuid].linkedTo.push instanceId
if config.linkedFromStart
result['engine-start'] ?=
type: 'engine-start'
linkedTo: []
result['engine-start'].linkedTo.push instanceId
if config.linkedFromStop
result['engine-stop'] ?=
type: 'engine-stop'
linkedTo: []
result['engine-stop'].linkedTo.push instanceId
if config.linkedToNext
eventLinks = {config, nodeLinks, instanceMap} if config.isDevice
linkedTo = {nodeLinks, instanceMap} unless config.isDevice
_.each config.linkedTo, (templateLinkId) =>
_.each instanceMap, (data, key) =>
if data.nodeUuid == config.nodeUuid && data.templateId == templateLinkId
linkedTo.push key
linkedTo.push 'engine-output' if config.linkedToOutput
linkedTo.push 'engine-update' if config.linkedToUpdate
linkedTo.push 'engine-pulse' if config.linkedToNext || config.linkedToPulse || config.linkedToOutput || config.linkedToUpdate
linkedTo.push 'engine-data' if config.linkedToData
linkedTo.push 'engine-debug' if config.debug
result[instanceId] =
eventLinks: eventLinks
type: config.type
linkedTo: linkedTo
linkedToNext: config.linkedToNext
result[instanceId].transactionGroupId = config.transactionGroupId if config.transactionGroupId?
result['engine-output'] =
type: 'engine-output'
linkedTo: []
result['engine-update'] =
type: 'engine-update'
linkedTo: []
result['engine-debug'] =
type: 'engine-debug'
linkedTo: []
result['engine-pulse'] =
type: 'engine-pulse'
linkedTo: []
result['engine-data'] =
type: 'engine-data'
linkedTo: []
return result
_getEventLinks: ({config, nodeLinks, instanceMap}) =>
eventLinks =
'broadcast.sent': []
'message.received': []
'configure.sent': []
_.each nodeLinks, (link) =>
prevNanocytes = _.filter instanceMap, nodeUuid: link.to, linkedToPrev: true
_.each prevNanocytes, (nanocyte) =>
if config.eventType == 'configure'
eventLinks['configure.sent'].push nanocyte.instanceId
else
eventLinks['broadcast.sent'].push nanocyte.instanceId
eventLinks['message.received'].push nanocyte.instanceId
return eventLinks
_getLinkedTo: ({nodeLinks, instanceMap}) =>
linkedTo = []
linkUuids = _.pluck nodeLinks, 'to'
_.each instanceMap, (data, key) =>
if _.contains linkUuids, data.nodeUuid
linkedTo.push key if data.linkedToPrev
return linkedTo
_legacyConversion: (config) =>
if config.type == 'operation:debounce'
config.timeout = config.interval
delete config.interval
if config.type == 'operation:throttle'
config.repeat = config.interval
delete config.interval
if config.type == 'operation:delay'
config.fireOnce = true
config.noUnsubscribe = true
return config
_mutilateGetSetNodes: (options, template) =>
return {} unless template.type == 'operation:get-key' || template.type == 'operation:set-key'
{uuid, token} = options
bearerToken = new Buffer("#{uuid}:#{token}").toString('base64')
{host,protocol,port} =
host ?= 'meshblu-http.octoblu.com:443'
port ?= 443
protocol ?= 'http'
protocol = 'https' if parseInt(port) == 443
config =
bodyEncoding: 'json'
url: "#{protocol}://#{host}/v2/devices/#{uuid}"
method: 'GET'
headerKeys: [
'Content-Type'
'Authorization'
]
headerValues: [
'application/json'
"Bearer #{bearerToken}"
]
if template.type == 'operation:set-key'
config.method = 'PATCH'
config.bodyKeys = [ 'data.{{msg.key}}' ]
config.bodyValues = [ '{{msg.value}}' ]
return config
_generateFlowMetricId: =>
UUID.v4()
_generateInstanceId: =>
UUID.v4()
_generateNonce: =>
UUID.v4()
_generateTransactionGroupId: =>
UUID.v4()
module.exports = ConfigurationGenerator