Veterans of Asterisk configuration have likely dealt with static configuration files or realtime before. Since Asterisk 13.5.0, there is a new player in town: ARI push configuration.
How does ARI push configuration work?
ARI push configuration uses a subsystem of Asterisk called sorcery. This is the first mention of sorcery on this blog, and I suspect it will not be the last. We’ll save the in-depth discussion of sorcery for a future post. For now, understand that it is a CRUD (create, read, update, delete) API in Asterisk that can read and write to different backends. Sorcery was created for Asterisk 12. Any new modules that require configuration or persistent storage are encouraged to use sorcery. At this time, the only part of Asterisk that uses sorcery for configuration is PJSIP.
ARI has been outfitted with a mechanism to push configuration to sorcery-configured areas of Asterisk. The HTTP PUT, GET, and DELETE commands map to sorcery’s create/update, read, and delete operations. Sending configuration data over HTTP causes sorcery to store the given configuration in the backend. When something internal to Asterisk attempts to retrieve the configuration, sorcery will then retrieve the stored configuration.
Show me, please
The best way to illustrate this is with an example. I have a running Asterisk instance with nothing configured in pjsip.conf
I start with the following sorcery.conf file:
[res_pjsip] auth = memory
This tells sorcery that objects of type “auth” managed by the “res_pjsip” module should be stored using the “memory” sorcery backend. The memory backend, as the name implies, stores configuration in memory.
I also have the following ari.conf file:
[general] enabled = yes [asterisk] type = user password = asterisk
This enables ARI and creates an “asterisk” user with password “asterisk”.
Getting configuration
I have the following script that will use ARI to retrieve the “alice” auth object from res_pjsip:
#!/usr/bin/env python import requests import json url = "http://localhost:8088/ari/asterisk/config/dynamic/res_pjsip/auth/alice" resp = requests.get(url, auth=('asterisk', 'asterisk')) if resp.status_code == 200: print "Received object" print json.dumps(resp.json(), sort_keys=True, indent=2, separators=(',', ': ')) else: print "Received {0} response".format(resp.status_code)
If you are familiar with Python’s requests library, this should be pretty straightforward. The construction of the URL tells what object type and object name is to be retrieved. The script issues an HTTP GET request to the URL. If the request receives a 200 response, then the script prints the object that was retrieved.
When I run this script right now, it gets a 404 response since the “alice” auth object has not been created. You can see that the “pjsip show auth alice” also shows that the object does not exist.
Pushing Configuration
I have the following python script that will use ARI to push the “alice” auth object to Asterisk.
#!/usr/bin/env python import requests import json url = "http://localhost:8088/ari/asterisk/config/dynamic/res_pjsip/auth/alice" config = { 'fields': [ { 'attribute': 'username', 'value': 'alice' }, { 'attribute': 'password', 'value': 'supersecret' }, ] } resp = requests.put(url, auth=('asterisk', 'asterisk'), json=config) if resp.status_code == 200: print "Successfully pushed" print json.dumps(resp.json(), sort_keys=True, indent=2, separators=(',', ': ')) else: print "Received {0} response".format(resp.status_code)
The configuration we are pushing is exactly the same as the following pjsip.conf:
[alice] type = auth username = alice secret = supersecret
When I run the push.py script, you can see that Asterisk tells us what it set for the auth. The “pjsip show auth alice” command now also shows the pushed auth object.
Now that the object has been pushed, the get.py script will also now retrieve the object as expected
Deleting Configuration
I have the following script to delete the object from configuration:
#!/usr/bin/env python import requests url = 'http://localhost:8088/ari/asterisk/config/dynamic/res_pjsip/auth/alice' resp = requests.delete(url, auth=('asterisk', 'asterisk')) if resp.status_code == 204: print "Successfully deleted" else: print "Received {0} response".format(resp.status_code)
When I run this, the object no longer appears when the “pjsip show auth alice” command is run.
Anything else you can show me?
Look at what happens with the configuration when we restart Asterisk.
Uh oh! The configuration is gone! This is because the sorcery memory backend gets cleared during an Asterisk shutdown or restart. We can use sorcery to our advantage, though. We’ll alter sorcery.conf a bit:
[res_pjsip] auth = astdb,dynamic_auth
Sorcery can be told to use the astdb instead of memory. The astdb is a persistent data store and retains its data across restarts and shutdowns. In this case, we’re telling sorcery to store res_pjsip auth objects in the astdb using the prefix “dynamic_auth”. With this change to sorcery, we can push our configuration again and try our previous restart test.
Yay! The configuration is still there! The final CLI command is there just to prove that you can see the object in the astdb.
“But Mark,” you might say, “wouldn’t the astdb be less efficient than memory? Is it possible to somehow have my cake and eat it too?”
Of course there is! Sorcery can be told to use multiple configuration backends, and the order you specify them in sorcery.conf is the order that sorcery uses when trying to find objects. Have a look at this updated sorcery.conf file:
[res_pjsip] auth/cache = memory auth = astdb,dynamic_auth
Now we’ve told sorcery to use the memory backend first when trying to find an object. If that fails, then try to find it in the astdb instead. Notice the “/cache” notation. That makes sorcery automatically store a retrieved object in that backend if the object was retrieved from a different backend. In other words, when Asterisk starts up, the “alice” auth object will be in the astdb and not in memory. When something asks for “alice” it will be retrieved from the astdb. Sorcery will then automatically store the object in memory, too. From then on, whenever “alice” is asked for, it will be retrieved directly from memory.
What are the pros and cons of push configuration:
- Sorcery provides flexibility when deciding how objects are stored/retrieved.
- Configuration can be updated with no need to reload.
- Since it uses the REST API, push configuration can be easily integrated into larger Asterisk applications.
- HTTP uses well known error codes. This makes it easy to troubleshoot
- The request and response bodies use JSON, which is widely supported by software libraries. JSON also has the benefit of being human-readable.
- Push configuration only works with sorcery-configured objects.
- Push configuration may not fit well in deployments that require an application to push configuration to multiple Asterisk servers. Pushing configuration to each Asterisk server is less efficient than writing to a database once.
7 Responses
If push configuration only works with sorcery configured objects, and only PJSIP uses sorcery, it seems of little use.
I use ARI to play music-on-hold to calls and would really like to be able to dynamically configure new moh classes (upload some audio files to a directory, then use ARI to create a new class to use that directory, and have some calls use that new class). How close is asterisk to allowing that (or can it do it already)?
Hi,
Can I ARI push to configure dynamic dial plans.
I know of noone working on such a thing. What most people do is they just reload the configuration I believe.
This is not currently supported, no.
body:
{
“fields”: [
{
“attribute”: “username”,
“value”: “alice”
},
{
“attribute”: “password”,
“value”: “supersecret”
}
]
}
403 Forbidden
Content-type: application/json
{
“message”: “Cannot create sorcery objects of type ‘auth'”
}
What is deal here ?
Have you configured sorcery.conf with a backend that allows storage of such things?
This is inside:
[res_pjsip]
endpoint=astdb,ps_endpoints
auth=astdb,ps_auths
aor=astdb,ps_aors