Managing Distributed Asterisk Configuration and NoSQL Database
In my quest of getting out of the configuration spaghetti hell, I’ve previously suggest to use a new configuration layout and some helper scripts to load configuration on-demand through #exec instruction. While this alternative is interesting, it lacks in terms of environment distribution where a cluster of Asterisk platforms needs to share configuration or at least a central configuration repository. While a tiny few DB backed configuration technique exist (Asterisk RealTime for instance), I feel its time to have something new, fresh, and obviously more versatile :-)
CouchDB to the rescue
First of, what is couchdb, directly from the website:
- A document database server, accessible via a RESTful JSON API.
- Ad-hoc and schema-free with a flat address space.
- Distributed, featuring robust, incremental replication with bi-directional conflict detection and management.
- Query-able and index-able, featuring a table oriented reporting engine that uses JavaScript as a query language.
Benefits
From our perspective, the benefits of this NoSQL DB can be highlighted as follow:
- Query support from command line through cURL.
- Automatic replication
- Revision history
- Flexible schema
Components
Documents
{
"section": "general",
"content": {
"port": "5038",
"enable": "yes"
},
"module": "manager",
"hostname": "casablanca"
}
Views
"views": {
"config": {
"map": "function(doc) {\n if (doc.section && doc.module) {\n emit(doc.module, doc);\n }\n}"
}
}
List functions
Now comes the nice part. It’s one thing to get a JSON document out of a view query, but we need output in the asterisk configuration form. List function allows just that, typically used to return XML or HTML version of a given result. It can also be used to return text/plain content type. Great! Here is what we have:
"lists": {
"section": "function(head, req) {
var row;\n
start({
'headers': {
'Content-Type': 'text/html'
}
});
while(row = getRow()) {
var content = '[' + row.value.section + ']\\n';
var items = row.value.content;
for (var key in items) {
content += key+'='+items[key] + '\\n';
}
send(content);
}
}"
}
This list function basically iterates over a view, and creates the section content based on the key-value pairs from the content object hash.
PUTting it all together
Then we can PUT our config and _design documents to the DB.
for I in `doc/*.json` ; do
uuid=`curl 'http://example.com:5984/_uuids'`
curl -X PUT -v -d @$I 'http://example.com:5984/asterisk/${uuid}'
done
curl -X PUT -v --data-binary @config-design.json 'http://example.com:5984/asterisk/_design/config'
Now we can query the list function for a specific hostname and module name through the following:
curl 'http://example.com:5984/asterisk/_design/config/_list/section/config?key=\["casablanca","manager"\]' [general] port=5038 enable=yes
Nice! Now, within the associated manager.conf file, simply perform the #exec command as follow:
#exec load-from-couchdb.sh manager
Where load-from-couchdb.sh script could be as follow
#!/bin/sh
hostname=`hostname`
module=$1
curl 'http://example.com:5984/asterisk/_design/config/_list/section/config?key=\["{$hostname}","$module"\]'
More to Come
Using CouchDB and this little list function trick open doors for a few more novelties:
* Config can be easily changed dynamically through any programming language supporting web requests. (Reload still need to be issued through Manager API though).
* A web dashboard could be explored to offer a nice and clean configuration management tool.
* Configuration could be distributed within infrastructure.
* Backup is only a matter of either replicating the config to some other node or copy the database file.
* Authentication is provided out of the box within couchdb itself.