12 changed files with 616 additions and 76 deletions
@ -0,0 +1,163 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
|
||||
|
'use strict'; |
||||
|
|
||||
|
const config = require('config'), |
||||
|
JsInvokeMessageProcessor = require('../api/jsInvokeMessageProcessor'), |
||||
|
logger = require('../config/logger')._logger('awsSqsTemplate'); |
||||
|
|
||||
|
const requestTopic = config.get('request_topic'); |
||||
|
|
||||
|
const accessKeyId = config.get('aws_sqs.access_key_id'); |
||||
|
const secretAccessKey = config.get('aws_sqs.secret_access_key'); |
||||
|
const region = config.get('aws_sqs.region'); |
||||
|
const AWS = require('aws-sdk'); |
||||
|
|
||||
|
let sqsClient; |
||||
|
let queueURL; |
||||
|
let responseTopics = new Map(); |
||||
|
let stopped = false; |
||||
|
|
||||
|
function AwsSqsProducer() { |
||||
|
this.send = async (responseTopic, scriptId, rawResponse, headers) => { |
||||
|
let msgBody = JSON.stringify( |
||||
|
{ |
||||
|
key: scriptId, |
||||
|
data: [...rawResponse], |
||||
|
headers: headers |
||||
|
}); |
||||
|
|
||||
|
let responseQueueUrl = responseTopics.get(responseTopic); |
||||
|
|
||||
|
if (!responseQueueUrl) { |
||||
|
responseQueueUrl = await createQueue(responseTopic); |
||||
|
responseTopics.set(responseTopic, responseQueueUrl); |
||||
|
} |
||||
|
|
||||
|
let params = {MessageBody: msgBody, QueueUrl: responseQueueUrl, MessageGroupId: scriptId}; |
||||
|
|
||||
|
return new Promise((resolve, reject) => { |
||||
|
sqsClient.sendMessage(params, function (err, data) { |
||||
|
if (err) { |
||||
|
reject(err); |
||||
|
} else { |
||||
|
resolve(data); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
(async () => { |
||||
|
try { |
||||
|
logger.info('Starting ThingsBoard JavaScript Executor Microservice...'); |
||||
|
AWS.config.update({accessKeyId: accessKeyId, secretAccessKey: secretAccessKey, region: region}); |
||||
|
|
||||
|
sqsClient = new AWS.SQS({apiVersion: '2012-11-05'}); |
||||
|
|
||||
|
queueURL = await createQueue(requestTopic); |
||||
|
const messageProcessor = new JsInvokeMessageProcessor(new AwsSqsProducer()); |
||||
|
|
||||
|
const params = { |
||||
|
MaxNumberOfMessages: 10, |
||||
|
QueueUrl: queueURL, |
||||
|
WaitTimeSeconds: 0.025 |
||||
|
}; |
||||
|
while (!stopped) { |
||||
|
const messages = await new Promise((resolve, reject) => { |
||||
|
sqsClient.receiveMessage(params, function (err, data) { |
||||
|
if (err) { |
||||
|
reject(err); |
||||
|
} else { |
||||
|
resolve(data.Messages); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
if (messages && messages.length > 0) { |
||||
|
const entries = []; |
||||
|
|
||||
|
messages.forEach(message => { |
||||
|
entries.push({ |
||||
|
Id: message.MessageId, |
||||
|
ReceiptHandle: message.ReceiptHandle |
||||
|
}); |
||||
|
messageProcessor.onJsInvokeMessage(message.Body); |
||||
|
}); |
||||
|
|
||||
|
const deleteBatch = { |
||||
|
QueueUrl: queueURL, |
||||
|
Entries: entries |
||||
|
}; |
||||
|
sqsClient.deleteMessageBatch(deleteBatch, function (err, data) { |
||||
|
if (err) { |
||||
|
logger.error("Failed to delete messages from queue.", err.message); |
||||
|
} else { |
||||
|
//do nothing
|
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} catch (e) { |
||||
|
logger.error('Failed to start ThingsBoard JavaScript Executor Microservice: %s', e.message); |
||||
|
logger.error(e.stack); |
||||
|
exit(-1); |
||||
|
} |
||||
|
})(); |
||||
|
|
||||
|
function createQueue(topic) { |
||||
|
let queueName = topic.replace(/\./g, '_') + '.fifo'; |
||||
|
let queueParams = { |
||||
|
QueueName: queueName, Attributes: { |
||||
|
FifoQueue: 'true', |
||||
|
ContentBasedDeduplication: 'true' |
||||
|
|
||||
|
} |
||||
|
}; |
||||
|
return new Promise((resolve, reject) => { |
||||
|
sqsClient.createQueue(queueParams, function (err, data) { |
||||
|
if (err) { |
||||
|
reject(err); |
||||
|
} else { |
||||
|
resolve(data.QueueUrl); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
process.on('exit', () => { |
||||
|
stopped = true; |
||||
|
logger.info('Aws Sqs client stopped.'); |
||||
|
exit(0); |
||||
|
}); |
||||
|
|
||||
|
async function exit(status) { |
||||
|
logger.info('Exiting with status: %d ...', status); |
||||
|
if (sqsClient) { |
||||
|
logger.info('Stopping Aws Sqs client.') |
||||
|
try { |
||||
|
await sqsClient.close(); |
||||
|
logger.info('Aws Sqs client is stopped.') |
||||
|
process.exit(status); |
||||
|
} catch (e) { |
||||
|
logger.info('Aws Sqs client stop error.'); |
||||
|
process.exit(status); |
||||
|
} |
||||
|
} else { |
||||
|
process.exit(status); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,87 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
|
||||
|
'use strict'; |
||||
|
|
||||
|
const config = require('config'), |
||||
|
JsInvokeMessageProcessor = require('../api/jsInvokeMessageProcessor'), |
||||
|
logger = require('../config/logger')._logger('pubSubTemplate'); |
||||
|
const {PubSub} = require('@google-cloud/pubsub'); |
||||
|
|
||||
|
const projectId = config.get('pubsub.project_id'); |
||||
|
const credentials = JSON.parse(config.get('pubsub.service_account')); |
||||
|
const requestTopic = config.get('request_topic'); |
||||
|
|
||||
|
let pubSubClient; |
||||
|
|
||||
|
function PubSubProducer() { |
||||
|
this.send = async (responseTopic, scriptId, rawResponse, headers) => { |
||||
|
let data = JSON.stringify( |
||||
|
{ |
||||
|
key: scriptId, |
||||
|
data: [...rawResponse], |
||||
|
headers: headers |
||||
|
}); |
||||
|
let dataBuffer = Buffer.from(data); |
||||
|
return pubSubClient.topic(responseTopic).publish(dataBuffer); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
(async () => { |
||||
|
try { |
||||
|
logger.info('Starting ThingsBoard JavaScript Executor Microservice...'); |
||||
|
pubSubClient = new PubSub({projectId: projectId, credentials: credentials}); |
||||
|
|
||||
|
const subscription = pubSubClient.subscription(requestTopic); |
||||
|
|
||||
|
const messageProcessor = new JsInvokeMessageProcessor(new PubSubProducer()); |
||||
|
|
||||
|
const messageHandler = message => { |
||||
|
|
||||
|
messageProcessor.onJsInvokeMessage(message.data.toString('utf8')); |
||||
|
message.ack(); |
||||
|
}; |
||||
|
|
||||
|
subscription.on('message', messageHandler); |
||||
|
|
||||
|
} catch (e) { |
||||
|
logger.error('Failed to start ThingsBoard JavaScript Executor Microservice: %s', e.message); |
||||
|
logger.error(e.stack); |
||||
|
exit(-1); |
||||
|
} |
||||
|
})(); |
||||
|
|
||||
|
process.on('exit', () => { |
||||
|
exit(0); |
||||
|
}); |
||||
|
|
||||
|
async function exit(status) { |
||||
|
logger.info('Exiting with status: %d ...', status); |
||||
|
if (pubSubClient) { |
||||
|
logger.info('Stopping Pub/Sub client.') |
||||
|
try { |
||||
|
await pubSubClient.close(); |
||||
|
logger.info('Pub/Sub client is stopped.') |
||||
|
process.exit(status); |
||||
|
} catch (e) { |
||||
|
logger.info('Pub/Sub client stop error.'); |
||||
|
process.exit(status); |
||||
|
} |
||||
|
} else { |
||||
|
process.exit(status); |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,149 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
|
||||
|
'use strict'; |
||||
|
|
||||
|
const config = require('config'), |
||||
|
JsInvokeMessageProcessor = require('../api/jsInvokeMessageProcessor'), |
||||
|
logger = require('../config/logger')._logger('rabbitmqTemplate'); |
||||
|
|
||||
|
const requestTopic = config.get('request_topic'); |
||||
|
const amqp = require('amqplib/callback_api'); |
||||
|
let connection; |
||||
|
let channel; |
||||
|
let stopped = false; |
||||
|
const responseTopics = []; |
||||
|
|
||||
|
function RabbitMqProducer() { |
||||
|
this.send = async (responseTopic, scriptId, rawResponse, headers) => { |
||||
|
|
||||
|
if (!responseTopics.includes(responseTopic)) { |
||||
|
await createQueue(responseTopic); |
||||
|
responseTopics.push(responseTopic); |
||||
|
} |
||||
|
|
||||
|
let data = JSON.stringify( |
||||
|
{ |
||||
|
key: scriptId, |
||||
|
data: [...rawResponse], |
||||
|
headers: headers |
||||
|
}); |
||||
|
let dataBuffer = Buffer.from(data); |
||||
|
channel.sendToQueue(responseTopic, dataBuffer); |
||||
|
return new Promise((resolve, reject) => { |
||||
|
channel.waitForConfirms((err) => { |
||||
|
if (err) { |
||||
|
reject(err); |
||||
|
} else { |
||||
|
resolve(); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
(async () => { |
||||
|
try { |
||||
|
logger.info('Starting ThingsBoard JavaScript Executor Microservice...'); |
||||
|
|
||||
|
amqp.credentials.amqplain('admin', 'password'); |
||||
|
connection = await new Promise((resolve, reject) => { |
||||
|
amqp.connect('amqp://localhost:5672/', function (err, connection) { |
||||
|
if (err) { |
||||
|
reject(err); |
||||
|
} else { |
||||
|
resolve(connection); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
channel = await new Promise((resolve, reject) => { |
||||
|
connection.createConfirmChannel(function (err, channel) { |
||||
|
if (err) { |
||||
|
reject(err); |
||||
|
} else { |
||||
|
resolve(channel); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
await createQueue(requestTopic); |
||||
|
|
||||
|
const messageProcessor = new JsInvokeMessageProcessor(new RabbitMqProducer()); |
||||
|
|
||||
|
while (!stopped) { |
||||
|
let message = await new Promise((resolve, reject) => { |
||||
|
channel.get(requestTopic, {}, function (err, msg) { |
||||
|
if (err) { |
||||
|
reject(err); |
||||
|
} else { |
||||
|
resolve(msg); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
if (message) { |
||||
|
messageProcessor.onJsInvokeMessage(message.content.toString('utf8')); |
||||
|
channel.ack(message); |
||||
|
} |
||||
|
} |
||||
|
} catch (e) { |
||||
|
logger.error('Failed to start ThingsBoard JavaScript Executor Microservice: %s', e.message); |
||||
|
logger.error(e.stack); |
||||
|
exit(-1); |
||||
|
} |
||||
|
})(); |
||||
|
|
||||
|
function createQueue(topic) { |
||||
|
let params = {durable: false}; |
||||
|
return new Promise((resolve, reject) => { |
||||
|
channel.assertQueue(topic, params, function (err, data) { |
||||
|
if (err) { |
||||
|
reject(err); |
||||
|
} else { |
||||
|
resolve(); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
process.on('exit', () => { |
||||
|
exit(0); |
||||
|
}); |
||||
|
|
||||
|
async function exit(status) { |
||||
|
logger.info('Exiting with status: %d ...', status); |
||||
|
|
||||
|
if (channel) { |
||||
|
logger.info('Stopping RabbitMq chanel.') |
||||
|
await channel.close(); |
||||
|
logger.info('RabbitMq chanel is stopped'); |
||||
|
} |
||||
|
|
||||
|
if (connection) { |
||||
|
logger.info('Stopping RabbitMq connection.') |
||||
|
try { |
||||
|
await connection.close(); |
||||
|
logger.info('RabbitMq client is connection.') |
||||
|
process.exit(status); |
||||
|
} catch (e) { |
||||
|
logger.info('RabbitMq connection stop error.'); |
||||
|
process.exit(status); |
||||
|
} |
||||
|
} else { |
||||
|
process.exit(status); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue