Asterisk Manager Interface

Ben Langfeld edited this page Apr 11, 2012 · 14 revisions

DEPRECATION NOTICE: This is old documentation relevant to Adhearsion 1.x and will soon be removed. See the main documentation for up-to-date info.

There are principally two ways Adhearsion can control Asterisk: the Asterisk Gateway Interface protocol or the Asterisk Manager Interface protocol (henceforth referred to as AGI and AMI respectively). The AGI protocol is modeled after the CGI protocol and is bound to a particular call. When a call comes in, Asterisk itself establishes an AGI connection out to Adhearsion via a TCP socket.

Conversely, the AMI protocol is for other processes to connect into Asterisk following a 3rd Party Call Control model similar, but not compliant with, TSAPI. An AMI connection is therefore not bound to a particular call and, instead, allows the other process to manage global operations and query global state on the Asterisk server, as well as manage and receive events on individual calls. The AMI protocol itself is inconsistently designed, but painstaking effort has been made to make it consistent to Adhearsion users. For more information on the protocol, see this page.

With the AMI protocol, users can...

  • Arbitrarily initiate calls
  • Set channel variables on any channel
  • Get the state for all SIP peers registered with the server
  • Receive events about things which occur within Asterisk in real-time
  • Start recording a particular channel to a sound file
  • View all parked calls
  • View state of all the queues on the system
  • Execute CLI commands
  • and so forth...

Here are a few ideas of how you might use AMI:

  • Subscribe to the Hangup event and then log it in a database for billing
  • Connect into an Adhearsion process via DRb and spawn a call
  • Dynamically change the configuration files of Asterisk

AMI is not configured by default with Asterisk, though the steps to setting it up are all simple.

##Configure Asterisk##

Open the file /etc/asterisk/manager.conf in a text editor. You will probably see a lot of commented-out lines (a ";" denotes a comment) and a "general" section similar to the following:

[general]
enabled = yes
port = 5038
bindaddr = 0.0.0.0

The enabled key must be set to yes (it defaults to no). If you are running Adhearsion and Asterisk on the same machine, you can tighten security a bit by setting bindaddr = 127.0.0.1. If you bind to "127.0.0.1", you will not be able to access your Asterisk server's AMI port from an outside machine.

Additionally you will need to define an AMI user for your Adhearsion app. Below is an example.

[ahn_ami]
secret = iheartlolcats
read   = system,call,log,verbose,agent,user,config,dtmf,reporting,cdr,dialplan
write  = system,call,agent,user,config,command,reporting,originate

The permission settings above provide complete access to Asterisk and is the recommended setting. You can limit the access to AMI if you wish, provided you know exactly which actions you want to allow.

NOTE You must create a user with both read and write permissions in Asterisk manager.conf file. Adhearsion opens two connections to that user. One sends actions and one listen to requests and events. A read only user will cause Adhearsion to throw an exception that it can not connect to the Asterisk Manager Interface without such user.

##Configure Adhearsion##

Enabling Adhearsion to use AMI is quite easy. In your Adhearsion application, open up config/startup.rb with a text editor and uncomment the line that looks similar to the following:

config.asterisk.enable_ami :host     => '127.0.0.1', :events   => true,
                           :username => 'ahn_ami',   :password => 'iheartlolcats'

Make sure that the line remains below the config.enable_asterisk line.

##Using AMI events##

The Asterisk Manager Interface supports broadcasting Asterisk events as they occur in real-time. Adhearsion exposes these events for easy processing using its own internal events subsystem. For more information on how to make use of AMI events, see the Events page.

NOTE If you use UserEvents be sure to read the Events page for important information.

##Controlling Asterisk from Rails with Adhearsion##

Adhearsion ships with DRb support, which allows Adhearsion to receive instructions from another Ruby application. DRb allows an object in one Ruby process to invoke methods on an object in another Ruby process on the same or a different machine. Add the line below to config/startup.rb:

config.enable_drb

If you wish to specify an alternate port for DRb you may use syntax like this: config.enable_drb :port => 8888

Next, in your separate Ruby process, add the following code. If you're using Rails, add this to the bottom of your Rails app's config/environment.rb file.

AdhearsionDRb = DRbObject.new_with_uri 'druby://localhost'
AdhearsionDRb.introduce('SIP/first-person', 'ZAP/second-person')

The methods exposed to this remote DRbObject are defined in components using the methods_for(:rpc) do..end }} syntax. New Adhearsion apps come with the ami_remote component already installed and enabled, which exposes certain AMI methods via Adhearsion's RPC system; if you take a look at ami_remote, you can see the other available AMI-related abstractions. Additionally, you can write your own component and expose new, custom abstractions of AMI very easily. See the Components page for more info.

##AMI from a standalone Ruby script##

Adhearsion's AMI library was designed to be very reusable. Here's an example Ruby script (the examples/asterisk_manager_interface folder contains a few additional examples as well):

require 'rubygems'
require 'adhearsion'
require 'adhearsion/voip/asterisk/manager_interface'

include Adhearsion::VoIP::Asterisk::Manager

asterisk = ManagerInterface.connect :host => "10.0.1.97", :username => "jicksta", :password => "roflcopter"

response = asterisk.send_action "Originate", "Channel" => "SIP/mytrunk",
    "Application" => "Playback", "Args" => "hello-world"

p response.headers

For playback application:

response = asterisk.send_action "Originate", "Channel" => "SIP/mytrunk",
   "Application" => "Playback", "Data" => "hello-world"

##Handling Future Resources##

A Future Resource allows you to have an object updated on a future event. Adhearsion provides this capability to deal with the asynchronous nature of AMI requests and responses. An example would be to launch a request for the status of a call channel over AMI. The AMI action 'Status' only returns that the action was accepted by the AMI, but the result of that status request comes back as a response event. If you call the method synchronously:

result = send_action_synchronously 'status', { :channel => 'SIP/teliax-011fc210' }

It is the same as the following:

my_future_resource = send_action_asynchronously 'status', { :channel => 'SIP/teliax-011fc210' }
result = my_future_resource.response

In the second case the thread will block until my_future_resource.response returns. Keep in mind that send_action is an alias for send_action_synchronously and to send asynchronously to use send_action_asynchronously.

##Handling AMI Errors##

When making an action request to the AMI using send_action or send_action_asynchronously and the AMI raises an error as the response, Adhearsion will trap this error and raise it as an exception. To handle this properly, you want to use a begin/rescue clause in order to have access to the exception. This example logs the resulting response to the Status action:

begin
  Adhearsion::VoIP::Asterisk.manager_interface.send_action_synchronously 'status', { :channel => 'SIP/teliax-011fc210' }
rescue Adhearsion::VoIP::Asterisk::Manager::ManagerInterfaceError => error
  ahn_log.statusrequest.debug error
end

##Asterisk disconnects##

When you restart the Asterisk process or the Asterisk process crashes for whatever reason, Adhearsion may be in the middle of something important (e.g. something billable). This introduces a race condition in the way that Adhearsion talks to Asterisk. Below is a high-level explanation of how commands are sent and received over AMI with Adhearsion:

  • Adhearsion starts
  • If the Asterisk module is enabled with AMI (enable_ami in startup.rb), the AsteriskInitializer will attempt a connection to Asterisk.
    • If events are enabled, a separate socket will be established with events turned on.
    • The actions-only socket always has events turned off (but it still receives "causal events")
  • When a connection is made, a Thread is spawned to constantly read data from the socket.
  • When an action is sent, an ActionID is generated for it and the ManagerInterfaceAction is stored in a Thread-safe dictionary (Hash).
  • The reading Thread is constantly pulling responses (and "causal events") from the actions socket and performs a lookup with the Thread-safe dictionary for a ManagerInterfaceAction object associated with the received ActionID.
  • The reader thread then gives the ManagerInterfaceAction the new ManagerInterfaceResponse object
  • All other Threads that were waiting on the response are then given access to the response by calling ManagerInterfaceAction#response.

The race condition exists during these scenarios

  • If Asterisk goes down while data is being sent
  • If Asterisk goes down after data has been sent but before a response has been received
  • If Asterisk goes down after a response has been received but not all "causal events" have been received

The way to protect yourself from this is simple: don't kill a production system forcefully! You should route traffic around any Asterisk/Adhearsion instance that needs to be removed from the cluster until Adhearsion and Asterisk are completely idle. When doing a new deployment (say, an upgrade of Asterisk), you should stop Adhearsion and Asterisk, then start Asterisk first and Adhearsion second.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.