mirror of
https://github.com/jambonz/sbc-sip-sidecar.git
synced 2025-12-19 04:27:46 +00:00
first version
This commit is contained in:
46
.gitignore
vendored
Normal file
46
.gitignore
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# github pages site
|
||||
_site
|
||||
|
||||
#transient test cases
|
||||
examples/nosave.*.js
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul and nyc
|
||||
coverage
|
||||
.nyc_output/
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
node_modules
|
||||
|
||||
.DS_Store
|
||||
|
||||
examples/*
|
||||
|
||||
create_db.sql
|
||||
|
||||
.vscode
|
||||
|
||||
.env.*
|
||||
.env
|
||||
|
||||
82
app.js
Normal file
82
app.js
Normal file
@@ -0,0 +1,82 @@
|
||||
const assert = require('assert');
|
||||
assert.ok(process.env.JAMBONES_MYSQL_HOST &&
|
||||
process.env.JAMBONES_MYSQL_USER &&
|
||||
process.env.JAMBONES_MYSQL_PASSWORD &&
|
||||
process.env.JAMBONES_MYSQL_DATABASE, 'missing JAMBONES_MYSQL_XXX env vars');
|
||||
assert.ok(process.env.JAMBONES_REDIS_HOST, 'missing JAMBONES_REDIS_HOST env var');
|
||||
assert.ok(process.env.DRACHTIO_HOST, 'missing DRACHTIO_HOST env var');
|
||||
assert.ok(process.env.DRACHTIO_PORT, 'missing DRACHTIO_PORT env var');
|
||||
assert.ok(process.env.DRACHTIO_SECRET, 'missing DRACHTIO_SECRET env var');
|
||||
const opts = Object.assign({
|
||||
timestamp: () => { return `, "time": "${new Date().toISOString()}"`; }
|
||||
}, { level: process.env.JAMBONES_LOGLEVEL || 'info' });
|
||||
const logger = require('pino')(opts);
|
||||
const Srf = require('drachtio-srf');
|
||||
const srf = new Srf();
|
||||
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-sip`;
|
||||
|
||||
const {
|
||||
lookupAllVoipCarriers,
|
||||
lookupSipGatewaysByCarrier,
|
||||
} = require('@jambonz/db-helpers')({
|
||||
host: process.env.JAMBONES_MYSQL_HOST,
|
||||
user: process.env.JAMBONES_MYSQL_USER,
|
||||
password: process.env.JAMBONES_MYSQL_PASSWORD,
|
||||
database: process.env.JAMBONES_MYSQL_DATABASE,
|
||||
connectionLimit: process.env.JAMBONES_MYSQL_CONNECTION_LIMIT || 10
|
||||
}, logger);
|
||||
|
||||
const {
|
||||
retrieveSet } = require('@jambonz/realtimedb-helpers')({
|
||||
host: process.env.JAMBONES_REDIS_HOST || 'localhost',
|
||||
port: process.env.JAMBONES_REDIS_PORT || 6379
|
||||
}, logger);
|
||||
|
||||
srf.locals = {
|
||||
...srf.locals,
|
||||
dbHelpers: {
|
||||
lookupAllVoipCarriers,
|
||||
lookupSipGatewaysByCarrier,
|
||||
},
|
||||
realtimeDbHelpers: {
|
||||
retrieveSet
|
||||
}
|
||||
};
|
||||
|
||||
srf.connect({ host: process.env.DRACHTIO_HOST, port: process.env.DRACHTIO_PORT, secret: process.env.DRACHTIO_SECRET });
|
||||
srf.on('connect', (err, hp) => {
|
||||
const ativateRegBot = async(err, hp) => {
|
||||
if (err) return logger.error({ err }, 'Error connecting to drachtio server');
|
||||
logger.info(`connected to drachtio listening on ${hp}`);
|
||||
// Only run when I'm the first member in the set Of Actip Sip SBC
|
||||
const set = await retrieveSet(setName);
|
||||
const newArray = Array.from(set);
|
||||
let startRegBot = !newArray || newArray.length == 0;
|
||||
if (!startRegBot) {
|
||||
const firstSbc = newArray.at(0);
|
||||
const hostports = hp.split(',');
|
||||
for (const hp of hostports) {
|
||||
const arr = /^(.*)\/(.*:\d+)$/.exec(hp);
|
||||
if (firstSbc === arr[2]) {
|
||||
startRegBot = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (startRegBot) {
|
||||
srf.locals.regbotStatus = require('./lib/sip-trunk-register')(logger, srf);
|
||||
} else {
|
||||
// Timer 30 seconds to make sure the task is transfered to another SBC outbound handler
|
||||
setTimeout(ativateRegBot.bind(this, err, hp), 30 * 1000);
|
||||
}
|
||||
};
|
||||
ativateRegBot(err, hp);
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
srf.on('error', (err) => {
|
||||
logger.info(err, 'Error connecting to drachtio');
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { srf, logger };
|
||||
164
lib/sip-trunk-register.js
Normal file
164
lib/sip-trunk-register.js
Normal file
@@ -0,0 +1,164 @@
|
||||
const debug = require('debug')('jambonz:sbc-registrar');
|
||||
const assert = require('assert');
|
||||
const DEFAULT_EXPIRES = 3600;
|
||||
|
||||
const regbots = [];
|
||||
const carriers = [];
|
||||
const gateways = [];
|
||||
|
||||
class Regbot {
|
||||
constructor(logger, opts) {
|
||||
this.logger = logger;
|
||||
|
||||
['ipv4', 'port', 'username', 'password', 'sip_realm'].forEach((prop) => this[prop] = opts[prop]);
|
||||
|
||||
this.username = opts.username;
|
||||
this.password = opts.password;
|
||||
this.sip_realm = opts.sip_realm || opts.ipv4;
|
||||
this.ipv4 = opts.ipv4;
|
||||
this.port = opts.port;
|
||||
this.aor = `${this.username}@${this.sip_realm}`;
|
||||
this.status = 'none';
|
||||
}
|
||||
|
||||
start(srf) {
|
||||
assert(!this.timer);
|
||||
this.register(srf);
|
||||
}
|
||||
|
||||
stop() {
|
||||
assert(this.timer);
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
username: this.username,
|
||||
sip_realm: this.sip_realm,
|
||||
ipv4: this.ipv4,
|
||||
port: this.port,
|
||||
aor: this.aor,
|
||||
status: this.status
|
||||
};
|
||||
}
|
||||
|
||||
async register(srf) {
|
||||
try {
|
||||
const req = await srf.request(`sip:${this.aor}`, {
|
||||
method: 'REGISTER',
|
||||
proxy: `sip:${this.ipv4}:${this.port}`,
|
||||
headers: {
|
||||
'From': `sip:${this.aor}`,
|
||||
'Contact': `<sip:${this.aor}>;expires=${DEFAULT_EXPIRES}`,
|
||||
'Expires': DEFAULT_EXPIRES
|
||||
},
|
||||
auth: {
|
||||
username: this.username,
|
||||
password: this.password
|
||||
}
|
||||
});
|
||||
req.on('response', (res) => {
|
||||
if (res.status !== 200) {
|
||||
this.status = 'fail';
|
||||
this.logger.info(`Regbot: got ${res.status} registering to ${this.sip_realm} at ${this.ipv4}:${this.port}`);
|
||||
this.timer = setTimeout(this.register.bind(this, srf), 30 * 1000);
|
||||
}
|
||||
else {
|
||||
this.status = 'registered';
|
||||
let expires = DEFAULT_EXPIRES;
|
||||
const contact = res.getParsedHeader('Contact');
|
||||
if (contact.length > 0 && contact[0].params && contact[0].params.expires) {
|
||||
if (contact[0].params.expires) expires = parseInt(contact[0].params.expires);
|
||||
}
|
||||
else if (res.has('Expires')) {
|
||||
expires = parseInt(res.get('Expires'));
|
||||
}
|
||||
if (isNaN(expires) || expires < 30) expires = DEFAULT_EXPIRES;
|
||||
debug(`setting timer for next register to ${expires} seconds`);
|
||||
this.timer = setTimeout(this.register.bind(this, srf), (expires - 5) * 1000);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
this.logger.error({ err }, `Regbot Error registering to ${this.ipv4}:${this.port}`);
|
||||
this.timer = setTimeout(this.register.bind(this, srf), 60 * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = (logger, srf) => {
|
||||
|
||||
// check for new / changed carriers every 30 seconds
|
||||
setInterval(() => { getCarriers(logger, srf); }, 30000);
|
||||
|
||||
// do initial setup
|
||||
getCarriers(logger, srf);
|
||||
|
||||
return function() {
|
||||
debug(`status: we have ${regbots.length} regbots`);
|
||||
return {
|
||||
total: regbots.length,
|
||||
registered: regbots.reduce((acc, current) => {
|
||||
return current.status === 'registered' ? ++acc : acc;
|
||||
}, 0)
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const getCarriers = async(logger, srf) => {
|
||||
// Check if We are
|
||||
const { lookupAllVoipCarriers, lookupSipGatewaysByCarrier } = srf.locals.dbHelpers;
|
||||
try {
|
||||
|
||||
/* first check: has anything changed (new carriers or gateways)? */
|
||||
let hasChanged = false;
|
||||
const gws = [];
|
||||
const cs = (await lookupAllVoipCarriers())
|
||||
.filter((c) => c.requires_register);
|
||||
if (JSON.stringify(cs) !== JSON.stringify(carriers)) hasChanged = true;
|
||||
for (const c of cs) {
|
||||
try {
|
||||
const arr = (await lookupSipGatewaysByCarrier(c.voip_carrier_sid))
|
||||
.filter((gw) => gw.outbound && gw.is_active)
|
||||
.map((gw) => {
|
||||
gw.carrier = c;
|
||||
return gw;
|
||||
});
|
||||
Array.prototype.push.apply(gws, arr);
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'getCarriers Error retrieving gateways');
|
||||
}
|
||||
}
|
||||
if (JSON.stringify(gws) !== JSON.stringify(gateways)) hasChanged = true;
|
||||
|
||||
if (hasChanged) {
|
||||
debug('getCarriers: got new or changed carriers');
|
||||
logger.info('getCarriers: got new or changed carriers');
|
||||
carriers.length = 0;
|
||||
Array.prototype.push.apply(carriers, cs);
|
||||
|
||||
gateways.length = 0;
|
||||
Array.prototype.push.apply(gateways, gws);
|
||||
|
||||
// stop / kill existing regbots
|
||||
regbots.forEach((rb) => rb.stop());
|
||||
regbots.length = 0;
|
||||
|
||||
// start new regbots
|
||||
for (const gw of gateways) {
|
||||
const rb = new Regbot(logger, {
|
||||
ipv4: gw.ipv4,
|
||||
port: gw.port,
|
||||
username: gw.carrier.register_username,
|
||||
password: gw.carrier.register_password,
|
||||
sip_realm: gw.carrier.register_sip_realm
|
||||
});
|
||||
regbots.push(rb);
|
||||
rb.start(srf);
|
||||
logger.info({ regbot: rb.toJSON() }, 'Starting regbot');
|
||||
}
|
||||
debug(`getCarriers: we have ${regbots.length} regbots`);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'getCarriers Error');
|
||||
}
|
||||
};
|
||||
6048
package-lock.json
generated
Normal file
6048
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
42
package.json
Normal file
42
package.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "sbc-outbound-handler",
|
||||
"version": "0.0.1",
|
||||
"description": "SBC Outbound Handler",
|
||||
"main": "app.js",
|
||||
"engines": {
|
||||
"node": ">= 14.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"sip",
|
||||
"drachtio"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "node app",
|
||||
"test": "NODE_ENV=test JAMBONES_HOSTING=1 HTTP_POOL=1 DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9022 DRACHTIO_SECRET=cymru JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3306 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=127.0.0.1 JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=error ENABLE_METRICS=0 HTTP_PORT=3000 JAMBONES_SBCS=172.39.0.10 JAMBONES_FREESWITCH=127.0.0.1:8022:ClueCon:docker-host JAMBONES_TIME_SERIES_HOST=127.0.0.1 JAMBONES_NETWORK_CIDR=172.39.0.0/16 node test/ ",
|
||||
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
|
||||
"jslint": "eslint app.js lib"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/xquanluu/sbc-outbound-handler.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/xquanluu/sbc-outbound-handler/issues"
|
||||
},
|
||||
"homepage": "https://github.com/xquanluu/sbc-outbound-handler#readme",
|
||||
"dependencies": {
|
||||
"@jambonz/db-helpers": "^0.6.18",
|
||||
"@jambonz/realtimedb-helpers": "^0.4.29",
|
||||
"@jambonz/time-series": "^0.1.12",
|
||||
"drachtio-srf": "^4.5.17",
|
||||
"pino": "^6.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"clear-module": "^4.1.2",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-promise": "^4.3.1",
|
||||
"tape": "^4.15.1"
|
||||
}
|
||||
}
|
||||
26
test/create-test-db.js
Normal file
26
test/create-test-db.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const test = require('tape') ;
|
||||
const exec = require('child_process').exec ;
|
||||
|
||||
test('creating jambones_test database', (t) => {
|
||||
exec(`mysql -h 127.0.0.1 -u root --protocol=tcp < ${__dirname}/db/create_test_db.sql`, (err, stdout, stderr) => {
|
||||
if (err) return t.end(err);
|
||||
t.pass('database successfully created');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('creating schema', (t) => {
|
||||
exec(`mysql -h 127.0.0.1 -u root --protocol=tcp -D jambones_test < ${__dirname}/db/jambones-sql.sql`, (err, stdout, stderr) => {
|
||||
if (err) return t.end(err);
|
||||
t.pass('schema successfully created');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('populating test case data', (t) => {
|
||||
exec(`mysql -h 127.0.0.1 -u root --protocol=tcp -D jambones_test < ${__dirname}/db/populate-test-data.sql`, (err, stdout, stderr) => {
|
||||
if (err) return t.end(err);
|
||||
t.pass('test data set created');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
3
test/db/create_test_db.sql
Normal file
3
test/db/create_test_db.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
create database jambones_test;
|
||||
create user jambones_test@'%' IDENTIFIED WITH mysql_native_password by 'jambones_test';
|
||||
grant all on jambones_test.* to jambones_test@'%';
|
||||
499
test/db/jambones-sql.sql
Normal file
499
test/db/jambones-sql.sql
Normal file
@@ -0,0 +1,499 @@
|
||||
/* SQLEditor (MySQL (2))*/
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
DROP TABLE IF EXISTS account_static_ips;
|
||||
|
||||
DROP TABLE IF EXISTS account_products;
|
||||
|
||||
DROP TABLE IF EXISTS account_subscriptions;
|
||||
|
||||
DROP TABLE IF EXISTS beta_invite_codes;
|
||||
|
||||
DROP TABLE IF EXISTS call_routes;
|
||||
|
||||
DROP TABLE IF EXISTS dns_records;
|
||||
|
||||
DROP TABLE IF EXISTS lcr_carrier_set_entry;
|
||||
|
||||
DROP TABLE IF EXISTS lcr_routes;
|
||||
|
||||
DROP TABLE IF EXISTS predefined_sip_gateways;
|
||||
|
||||
DROP TABLE IF EXISTS predefined_carriers;
|
||||
|
||||
DROP TABLE IF EXISTS account_offers;
|
||||
|
||||
DROP TABLE IF EXISTS products;
|
||||
|
||||
DROP TABLE IF EXISTS api_keys;
|
||||
|
||||
DROP TABLE IF EXISTS sbc_addresses;
|
||||
|
||||
DROP TABLE IF EXISTS signup_history;
|
||||
|
||||
DROP TABLE IF EXISTS ms_teams_tenants;
|
||||
|
||||
DROP TABLE IF EXISTS speech_credentials;
|
||||
|
||||
DROP TABLE IF EXISTS users;
|
||||
|
||||
DROP TABLE IF EXISTS phone_numbers;
|
||||
|
||||
DROP TABLE IF EXISTS sip_gateways;
|
||||
|
||||
DROP TABLE IF EXISTS voip_carriers;
|
||||
|
||||
DROP TABLE IF EXISTS accounts;
|
||||
|
||||
DROP TABLE IF EXISTS applications;
|
||||
|
||||
DROP TABLE IF EXISTS service_providers;
|
||||
|
||||
DROP TABLE IF EXISTS webhooks;
|
||||
|
||||
CREATE TABLE account_static_ips
|
||||
(
|
||||
account_static_ip_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
public_ipv4 VARCHAR(16) NOT NULL UNIQUE ,
|
||||
private_ipv4 VARBINARY(16) NOT NULL UNIQUE ,
|
||||
PRIMARY KEY (account_static_ip_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE account_subscriptions
|
||||
(
|
||||
account_subscription_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
pending BOOLEAN NOT NULL DEFAULT false,
|
||||
effective_start_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
effective_end_date DATETIME,
|
||||
change_reason VARCHAR(255),
|
||||
stripe_subscription_id VARCHAR(56),
|
||||
stripe_payment_method_id VARCHAR(56),
|
||||
stripe_statement_descriptor VARCHAR(255),
|
||||
last4 VARCHAR(512),
|
||||
exp_month INTEGER,
|
||||
exp_year INTEGER,
|
||||
card_type VARCHAR(16),
|
||||
pending_reason VARBINARY(52),
|
||||
PRIMARY KEY (account_subscription_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE beta_invite_codes
|
||||
(
|
||||
invite_code CHAR(6) NOT NULL UNIQUE ,
|
||||
in_use BOOLEAN NOT NULL DEFAULT false,
|
||||
PRIMARY KEY (invite_code)
|
||||
);
|
||||
|
||||
CREATE TABLE call_routes
|
||||
(
|
||||
call_route_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
priority INTEGER NOT NULL,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
regex VARCHAR(255) NOT NULL,
|
||||
application_sid CHAR(36) NOT NULL,
|
||||
PRIMARY KEY (call_route_sid)
|
||||
) COMMENT='a regex-based pattern match for call routing';
|
||||
|
||||
CREATE TABLE dns_records
|
||||
(
|
||||
dns_record_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
record_type VARCHAR(6) NOT NULL,
|
||||
record_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (dns_record_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE lcr_routes
|
||||
(
|
||||
lcr_route_sid CHAR(36),
|
||||
regex VARCHAR(32) NOT NULL COMMENT 'regex-based pattern match against dialed number, used for LCR routing of PSTN calls',
|
||||
description VARCHAR(1024),
|
||||
priority INTEGER NOT NULL UNIQUE COMMENT 'lower priority routes are attempted first',
|
||||
PRIMARY KEY (lcr_route_sid)
|
||||
) COMMENT='Least cost routing table';
|
||||
|
||||
CREATE TABLE predefined_carriers
|
||||
(
|
||||
predefined_carrier_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
requires_static_ip BOOLEAN NOT NULL DEFAULT false,
|
||||
e164_leading_plus BOOLEAN NOT NULL DEFAULT false COMMENT 'if true, a leading plus should be prepended to outbound phone numbers',
|
||||
requires_register BOOLEAN NOT NULL DEFAULT false,
|
||||
register_username VARCHAR(64),
|
||||
register_sip_realm VARCHAR(64),
|
||||
register_password VARCHAR(64),
|
||||
tech_prefix VARCHAR(16) COMMENT 'tech prefix to prepend to outbound calls to this carrier',
|
||||
inbound_auth_username VARCHAR(64),
|
||||
inbound_auth_password VARCHAR(64),
|
||||
diversion VARCHAR(32),
|
||||
PRIMARY KEY (predefined_carrier_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE predefined_sip_gateways
|
||||
(
|
||||
predefined_sip_gateway_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
ipv4 VARCHAR(128) NOT NULL COMMENT 'ip address or DNS name of the gateway. For gateways providing inbound calling service, ip address is required.',
|
||||
port INTEGER NOT NULL DEFAULT 5060 COMMENT 'sip signaling port',
|
||||
inbound BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound calls from the gateway',
|
||||
outbound BOOLEAN NOT NULL COMMENT 'if true, include in least-cost routing when placing calls to the PSTN',
|
||||
netmask INTEGER NOT NULL DEFAULT 32,
|
||||
predefined_carrier_sid CHAR(36) NOT NULL,
|
||||
PRIMARY KEY (predefined_sip_gateway_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE products
|
||||
(
|
||||
product_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(32) NOT NULL,
|
||||
category ENUM('api_rate','voice_call_session', 'device') NOT NULL,
|
||||
PRIMARY KEY (product_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE account_products
|
||||
(
|
||||
account_product_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
account_subscription_sid CHAR(36) NOT NULL,
|
||||
product_sid CHAR(36) NOT NULL,
|
||||
quantity INTEGER NOT NULL,
|
||||
PRIMARY KEY (account_product_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE account_offers
|
||||
(
|
||||
account_offer_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
product_sid CHAR(36) NOT NULL,
|
||||
stripe_product_id VARCHAR(56) NOT NULL,
|
||||
PRIMARY KEY (account_offer_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE api_keys
|
||||
(
|
||||
api_key_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
token CHAR(36) NOT NULL UNIQUE ,
|
||||
account_sid CHAR(36),
|
||||
service_provider_sid CHAR(36),
|
||||
expires_at TIMESTAMP NULL DEFAULT NULL,
|
||||
last_used TIMESTAMP NULL DEFAULT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (api_key_sid)
|
||||
) COMMENT='An authorization token that is used to access the REST api';
|
||||
|
||||
CREATE TABLE sbc_addresses
|
||||
(
|
||||
sbc_address_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
ipv4 VARCHAR(255) NOT NULL,
|
||||
port INTEGER NOT NULL DEFAULT 5060,
|
||||
service_provider_sid CHAR(36),
|
||||
PRIMARY KEY (sbc_address_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE signup_history
|
||||
(
|
||||
email VARCHAR(255) NOT NULL,
|
||||
name VARCHAR(255),
|
||||
signed_up_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (email)
|
||||
);
|
||||
|
||||
CREATE TABLE ms_teams_tenants
|
||||
(
|
||||
ms_teams_tenant_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
service_provider_sid CHAR(36) NOT NULL,
|
||||
account_sid CHAR(36) NOT NULL,
|
||||
application_sid CHAR(36),
|
||||
tenant_fqdn VARCHAR(255) NOT NULL UNIQUE ,
|
||||
PRIMARY KEY (ms_teams_tenant_sid)
|
||||
) COMMENT='A Microsoft Teams customer tenant';
|
||||
|
||||
CREATE TABLE speech_credentials
|
||||
(
|
||||
speech_credential_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
service_provider_sid CHAR(36),
|
||||
account_sid CHAR(36),
|
||||
vendor VARCHAR(32) NOT NULL,
|
||||
credential VARCHAR(8192) NOT NULL,
|
||||
use_for_tts BOOLEAN DEFAULT true,
|
||||
use_for_stt BOOLEAN DEFAULT true,
|
||||
last_used DATETIME,
|
||||
last_tested DATETIME,
|
||||
tts_tested_ok BOOLEAN,
|
||||
stt_tested_ok BOOLEAN,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (speech_credential_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE users
|
||||
(
|
||||
user_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
pending_email VARCHAR(255),
|
||||
phone VARCHAR(20) UNIQUE ,
|
||||
hashed_password VARCHAR(1024),
|
||||
account_sid CHAR(36),
|
||||
service_provider_sid CHAR(36),
|
||||
force_change BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
provider VARCHAR(255) NOT NULL,
|
||||
provider_userid VARCHAR(255),
|
||||
scope VARCHAR(16) NOT NULL DEFAULT 'read-write',
|
||||
phone_activation_code VARCHAR(16),
|
||||
email_activation_code VARCHAR(16),
|
||||
email_validated BOOLEAN NOT NULL DEFAULT false,
|
||||
phone_validated BOOLEAN NOT NULL DEFAULT false,
|
||||
email_content_opt_out BOOLEAN NOT NULL DEFAULT false,
|
||||
PRIMARY KEY (user_sid)
|
||||
);
|
||||
|
||||
CREATE TABLE voip_carriers
|
||||
(
|
||||
voip_carrier_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
description VARCHAR(255),
|
||||
account_sid CHAR(36) COMMENT 'if provided, indicates this entity represents a sip trunk that is associated with a specific account',
|
||||
application_sid CHAR(36) COMMENT 'If provided, all incoming calls from this source will be routed to the associated application',
|
||||
e164_leading_plus BOOLEAN NOT NULL DEFAULT false COMMENT 'if true, a leading plus should be prepended to outbound phone numbers',
|
||||
requires_register BOOLEAN NOT NULL DEFAULT false,
|
||||
register_username VARCHAR(64),
|
||||
register_sip_realm VARCHAR(64),
|
||||
register_password VARCHAR(64),
|
||||
tech_prefix VARCHAR(16) COMMENT 'tech prefix to prepend to outbound calls to this carrier',
|
||||
inbound_auth_username VARCHAR(64),
|
||||
inbound_auth_password VARCHAR(64),
|
||||
diversion VARCHAR(32),
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (voip_carrier_sid)
|
||||
) COMMENT='A Carrier or customer PBX that can send or receive calls';
|
||||
|
||||
CREATE TABLE phone_numbers
|
||||
(
|
||||
phone_number_sid CHAR(36) UNIQUE ,
|
||||
number VARCHAR(32) NOT NULL UNIQUE ,
|
||||
voip_carrier_sid CHAR(36),
|
||||
account_sid CHAR(36),
|
||||
application_sid CHAR(36),
|
||||
service_provider_sid CHAR(36) COMMENT 'if not null, this number is a test number for the associated service provider',
|
||||
PRIMARY KEY (phone_number_sid)
|
||||
) COMMENT='A phone number that has been assigned to an account';
|
||||
|
||||
CREATE TABLE sip_gateways
|
||||
(
|
||||
sip_gateway_sid CHAR(36),
|
||||
ipv4 VARCHAR(128) NOT NULL COMMENT 'ip address or DNS name of the gateway. For gateways providing inbound calling service, ip address is required.',
|
||||
netmask INTEGER NOT NULL DEFAULT 32,
|
||||
port INTEGER NOT NULL DEFAULT 5060 COMMENT 'sip signaling port',
|
||||
inbound BOOLEAN NOT NULL COMMENT 'if true, whitelist this IP to allow inbound calls from the gateway',
|
||||
outbound BOOLEAN NOT NULL COMMENT 'if true, include in least-cost routing when placing calls to the PSTN',
|
||||
voip_carrier_sid CHAR(36) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (sip_gateway_sid)
|
||||
) COMMENT='A whitelisted sip gateway used for origination/termination';
|
||||
|
||||
CREATE TABLE lcr_carrier_set_entry
|
||||
(
|
||||
lcr_carrier_set_entry_sid CHAR(36),
|
||||
workload INTEGER NOT NULL DEFAULT 1 COMMENT 'represents a proportion of traffic to send through the associated carrier; can be used for load balancing traffic across carriers with a common priority for a destination',
|
||||
lcr_route_sid CHAR(36) NOT NULL,
|
||||
voip_carrier_sid CHAR(36) NOT NULL,
|
||||
priority INTEGER NOT NULL DEFAULT 0 COMMENT 'lower priority carriers are attempted first',
|
||||
PRIMARY KEY (lcr_carrier_set_entry_sid)
|
||||
) COMMENT='An entry in the LCR routing list';
|
||||
|
||||
CREATE TABLE webhooks
|
||||
(
|
||||
webhook_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
url VARCHAR(1024) NOT NULL,
|
||||
method ENUM("GET","POST") NOT NULL DEFAULT 'POST',
|
||||
username VARCHAR(255),
|
||||
password VARCHAR(255),
|
||||
PRIMARY KEY (webhook_sid)
|
||||
) COMMENT='An HTTP callback';
|
||||
|
||||
CREATE TABLE applications
|
||||
(
|
||||
application_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
service_provider_sid CHAR(36) COMMENT 'if non-null, this application is a test application that can be used by any account under the associated service provider',
|
||||
account_sid CHAR(36) COMMENT 'account that this application belongs to (if null, this is a service provider test application)',
|
||||
call_hook_sid CHAR(36) COMMENT 'webhook to call for inbound calls ',
|
||||
call_status_hook_sid CHAR(36) COMMENT 'webhook to call for call status events',
|
||||
messaging_hook_sid CHAR(36) COMMENT 'webhook to call for inbound SMS/MMS ',
|
||||
speech_synthesis_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
speech_synthesis_language VARCHAR(12) NOT NULL DEFAULT 'en-US',
|
||||
speech_synthesis_voice VARCHAR(64),
|
||||
speech_recognizer_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||
speech_recognizer_language VARCHAR(64) NOT NULL DEFAULT 'en-US',
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (application_sid)
|
||||
) COMMENT='A defined set of behaviors to be applied to phone calls ';
|
||||
|
||||
CREATE TABLE service_providers
|
||||
(
|
||||
service_provider_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) NOT NULL UNIQUE ,
|
||||
description VARCHAR(255),
|
||||
root_domain VARCHAR(128) UNIQUE ,
|
||||
registration_hook_sid CHAR(36),
|
||||
ms_teams_fqdn VARCHAR(255),
|
||||
PRIMARY KEY (service_provider_sid)
|
||||
) COMMENT='A partition of the platform used by one service provider';
|
||||
|
||||
CREATE TABLE accounts
|
||||
(
|
||||
account_sid CHAR(36) NOT NULL UNIQUE ,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
sip_realm VARCHAR(132) UNIQUE COMMENT 'sip domain that will be used for devices registering under this account',
|
||||
service_provider_sid CHAR(36) NOT NULL COMMENT 'service provider that owns the customer relationship with this account',
|
||||
registration_hook_sid CHAR(36) COMMENT 'webhook to call when devices underr this account attempt to register',
|
||||
device_calling_application_sid CHAR(36) COMMENT 'application to use for outbound calling from an account',
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
plan_type ENUM('trial','free','paid') NOT NULL DEFAULT 'trial',
|
||||
stripe_customer_id VARCHAR(56),
|
||||
webhook_secret VARCHAR(36) NOT NULL,
|
||||
disable_cdrs BOOLEAN NOT NULL DEFAULT 0,
|
||||
trial_end_date DATETIME,
|
||||
deactivated_reason VARCHAR(255),
|
||||
device_to_call_ratio INTEGER NOT NULL DEFAULT 5,
|
||||
PRIMARY KEY (account_sid)
|
||||
) COMMENT='An enterprise that uses the platform for comm services';
|
||||
|
||||
CREATE INDEX account_static_ip_sid_idx ON account_static_ips (account_static_ip_sid);
|
||||
CREATE INDEX account_sid_idx ON account_static_ips (account_sid);
|
||||
ALTER TABLE account_static_ips ADD FOREIGN KEY account_sid_idxfk (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX account_subscription_sid_idx ON account_subscriptions (account_subscription_sid);
|
||||
CREATE INDEX account_sid_idx ON account_subscriptions (account_sid);
|
||||
ALTER TABLE account_subscriptions ADD FOREIGN KEY account_sid_idxfk_1 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX invite_code_idx ON beta_invite_codes (invite_code);
|
||||
CREATE INDEX call_route_sid_idx ON call_routes (call_route_sid);
|
||||
ALTER TABLE call_routes ADD FOREIGN KEY account_sid_idxfk_2 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
ALTER TABLE call_routes ADD FOREIGN KEY application_sid_idxfk (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE INDEX dns_record_sid_idx ON dns_records (dns_record_sid);
|
||||
ALTER TABLE dns_records ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX predefined_carrier_sid_idx ON predefined_carriers (predefined_carrier_sid);
|
||||
CREATE INDEX predefined_sip_gateway_sid_idx ON predefined_sip_gateways (predefined_sip_gateway_sid);
|
||||
CREATE INDEX predefined_carrier_sid_idx ON predefined_sip_gateways (predefined_carrier_sid);
|
||||
ALTER TABLE predefined_sip_gateways ADD FOREIGN KEY predefined_carrier_sid_idxfk (predefined_carrier_sid) REFERENCES predefined_carriers (predefined_carrier_sid);
|
||||
|
||||
CREATE INDEX product_sid_idx ON products (product_sid);
|
||||
CREATE INDEX account_product_sid_idx ON account_products (account_product_sid);
|
||||
CREATE INDEX account_subscription_sid_idx ON account_products (account_subscription_sid);
|
||||
ALTER TABLE account_products ADD FOREIGN KEY account_subscription_sid_idxfk (account_subscription_sid) REFERENCES account_subscriptions (account_subscription_sid);
|
||||
|
||||
ALTER TABLE account_products ADD FOREIGN KEY product_sid_idxfk (product_sid) REFERENCES products (product_sid);
|
||||
|
||||
CREATE INDEX account_offer_sid_idx ON account_offers (account_offer_sid);
|
||||
CREATE INDEX account_sid_idx ON account_offers (account_sid);
|
||||
ALTER TABLE account_offers ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX product_sid_idx ON account_offers (product_sid);
|
||||
ALTER TABLE account_offers ADD FOREIGN KEY product_sid_idxfk_1 (product_sid) REFERENCES products (product_sid);
|
||||
|
||||
CREATE INDEX api_key_sid_idx ON api_keys (api_key_sid);
|
||||
CREATE INDEX account_sid_idx ON api_keys (account_sid);
|
||||
ALTER TABLE api_keys ADD FOREIGN KEY account_sid_idxfk_5 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX service_provider_sid_idx ON api_keys (service_provider_sid);
|
||||
ALTER TABLE api_keys ADD FOREIGN KEY service_provider_sid_idxfk (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
CREATE INDEX sbc_addresses_idx_host_port ON sbc_addresses (ipv4,port);
|
||||
|
||||
CREATE INDEX sbc_address_sid_idx ON sbc_addresses (sbc_address_sid);
|
||||
CREATE INDEX service_provider_sid_idx ON sbc_addresses (service_provider_sid);
|
||||
ALTER TABLE sbc_addresses ADD FOREIGN KEY service_provider_sid_idxfk_1 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
CREATE INDEX email_idx ON signup_history (email);
|
||||
CREATE INDEX ms_teams_tenant_sid_idx ON ms_teams_tenants (ms_teams_tenant_sid);
|
||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY service_provider_sid_idxfk_2 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY account_sid_idxfk_6 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY application_sid_idxfk_1 (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE INDEX tenant_fqdn_idx ON ms_teams_tenants (tenant_fqdn);
|
||||
CREATE UNIQUE INDEX speech_credentials_idx_1 ON speech_credentials (vendor,account_sid);
|
||||
|
||||
CREATE INDEX speech_credential_sid_idx ON speech_credentials (speech_credential_sid);
|
||||
CREATE INDEX service_provider_sid_idx ON speech_credentials (service_provider_sid);
|
||||
ALTER TABLE speech_credentials ADD FOREIGN KEY service_provider_sid_idxfk_3 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
CREATE INDEX account_sid_idx ON speech_credentials (account_sid);
|
||||
ALTER TABLE speech_credentials ADD FOREIGN KEY account_sid_idxfk_7 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX user_sid_idx ON users (user_sid);
|
||||
CREATE INDEX email_idx ON users (email);
|
||||
CREATE INDEX phone_idx ON users (phone);
|
||||
CREATE INDEX account_sid_idx ON users (account_sid);
|
||||
ALTER TABLE users ADD FOREIGN KEY account_sid_idxfk_8 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
CREATE INDEX service_provider_sid_idx ON users (service_provider_sid);
|
||||
ALTER TABLE users ADD FOREIGN KEY service_provider_sid_idxfk_4 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
CREATE INDEX email_activation_code_idx ON users (email_activation_code);
|
||||
CREATE INDEX voip_carrier_sid_idx ON voip_carriers (voip_carrier_sid);
|
||||
CREATE INDEX account_sid_idx ON voip_carriers (account_sid);
|
||||
ALTER TABLE voip_carriers ADD FOREIGN KEY account_sid_idxfk_9 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
ALTER TABLE voip_carriers ADD FOREIGN KEY application_sid_idxfk_2 (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE INDEX phone_number_sid_idx ON phone_numbers (phone_number_sid);
|
||||
CREATE INDEX number_idx ON phone_numbers (number);
|
||||
CREATE INDEX voip_carrier_sid_idx ON phone_numbers (voip_carrier_sid);
|
||||
ALTER TABLE phone_numbers ADD FOREIGN KEY voip_carrier_sid_idxfk (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
|
||||
ALTER TABLE phone_numbers ADD FOREIGN KEY account_sid_idxfk_10 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
ALTER TABLE phone_numbers ADD FOREIGN KEY application_sid_idxfk_3 (application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
CREATE INDEX service_provider_sid_idx ON phone_numbers (service_provider_sid);
|
||||
ALTER TABLE phone_numbers ADD FOREIGN KEY service_provider_sid_idxfk_5 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
CREATE INDEX sip_gateway_idx_hostport ON sip_gateways (ipv4,port);
|
||||
|
||||
CREATE INDEX voip_carrier_sid_idx ON sip_gateways (voip_carrier_sid);
|
||||
ALTER TABLE sip_gateways ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
|
||||
ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY lcr_route_sid_idxfk (lcr_route_sid) REFERENCES lcr_routes (lcr_route_sid);
|
||||
|
||||
ALTER TABLE lcr_carrier_set_entry ADD FOREIGN KEY voip_carrier_sid_idxfk_2 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||
|
||||
CREATE INDEX webhook_sid_idx ON webhooks (webhook_sid);
|
||||
CREATE UNIQUE INDEX applications_idx_name ON applications (account_sid,name);
|
||||
|
||||
CREATE INDEX application_sid_idx ON applications (application_sid);
|
||||
CREATE INDEX service_provider_sid_idx ON applications (service_provider_sid);
|
||||
ALTER TABLE applications ADD FOREIGN KEY service_provider_sid_idxfk_6 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
CREATE INDEX account_sid_idx ON applications (account_sid);
|
||||
ALTER TABLE applications ADD FOREIGN KEY account_sid_idxfk_11 (account_sid) REFERENCES accounts (account_sid);
|
||||
|
||||
ALTER TABLE applications ADD FOREIGN KEY call_hook_sid_idxfk (call_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||
|
||||
ALTER TABLE applications ADD FOREIGN KEY call_status_hook_sid_idxfk (call_status_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||
|
||||
ALTER TABLE applications ADD FOREIGN KEY messaging_hook_sid_idxfk (messaging_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||
|
||||
CREATE INDEX service_provider_sid_idx ON service_providers (service_provider_sid);
|
||||
CREATE INDEX name_idx ON service_providers (name);
|
||||
CREATE INDEX root_domain_idx ON service_providers (root_domain);
|
||||
ALTER TABLE service_providers ADD FOREIGN KEY registration_hook_sid_idxfk (registration_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||
|
||||
CREATE INDEX account_sid_idx ON accounts (account_sid);
|
||||
CREATE INDEX sip_realm_idx ON accounts (sip_realm);
|
||||
CREATE INDEX service_provider_sid_idx ON accounts (service_provider_sid);
|
||||
ALTER TABLE accounts ADD FOREIGN KEY service_provider_sid_idxfk_7 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||
|
||||
ALTER TABLE accounts ADD FOREIGN KEY registration_hook_sid_idxfk_1 (registration_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||
|
||||
ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (device_calling_application_sid) REFERENCES applications (application_sid);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
||||
21
test/db/populate-test-data.sql
Normal file
21
test/db/populate-test-data.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
insert into products (product_sid, name, category)
|
||||
values
|
||||
('c4403cdb-8e75-4b27-9726-7d8315e3216d', 'concurrent call session', 'voice_call_session'),
|
||||
('2c815913-5c26-4004-b748-183b459329df', 'registered device', 'device'),
|
||||
('35a9fb10-233d-4eb9-aada-78de5814d680', 'api call', 'api_rate');
|
||||
|
||||
insert into webhooks(webhook_sid, url, username, password) values('90dda62e-0ea2-47d1-8164-5bd49003476c', 'http://127.0.0.1:4000/auth', 'foo', 'bar');
|
||||
|
||||
insert into service_providers (service_provider_sid, name, root_domain, registration_hook_sid)
|
||||
values ('3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'SP A', 'jambonz.org', '90dda62e-0ea2-47d1-8164-5bd49003476c');
|
||||
|
||||
insert into accounts(account_sid, service_provider_sid, name, sip_realm, registration_hook_sid, webhook_secret, device_to_call_ratio)
|
||||
values ('ed649e33-e771-403a-8c99-1780eabbc803', '3f35518f-5a0d-4c2e-90a5-2407bb3b36f0', 'test account', 'jambonz.org', '90dda62e-0ea2-47d1-8164-5bd49003476c', 'foobar', 0);
|
||||
|
||||
insert into account_subscriptions(account_subscription_sid, account_sid, pending)
|
||||
values ('f4e1848d-3ff8-40eb-b9c1-30e1ef053f94','ed649e33-e771-403a-8c99-1780eabbc803',0);
|
||||
insert into account_products(account_product_sid, account_subscription_sid, product_sid,quantity)
|
||||
values ('f23ff996-6534-4aba-8666-4b347391eca2', 'f4e1848d-3ff8-40eb-b9c1-30e1ef053f94', '2c815913-5c26-4004-b748-183b459329df', 1);
|
||||
insert into account_products(account_product_sid, account_subscription_sid, product_sid,quantity)
|
||||
values ('f23ff997-6534-4aba-8666-4b347391eca2', 'f4e1848d-3ff8-40eb-b9c1-30e1ef053f94', 'c4403cdb-8e75-4b27-9726-7d8315e3216d', 20);
|
||||
|
||||
4
test/db/populate-test-data2.sql
Normal file
4
test/db/populate-test-data2.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
insert into voip_carriers (voip_carrier_sid, name, e164_leading_plus, requires_register, register_username, register_sip_realm, register_password)
|
||||
values ('287c1452-620d-4195-9f19-c9814ef90d78', 'westco', 1, 1, 'foo', 'sip.jambonz.org', 'bar');
|
||||
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
|
||||
values ('124a5339-c62c-4075-9e19-f4de70a96597', '287c1452-620d-4195-9f19-c9814ef90d78', '172.39.0.14', true, true);
|
||||
2
test/db/populate-test-data3.sql
Normal file
2
test/db/populate-test-data3.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
insert into sip_gateways (sip_gateway_sid, voip_carrier_sid, ipv4, inbound, outbound)
|
||||
values ('987a5339-c62c-4075-9e19-f4de70a96597', '287c1452-620d-4195-9f19-c9814ef90d78', '172.39.0.15', false, true);
|
||||
3
test/db/remove_test_db.sql
Normal file
3
test/db/remove_test_db.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
DROP DATABASE jambones_test;
|
||||
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'jambones_test'@'localhost';
|
||||
DROP USER 'jambones_test'@'localhost';
|
||||
64
test/docker-compose-testbed.yaml
Normal file
64
test/docker-compose-testbed.yaml
Normal file
@@ -0,0 +1,64 @@
|
||||
version: '3'
|
||||
|
||||
networks:
|
||||
sbc-registrar:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.39.0.0/16
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
platform: linux/x86_64
|
||||
ports:
|
||||
- "3306:3306"
|
||||
environment:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost", "--protocol", "tcp"]
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
networks:
|
||||
sbc-registrar:
|
||||
ipv4_address: 172.39.0.2
|
||||
sbc:
|
||||
image: drachtio/drachtio-server:latest
|
||||
command: drachtio --contact "sip:*;transport=udp" --loglevel debug --sofia-loglevel 9
|
||||
ports:
|
||||
- "9022:9022/tcp"
|
||||
networks:
|
||||
sbc-registrar:
|
||||
ipv4_address: 172.39.0.10
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
image: redis:5-alpine
|
||||
ports:
|
||||
- "16379:6379/tcp"
|
||||
networks:
|
||||
sbc-registrar:
|
||||
ipv4_address: 172.39.0.13
|
||||
|
||||
sipp-uas-auth-register:
|
||||
image: drachtio/sipp:latest
|
||||
command: sipp -sf /tmp/uas-auth-register.xml
|
||||
container_name: sipp-uas-auth-register
|
||||
volumes:
|
||||
- ./scenarios:/tmp
|
||||
tty: true
|
||||
networks:
|
||||
sbc-registrar:
|
||||
ipv4_address: 172.39.0.14
|
||||
|
||||
sipp-uas-auth-register-fail:
|
||||
image: drachtio/sipp:latest
|
||||
command: sipp -sf /tmp/uas-auth-register2.xml
|
||||
container_name: sipp-uas-auth-register2
|
||||
volumes:
|
||||
- ./scenarios:/tmp
|
||||
tty: true
|
||||
networks:
|
||||
sbc-registrar:
|
||||
ipv4_address: 172.39.0.15
|
||||
1
test/docker.env
Normal file
1
test/docker.env
Normal file
@@ -0,0 +1 @@
|
||||
NODE_CONFIG={"server":{"port":4000,"path":"/auth","auth":{"users":{"foo":"bar"}}},"credentials":{"jambonz.org":[{"username":"john","password":"1234"},{"username":"jane","password":"5678"}]}}
|
||||
11
test/docker_start.js
Normal file
11
test/docker_start.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const test = require('tape');
|
||||
const exec = require('child_process').exec ;
|
||||
|
||||
test('starting docker network..', (t) => {
|
||||
exec(`docker-compose -f ${__dirname}/docker-compose-testbed.yaml up -d`, (err, stdout, stderr) => {
|
||||
t.pass('docker started');
|
||||
t.end(err);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
12
test/docker_stop.js
Normal file
12
test/docker_stop.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const test = require('tape').test ;
|
||||
const exec = require('child_process').exec ;
|
||||
|
||||
test('stopping docker network..', (t) => {
|
||||
t.timeoutAfter(10000);
|
||||
exec(`docker-compose -f ${__dirname}/docker-compose-testbed.yaml down`, (err, stdout, stderr) => {
|
||||
//console.log(`stderr: ${stderr}`);
|
||||
process.exit(0);
|
||||
});
|
||||
t.end() ;
|
||||
});
|
||||
|
||||
4
test/index.js
Normal file
4
test/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
require('./docker_start');
|
||||
require('./create-test-db');
|
||||
require('./regbot-tests');
|
||||
require('./docker_stop');
|
||||
161
test/regbot-tests.js
Normal file
161
test/regbot-tests.js
Normal file
@@ -0,0 +1,161 @@
|
||||
const test = require('tape');
|
||||
const clearModule = require('clear-module');
|
||||
const exec = require('child_process').exec;
|
||||
const opts = Object.assign({
|
||||
timestamp: () => { return `, "time": "${new Date().toISOString()}"`; }
|
||||
}, { level: process.env.JAMBONES_LOGLEVEL || 'info' });
|
||||
const logger = require('pino')(opts);
|
||||
const {
|
||||
addToSet,
|
||||
removeFromSet } = require('@jambonz/realtimedb-helpers')({
|
||||
host: process.env.JAMBONES_REDIS_HOST || 'localhost',
|
||||
port: process.env.JAMBONES_REDIS_PORT || 6379
|
||||
}, logger);
|
||||
const setName = `${(process.env.JAMBONES_CLUSTER_ID || 'default')}:active-sip`;
|
||||
|
||||
process.on('unhandledRejection', (reason, p) => {
|
||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||
});
|
||||
|
||||
function connect(connectable) {
|
||||
return new Promise((resolve, reject) => {
|
||||
connectable.on('connect', () => {
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const wait = (duration) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, duration);
|
||||
});
|
||||
};
|
||||
|
||||
test('populating more test case data', (t) => {
|
||||
exec(`mysql -h 127.0.0.1 -u root --protocol=tcp -D jambones_test < ${__dirname}/db/populate-test-data2.sql`, (err, stdout, stderr) => {
|
||||
if (err) return t.end(err);
|
||||
t.pass('test data set created');
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
|
||||
test('trunk register tests', (t) => {
|
||||
clearModule.all();
|
||||
const { srf } = require('../app');
|
||||
t.timeoutAfter(60000);
|
||||
|
||||
connect(srf)
|
||||
.then(wait.bind(null, 1500))
|
||||
.then(() => {
|
||||
const obj = srf.locals.regbotStatus();
|
||||
return t.ok(obj.total === 1 && obj.registered === 1, 'initial regbot running and successfully registered to trunk');
|
||||
})
|
||||
.then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(`mysql -h 127.0.0.1 -u root --protocol=tcp -D jambones_test < ${__dirname}/db/populate-test-data3.sql`, (err, stdout, stderr) => {
|
||||
if (err) return reject(err);
|
||||
t.pass('added new gateway');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
return wait(35000);
|
||||
})
|
||||
.then(() => {
|
||||
const obj = srf.locals.regbotStatus();
|
||||
return t.ok(obj.total === 2 && obj.registered === 1, 'successfully added gateway that tests failure result');
|
||||
})
|
||||
.then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(`mysql -h 127.0.0.1 -u root --protocol=tcp -D jambones_test -e "delete from sip_gateways where sip_gateway_sid = '987a5339-c62c-4075-9e19-f4de70a96597'"`, (err, stdout, stderr) => {
|
||||
if (err) return reject(err);
|
||||
t.pass('added new gateway');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
if (srf.locals.lb) srf.locals.lb.disconnect();
|
||||
srf.disconnect();
|
||||
t.end();
|
||||
return;
|
||||
})
|
||||
.catch((err) => {
|
||||
if (srf.locals.lb) srf.locals.lb.disconnect();
|
||||
|
||||
if (srf) srf.disconnect();
|
||||
console.log(`error received: ${err}`);
|
||||
t.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
test('trunk register tests when its IP in redis cache', (t) => {
|
||||
clearModule.all();
|
||||
const { srf } = require('../app');
|
||||
t.timeoutAfter(60000);
|
||||
addToSet(setName, "172.39.0.10:5060");
|
||||
|
||||
connect(srf)
|
||||
.then(wait.bind(null, 1500))
|
||||
.then(() => {
|
||||
const obj = srf.locals.regbotStatus();
|
||||
return t.ok(obj.total === 1 && obj.registered === 1, 'initial regbot running and successfully registered to trunk');
|
||||
})
|
||||
.then(() => {
|
||||
if (srf.locals.lb) srf.locals.lb.disconnect();
|
||||
srf.disconnect();
|
||||
t.end();
|
||||
removeFromSet(setName, "172.39.0.10:5060");
|
||||
return;
|
||||
})
|
||||
.catch((err) => {
|
||||
if (srf.locals.lb) srf.locals.lb.disconnect();
|
||||
|
||||
if (srf) srf.disconnect();
|
||||
removeFromSet(setName, "172.39.0.10:5060");
|
||||
console.log(`error received: ${err}`);
|
||||
t.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
test('trunk not register tests when its IP in redis cache', (t) => {
|
||||
clearModule.all();
|
||||
const { srf } = require('../app');
|
||||
t.timeoutAfter(60000);
|
||||
addToSet(setName, "172.39.0.11:5060")
|
||||
|
||||
connect(srf)
|
||||
.then(wait.bind(null, 1500))
|
||||
.then(() => {
|
||||
return t.ok(!srf.locals.regbotStatus, 'No Regbot initiated');
|
||||
})
|
||||
.then(()=> {
|
||||
return removeFromSet(setName, "172.39.0.11:5060");
|
||||
})
|
||||
.then(() => {
|
||||
return addToSet(setName, "172.39.0.10:5060")
|
||||
})
|
||||
.then(() => {
|
||||
return wait(35000);
|
||||
})
|
||||
.then(()=> {
|
||||
const obj = srf.locals.regbotStatus();
|
||||
return t.ok(obj.total === 1 && obj.registered === 1, 'initial regbot running and successfully registered to trunk');
|
||||
})
|
||||
.then(() => {
|
||||
if (srf.locals.lb) srf.locals.lb.disconnect();
|
||||
srf.disconnect();
|
||||
t.end();
|
||||
removeFromSet(setName, "172.39.0.11:5060");
|
||||
return;
|
||||
})
|
||||
.catch((err) => {
|
||||
if (srf.locals.lb) srf.locals.lb.disconnect();
|
||||
|
||||
if (srf) srf.disconnect();
|
||||
removeFromSet(setName, "172.39.0.11:5060");
|
||||
console.log(`error received: ${err}`);
|
||||
t.error(err);
|
||||
});
|
||||
});
|
||||
101
test/scenarios/uas-auth-register.xml
Normal file
101
test/scenarios/uas-auth-register.xml
Normal file
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||
<!DOCTYPE scenario SYSTEM "sipp.dtd">
|
||||
|
||||
<!-- This program is free software; you can redistribute it and/or -->
|
||||
<!-- modify it under the terms of the GNU General Public License as -->
|
||||
<!-- published by the Free Software Foundation; either version 2 of the -->
|
||||
<!-- License, or (at your option) any later version. -->
|
||||
<!-- -->
|
||||
<!-- This program 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 General Public License for more details. -->
|
||||
<!-- -->
|
||||
<!-- You should have received a copy of the GNU General Public License -->
|
||||
<!-- along with this program; if not, write to the -->
|
||||
<!-- Free Software Foundation, Inc., -->
|
||||
<!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -->
|
||||
<!-- -->
|
||||
<!-- Sipp default 'uas' scenario. -->
|
||||
<!-- -->
|
||||
|
||||
<scenario name="Basic UAS responder">
|
||||
<!-- 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 request="REGISTER" crlf="true">
|
||||
</recv>
|
||||
|
||||
<send>
|
||||
<![CDATA[
|
||||
|
||||
SIP/2.0 401 Unauthorized
|
||||
[last_Via:]
|
||||
[last_From:]
|
||||
[last_To:];tag=[pid]SIPpTag01[call_number]
|
||||
[last_Call-ID:]
|
||||
[last_CSeq:]
|
||||
WWW-Authenticate: Digest realm="sip.jambonz.org", nonce="4cdbb733645816512687270b83d2ae5d11e4d9d8"
|
||||
Content-Length: 0
|
||||
|
||||
]]>
|
||||
</send>
|
||||
|
||||
<recv request="REGISTER" crlf="true">
|
||||
<action>
|
||||
<verifyauth assign_to="authvalid" username="foo" password="bar" />
|
||||
</action>
|
||||
</recv>
|
||||
|
||||
<nop hide="true" test="authvalid" next="goodauth" />
|
||||
|
||||
<send next="endit">
|
||||
<![CDATA[
|
||||
|
||||
SIP/2.0 403 Forbidden
|
||||
[last_Via:]
|
||||
[last_From:]
|
||||
[last_To:];tag=[pid]SIPpTag01[call_number]
|
||||
[last_Call-ID:]
|
||||
[last_CSeq:]
|
||||
Contact: <sip:[local_ip]:[local_port];transport=[transport]>;expires=60
|
||||
Content-Type: application/sdp
|
||||
Content-Length: 0
|
||||
|
||||
]]>
|
||||
</send>
|
||||
|
||||
<label id="goodauth"/>
|
||||
<send>
|
||||
<![CDATA[
|
||||
|
||||
SIP/2.0 200 OK
|
||||
[last_Via:]
|
||||
[last_From:]
|
||||
[last_To:];tag=[pid]SIPpTag01[call_number]
|
||||
[last_Call-ID:]
|
||||
[last_CSeq:]
|
||||
Contact: <sip:[local_ip]:[local_port];transport=[transport]>;expires=60
|
||||
Content-Type: application/sdp
|
||||
Content-Length: 0
|
||||
|
||||
]]>
|
||||
</send>
|
||||
|
||||
<label id="badauth"/>
|
||||
|
||||
<label id="endit"/>
|
||||
|
||||
<!-- Keep the call open for a while in case the 200 is lost to be -->
|
||||
<!-- able to retransmit it if we receive the BYE again. -->
|
||||
<timewait milliseconds="4000"/>
|
||||
|
||||
|
||||
<!-- definition of the response time repartition table (unit is ms) -->
|
||||
<ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
|
||||
|
||||
<!-- definition of the call length repartition table (unit is ms) -->
|
||||
<CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
|
||||
|
||||
</scenario>
|
||||
|
||||
Reference in New Issue
Block a user