feat: add alert for jambonz parsing falure (#148)

* feat: add alert for jambonz parsing falure

* fix: review comment

* fix: update time-series version
This commit is contained in:
xquanluu
2022-08-16 17:39:07 +07:00
committed by GitHub
parent 7199db5edb
commit 5b6f7dd3ee
5 changed files with 139 additions and 9 deletions

View File

@@ -19,6 +19,10 @@ module.exports = function(srf, logger) {
lookupAppByRealm, lookupAppByRealm,
lookupAppByTeamsTenant lookupAppByTeamsTenant
} = srf.locals.dbHelpers; } = srf.locals.dbHelpers;
const {
writeAlerts,
AlertType
} = srf.locals;
const {lookupAccountDetails} = dbUtils(logger, srf); const {lookupAccountDetails} = dbUtils(logger, srf);
function initLocals(req, res, next) { function initLocals(req, res, next) {
@@ -311,6 +315,12 @@ module.exports = function(srf, logger) {
} catch (err) { } catch (err) {
span?.setAttributes({webhookStatus: err.statusCode}); span?.setAttributes({webhookStatus: err.statusCode});
span?.end(); span?.end();
writeAlerts({
account_sid: req.locals.account_sid,
target_sid: req.locals.callSid,
alert_type: AlertType.INVALID_APP_PAYLOAD,
message: `${err?.message}`.trim()
}).catch((err) => this.logger.info({err}, 'Error generating alert for parsing application'));
logger.info({err}, `Error retrieving or parsing application: ${err?.message}`); logger.info({err}, `Error retrieving or parsing application: ${err?.message}`);
res.send(480, {headers: {'X-Reason': err?.message || 'unknown'}}); res.send(480, {headers: {'X-Reason': err?.message || 'unknown'}});
app.requestor.close(); app.requestor.close();

14
package-lock.json generated
View File

@@ -13,7 +13,7 @@
"@jambonz/http-health-check": "^0.0.1", "@jambonz/http-health-check": "^0.0.1",
"@jambonz/realtimedb-helpers": "^0.4.29", "@jambonz/realtimedb-helpers": "^0.4.29",
"@jambonz/stats-collector": "^0.1.6", "@jambonz/stats-collector": "^0.1.6",
"@jambonz/time-series": "^0.1.9", "@jambonz/time-series": "^0.1.10",
"@opentelemetry/api": "^1.1.0", "@opentelemetry/api": "^1.1.0",
"@opentelemetry/exporter-jaeger": "^1.3.1", "@opentelemetry/exporter-jaeger": "^1.3.1",
"@opentelemetry/exporter-trace-otlp-http": "^0.27.0", "@opentelemetry/exporter-trace-otlp-http": "^0.27.0",
@@ -566,9 +566,9 @@
} }
}, },
"node_modules/@jambonz/time-series": { "node_modules/@jambonz/time-series": {
"version": "0.1.9", "version": "0.1.10",
"resolved": "https://registry.npmjs.org/@jambonz/time-series/-/time-series-0.1.9.tgz", "resolved": "https://registry.npmjs.org/@jambonz/time-series/-/time-series-0.1.10.tgz",
"integrity": "sha512-FLD7mLGEMToG7s6LGWr/GtNp61753RBxyYuKSxCVI6G14bm6ydgcBdG85h8EGTVQlIp9dnwbn5ebSVsGb8a46w==", "integrity": "sha512-4wJcTPFjUJV6WWQEgqcrJrVU7kPUjmIbr43euGmpNDURCtt44MLBtrX7fF3SB5TWEAyno8z5hyN5bJPyEEHuGA==",
"dependencies": { "dependencies": {
"debug": "^4.3.1", "debug": "^4.3.1",
"influx": "^5.8.0" "influx": "^5.8.0"
@@ -6714,9 +6714,9 @@
} }
}, },
"@jambonz/time-series": { "@jambonz/time-series": {
"version": "0.1.9", "version": "0.1.10",
"resolved": "https://registry.npmjs.org/@jambonz/time-series/-/time-series-0.1.9.tgz", "resolved": "https://registry.npmjs.org/@jambonz/time-series/-/time-series-0.1.10.tgz",
"integrity": "sha512-FLD7mLGEMToG7s6LGWr/GtNp61753RBxyYuKSxCVI6G14bm6ydgcBdG85h8EGTVQlIp9dnwbn5ebSVsGb8a46w==", "integrity": "sha512-4wJcTPFjUJV6WWQEgqcrJrVU7kPUjmIbr43euGmpNDURCtt44MLBtrX7fF3SB5TWEAyno8z5hyN5bJPyEEHuGA==",
"requires": { "requires": {
"debug": "^4.3.1", "debug": "^4.3.1",
"influx": "^5.8.0" "influx": "^5.8.0"

View File

@@ -30,7 +30,7 @@
"@jambonz/http-health-check": "^0.0.1", "@jambonz/http-health-check": "^0.0.1",
"@jambonz/realtimedb-helpers": "^0.4.29", "@jambonz/realtimedb-helpers": "^0.4.29",
"@jambonz/stats-collector": "^0.1.6", "@jambonz/stats-collector": "^0.1.6",
"@jambonz/time-series": "^0.1.9", "@jambonz/time-series": "^0.1.10",
"@opentelemetry/api": "^1.1.0", "@opentelemetry/api": "^1.1.0",
"@opentelemetry/exporter-jaeger": "^1.3.1", "@opentelemetry/exporter-jaeger": "^1.3.1",
"@opentelemetry/exporter-trace-otlp-http": "^0.27.0", "@opentelemetry/exporter-trace-otlp-http": "^0.27.0",

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE scenario SYSTEM "sipp.dtd">
<scenario name="Basic Sipstone UAC">
<!-- In client mode (sipp placing calls), the Call-ID MUST be -->
<!-- generated by sipp. To do so, use [call_id] keyword. -->
<send retrans="500">
<![CDATA[
INVITE sip:[to]@[remote_ip]:[remote_port] SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: [from] <sip:[from]@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
To: <sip:[to]@[remote_ip]:[remote_port]>
Call-ID: [call_id]
CSeq: 1 INVITE
Contact: sip:[from]@[local_ip]:[local_port]
Max-Forwards: 70
X-Account-Sid: bb845d4b-83a9-4cde-a6e9-50f3743bab3f
Subject: uac-say
Content-Type: application/sdp
Content-Length: [len]
v=0
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
s=-
c=IN IP[media_ip_type] [media_ip]
t=0 0
m=audio [media_port] RTP/AVP 0
a=rtpmap:0 PCMU/8000
]]>
</send>
<recv response="100"
optional="true">
</recv>
<recv response="180" optional="true">
</recv>
<recv response="183" optional="true">
</recv>
<!-- By adding rrs="true" (Record Route Sets), the route sets -->
<!-- are saved and used for following messages sent. Useful to test -->
<!-- against stateful SIP proxies/B2BUAs. -->
<recv response="480" rtd="true">
</recv>
<!-- Packet lost can be simulated in any send/recv message by -->
<!-- by adding the 'lost = "10"'. Value can be [1-100] percent. -->
<send>
<![CDATA[
ACK sip:[to]@[remote_ip]:[remote_port] SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: [from] <sip:[from]@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
To: [to] <sip:[to]@[remote_ip]:[remote_port]>[peer_tag_param]
Call-ID: [call_id]
CSeq: 1 ACK
Contact: sip:[from]@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-say
Content-Length: 0
]]>
</send>
</scenario>

View File

@@ -1,6 +1,15 @@
const test = require('tape'); const test = require('tape');
const { sippUac } = require('./sipp')('test_fs'); const { sippUac } = require('./sipp')('test_fs');
const clearModule = require('clear-module'); const clearModule = require('clear-module');
const provisionCallHook = require('./utils');
const opts = {
timestamp: () => {return `, "time": "${new Date().toISOString()}"`;},
level: process.env.JAMBONES_LOGLEVEL || 'info'
};
const logger = require('pino')(opts);
const { queryAlerts } = require('@jambonz/time-series')(
logger, process.env.JAMBONES_TIME_SERIES_HOST
);
process.on('unhandledRejection', (reason, p) => { process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
@@ -28,7 +37,7 @@ test('basic webhook tests', async(t) => {
reason: 'Gone Fishin', reason: 'Gone Fishin',
headers: { headers: {
'Retry-After': 300 'Retry-After': 300
} }
} }
]; ];
@@ -45,3 +54,43 @@ test('basic webhook tests', async(t) => {
t.error(err); t.error(err);
} }
}); });
test('invalid jambonz json create alert tests', async(t) => {
clearModule.all();
const {srf, disconnect} = require('../app');
try {
await connect(srf);
// GIVEN
// Invalid json array
const verbs = {
verb: 'say',
text: 'hello'
};
const from = 'invalid_json_create_alert';
provisionCallHook(from, verbs)
// THEN
await sippUac('uac-invite-expect-480.xml', '172.38.0.10', from);
// sleep testcase for more than 7 second to wait alert pushed to database.
await sleep(8000);
const data = await queryAlerts(
{account_sid: 'bb845d4b-83a9-4cde-a6e9-50f3743bab3f', page: 1, page_size: 25, days: 7});
let checked = false;
for (let i = 0; i < data.total; i++) {
checked = data.data[i].message === 'malformed jambonz payload: must be array'
}
t.ok(checked, 'alert is raised as expected');
disconnect();
} catch (err) {
console.log(`error received: ${err}`);
disconnect();
t.error(err);
}
});
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}