Voice Apps in the Real World

Crosspost Note

This originally appeared on MojoLingo's Blog.


In my previous blog post I went through getting started with the Telephony Dev Box. However, most real-world apps are more than simply a telephony engine and Adhearsion. This post picks up where that one left off, and moves from ‘getting started’ into something a bit more real world. Here, we’ll build a complete development environment for a real world voice application and show how your team can benefit from the predictability of automated dev environments.

A github repo has been created to host in full the results of everything covered in this post.

Voice apps in the real world

On the Adhearsion website, the example on the front page reads:

def run  
  answer  
  resp = ask "How many woodchucks?", :limit => 1  
  say "You said #{resp}. That's obviously wrong!"  
end

It’s a cute example, but let’s face it – if your application is that simple, you could probably just use Asterisk and hardcode the dialplan logic in extensions.conf – ugly, but serviceable.

Integration

Real applications need to be integrated – with websites, *aaS’s, databases, instant messaging systems, etc… Many people coming to Adhearsion have an existing Rails app to which they want to add telephone features, while staying with their primary language, Ruby.

So let’s get started building a development environment for this more realistic scenario.

Roadmap

Our goal through this post is to set up an Adhearsion application and Ruby on Rails (RoR) application. Some background assumptions:

  • The RoR application will be backed by a MySQL database
  • The Adhearsion application will query the RoR app over an HTTP API
  • The Adhearsion application will pass info via Redis back to the RoR app
  • Real-time event information will be communicated back and forth via XMPP

You may ask why we would choose to use avenues of communication such as these instead of simply integrating ActiveRecord directly into Adhearsion? Based on our experience, we no longer recommend that approach. ActiveRecord tends to not perform well with long-lived threads, which is the type of usage a telephone app creates. You’ll avoid many headaches if you keep your concerns separated. Additionally, the use of an API layer allows you to keep all validation logic in a single place, which helps with data integrity in the long run.

Using the virtual machine templates provided by Telephony Dev Box (TDB), let’s create a new development environment tailored for this app. We’ll start by stripping out unneeded things.

That’s a lot of boxes!

The TDB provides VMs for all supported telephony engines, Adhearsion itself, and various utility boxes. But our application will not need most of those. We’re going to choose a single telephony engine, and base our development environment around there.

For our example we’ll use FreeSWITCH. Now we can go through and delete everything out of the Vagrantfile related to Asterisk and PRISM. We’ll also get rid of the load testing tools and TTS/ASR VMs as they are not a part of this application.

Adhearsion – In or Out?

Although you can run Adhearsion inside a VM, most of us at Mojo Lingo prefer to run all the Ruby apps locally on our own machine, and offload the telephone engine/services into the VM. So we’ll remove the Adhearsion VM also.

Now we’re left a [Vagrantfile] looking like this:

Vagrant.configure("2") do |config|  
  config.vm.box = 'precise64'  
  config.vm.box_url = 'http://files.vagrantup.com/precise64.box'  
  config.librarian_chef.cheffile_dir = "."

  config.vm.define :freeswitch do |freeswitch|  
    # ...  
  end  
end
Cleanup the Cheffile

You can also remove the cookbooks for all the apps/services we just removed, leaving you a cookbook looking like this:

#!/usr/bin/env ruby

site 'http://community.opscode.com/api/v1'

cookbook 'mojolingo-misc', path: 'mojolingo-misc'

cookbook 'apt'  
cookbook 'freeswitch', git: 'https://github.com/mojolingo/freeswitch-cookbook.git'  
cookbook 'chef-solo-search', git: 'https://github.com/edelight/chef-solo-search.git'  
cookbook 'yum'  
cookbook 'sudo'  

Clean up the Roles/Data Bags

You can also remove all the files in roles/ except for the FreeSWITCH one, as well as the data_bags/ directory.

Adding services

Now our VM has gotten lonely – nothing remains but FreeSWITCH itself. Let’s add some recipes to flush out the environment we’ll need.

How to add services

There are several options for provisioning your Vagrant VM. Chef is a favorite around MojoLingo, and what the TDB uses for its images. Another popular and mature alternative is Puppet.

You can mix and match provisioners – just because FreeSWITCH is provisioned with Chef doesn’t mean you HAVE to use Chef for the other packages. We’ll stick with Chef for this post, but if you’re going to be working on a project where there’s already a staging/production environment set up, it’s a great idea to use the same packages to build your dev environment that are already used for production.

Adding our services

We’ll need to extend our VM with

  • MySQL
  • Redis
  • ejabberd

to give us the services we chose to have our Adhearsion app connect to the RoR app with.

MySQL

We’ll use the standard mysql cookbook. Add this line to your Cheffile:

cookbook 'mysql'  

and this line to your Vagrantfile: ruby chef.add_recipie "mysql"

and set up the attributes in your Vagrantfile for a basic server:

mysql: {  
  server_debian_password: "foobar",  
  server_repl_password: "foobar",  
  server_root_password: 'foobar'  
}  

That’s really all there is to it. The database schema will come from the app, so we only need to make sure the database engine is accessible from our app.

Redis:

Redis is a relatively easy service to add; all we need is to add the cookbook and reference the recipe; it has sane defaults, so no further config is needed – we’ll just point Rails/Adhearsion app to the VM’s IP address using the default redis port (6379), and everything will Just Work.

Cheffile: ruby cookbook 'redisio'

Vagrantfile: ruby chef.add_recipe "redisio::install" chef.add_recipe "redisio::enable"

ejabberd

ejabberd takes a bit more configuration. First, add Mojo Lingo’s recipe to your Cheffile/Vagrantfile:

cookbook 'ejabberd', git: 'git@github.com:mojolingo/ejabberd.git'  
chef.add_recipe "ejabberd"  

Next, set up some basic attributes: ruby ejabberd: { domain: public_ip, registration_allowed: true }, jabber_domain: public_ip

Finally, we’ll need to create a data bag to setup an initial user. In the data_bags folder from the TDB, you can clear out the Asterisk stuff, but create a new folder called ejabberd_users/, and add a user user_100.json in that folder:

{  
  "id": "user100",  
  "data_bag": "ejabberd_users",  
  "node": "100",  
  "domain": "10.203.175.73",  
  "password": "passwd"  
}

Pick an IP near the ones used by the TDB, but different in case you need to use a stock TDB for another project, say 10.203.175.73. Let’s also pick a friendly hostname for it, say freeswitch.local-dev.my-ahn-app.com, and add it to your hosts file.

Our final Vagrantfile ends up looking like this:

Vagrant.configure("2") do |config|  
  config.vm.box = 'precise64'  
  config.vm.box_url = 'http://files.vagrantup.com/precise64.box'  
  config.librarian_chef.cheffile_dir = "."

  config.vm.define :freeswitch do |freeswitch|  
    public_ip = "10.203.175.73"

    freeswitch.vm.network :private_network, ip: public_ip  
    freeswitch.vm.hostname = "freeswitch.local-dev.my-ahn-app.com"

    freeswitch.vm.provider :virtualbox do |vb|  
      vb.name = "TDB-freeswitch"  
    end

    freeswitch.vm.provision :chef_solo do |chef|  
      chef.cookbooks_path = "cookbooks"  
      chef.data_bags_path = "data_bags"  
      chef.add_recipe "apt"  
      chef.add_recipe "mysql::server"  
      chef.add_recipe "freeswitch::default"  
      chef.add_recipe "chef-solo-search"  
      chef.add_recipe "ejabberd"  
      chef.add_recipe "redisio::install"  
      chef.add_recipe "redisio::enable"

      chef.log_level = :debug

      chef.json = {  
        freeswitch: {  
          tls_only: false,  
          local_ip: public_ip,  
          dialplan: {  
            head_fragments: '<extension name="adhearsion">  
              <condition>  
                <action application="rayo"/>  
              </condition>  
            </extension>'  
          }  
        },  
        mysql: {  
          server_debian_password: "foobar",  
          server_repl_password: "foobar",  
          server_root_password: 'foobar'  
        },  
        ejabberd: {  
          domain: public_ip,  
          registration_allowed: true  
        },  
        jabber_domain: public_ip  
      }  
    end  
  end  
end 

Run vagrant up, and the machine is building!

In the next part, we’ll point Rails and Adhearsion inside this VM.

Some tips:
  • Alias vagrant destroy && vagrant up to something easy – you’ll be using that command a lot when building a new VM! 😉
  • Learn when you need to destroy and rebuild, and when vagrant provision is sufficient to try some changes.
  • Focus on getting one service installed/configured at a time – comment out the other recipes so you don’t have to wait for FreeSWITCH to compile to see if your MySQL recipe works.
  • If you’re using Virtualbox as your Vagrant host, don’t start Parallels or your Mac will crash. :/
    • Virtualbox + VMWare Fusion seem to coexist happily.
Further reading / other resources
  • SOA for the Little Guys – With Adhearsion / RoR running seperate, you’re well on your way to your own little SOA…
  • Connecting Adhearsion – Video from 2012 Adhearsion Conference all about the different ways to wire Adhearsion up with Rails (or anything else)
  • Mailing List and IRC – Whatever service you’re trying to provision, there’s most likely someone in the community that already has, and can help your out.
You might also enjoy reading:
JustinAiken

I'm a Ruby/Rails developer currently working for UserTesting, living in Southern Utah.
More about the author →