continuous-qa
Brian Warner: Documenting my journey in QA, Automation and Tools Development.
16 December 2013
Finding Slow Queries in MySQL
I have this web application I built. It's a test tool that performs a variety of checks of phone numbers and phone carriers. I noticed after a recent update that it began to load really slow (the web interface.) Instead of the usual 4 second load time, it was taking 30 seconds.
I had indexed a variety of tables in the past, so I wasn't sure which query was causing the problem.
What helped me find out the source of the problem was MySQL's slow log. In my default installation of MySQL, the slow logging is not on. To turn it on, I had to first edit the mysql config (which on Centos is somewhere like /etc/my.cnf)
In the config file I added these lines under [mysqld]:
log-slow-queries = /var/log/mysql/slow_query.log
long_query_time = 10
log-queries-not-using-indexes
Next, I created the folder /var/log/mysql and set the ownership to mysql: sudo chown mysql.mysql /var/log/mysql
Inside the folder, I added a empty file: touch slow_query.log
After doing all that, I restarted MySQL with /etc/init.d/mysql restart (which is the default CentOS way to restart mysql.)
In the general mysql log (/var/log/mysqld.log) there should be no errors referencing the new slow log.
The lines added to the my.cnf file basically say:
when a query is slow, please log it in /var/log/mysql/slow_query.log
by slow, I mean anything taking longer then 10 seconds
also, log any queries not using an index.
After restarting mysql and hitting the webpage, I saw the new slow log updated and found the culprit query causing my problems.
02 October 2013
Groovy - say NO to FOR loops
Although for loops are part of Groovy, the each iterator is much more useful. I just found this out from a developer at my job.
I have been doing a pretty big project. Part of the project involves having a grails application run a job every min. each time it runs this job, it will iterate through a set of IP's. Each IP is a proxy that routes a telephone call. If the call completes it pass's, if it doesn't, we through a failure. Each iteration needs to be stored in the database.
The problem was this....
I had a For loop that would create a new instance, then do the validation... But each run would replace the results in the db.
By switching to an each closure, it resolved this. Here's some example code of what worked:
In this code, I no longer had the problem of iterating through a list and getting one db record at the end. Now, the list is correctly being iterated through, each run gets it's own db row entry.
Thanks Yakoob for your help!
I have been doing a pretty big project. Part of the project involves having a grails application run a job every min. each time it runs this job, it will iterate through a set of IP's. Each IP is a proxy that routes a telephone call. If the call completes it pass's, if it doesn't, we through a failure. Each iteration needs to be stored in the database.
The problem was this....
I had a For loop that would create a new instance, then do the validation... But each run would replace the results in the db.
By switching to an each closure, it resolved this. Here's some example code of what worked:
ipprox = ["127.0.0.1","128.0.0.1","129.0.0.1"]
ipprox.each { prox - def res = proxyCheckService.sipp(phone,proxy).result def proxyCheckInstance = new ProxyCheck proxyCheckInstance.results = res proxyCheckInstance.dnis = dnis proxyCheckInstance.proxy = prox
if (res =~ /Successful call\s+9[0])\s+1/){ proxyCheckInstance.pass = "PASS" } else { proxyCheckInstance.pass="FAIL" }
proxyCheckInstance.save(flush:true)
In this code, I no longer had the problem of iterating through a list and getting one db record at the end. Now, the list is correctly being iterated through, each run gets it's own db row entry.
Thanks Yakoob for your help!
30 September 2013
Grails - Setting Up Spring Security
This is pretty quick and Simple....
If you have a new grails project, you may not need to do Step 2
Step 1: edit BuildConfig.groovy and in the plugins closure, add compile ':spring-security-core:1.2.7.3'
Step 2: if you have an existing project you may need to add this: grails.project.dependency.resolver = "maven" above the grails.project.dependency.resolution closure
Step 3: compile
Step 4: run this grails command on the project: s2-quickstart [your package] User Role
Step 5: Add a user to the bootstrap by editing your BootStrap.groovy file and adding after servletContext ->
def eventOwnerRole = new Role(authority: 'ROLE_EVENT_OWNER').save(flush:true)
def userRole = new Role(authority: 'ROLE_USER').save(flush:true)
def testUser=new User(username: 'me', enabled: true, password: 'password')
testUser.save(flush:true)
UserRole.create testUser, userRole, true
Step 6: in one of your controllers, add:
import [the package your user/role classes were put].Role
import [the package your user/role classes were put].User
import [the package your user/role classes were put].UserRole
Step 7: Edit an existing controller and add
import grails.plugins.springsecurity.Secured
@Secured(['ROLE_USER'])
Run the app... go to the controller... it should now prompt for user/pass...
If you have a new grails project, you may not need to do Step 2
Step 1: edit BuildConfig.groovy and in the plugins closure, add compile ':spring-security-core:1.2.7.3'
Step 2: if you have an existing project you may need to add this: grails.project.dependency.resolver = "maven" above the grails.project.dependency.resolution closure
Step 3: compile
Step 4: run this grails command on the project: s2-quickstart [your package] User Role
Step 5: Add a user to the bootstrap by editing your BootStrap.groovy file and adding after servletContext ->
def eventOwnerRole = new Role(authority: 'ROLE_EVENT_OWNER').save(flush:true)
def userRole = new Role(authority: 'ROLE_USER').save(flush:true)
def testUser=new User(username: 'me', enabled: true, password: 'password')
testUser.save(flush:true)
UserRole.create testUser, userRole, true
Step 6: in one of your controllers, add:
import [the package your user/role classes were put].Role
import [the package your user/role classes were put].User
import [the package your user/role classes were put].UserRole
Step 7: Edit an existing controller and add
import grails.plugins.springsecurity.Secured
@Secured(['ROLE_USER'])
Run the app... go to the controller... it should now prompt for user/pass...
29 August 2013
Project: Making a SIP GUI test harness with Grails and SIPP - Part III
Part I, discussed this project and walked through setting up SIPP scenarios and Grails (making a basic Grails project.)
Part II, discussed adding in a Service Layer to do the SIPP call and added some call validation.
Now What?
So now what? Well you can add on to your test harness. You can add other domain classes, controllers and services as needed.
For example, what if you wanted to have a different interface, where a user just enters a phone number and a proxy, and the service not only uses sipp but iterates through a list of carriers?
You could do that by adding in a new Domain object, and generate controllers/views... then make a service to handle the logic. Like in the service you might define a list:
def carriers = ['Sprint','TCast','Verizon'] and iterate through it with a for loop, like:
for (c in carriers) { def carrier = c def dialNum = "sipp -s ${dnis} ${ipprox} -sf ${carrier}_outbound_call.xml -l 1 -m 1 -d 5000".execute().text ....
Or what if you wanted a Service that would handle some audio analysis, so you could turn the audio of the phone call to a string and compare it against an expected value?
You can keep adding onto the project with more and more cases.
In the end, the code is centralized, and we have a UI for free. This UI can be used by us or other testers.
Tomcat
So lastly, there is the aspect of deployment. For QA, we usually run our own servers and environments. In my case, I have a variety of Linux Centos VM's available to me. I basically put Tomcat up on one of those guys.I'm assuming you got Tomcat up and running, as I wont cover setting it up in this post.
To deploy a Grails application, you basically do what you would do in Java - you create a WAR file and drop the WAR file in the Tomcat webapp folder.
So there's two ways I can think of doing this.... the hard way and the easy way.
The Hard Way
The hard way, basically means you do it the long way. In this method, you simply open the grails command line in Intellij and type:grails war
and hit enter or click ok.
It will generate a war file and drop it in your project under /target.
Then you would SCP/FTP that up to your Tomcat server
That's not so hard. But there's an easier way....
The Easy Way
First you need to set this up. After that it's easy.The easy way is easy, because once it's set up, you don't do any FTP/SCP or WAR Generation. You just run a command and it does all that.
To get here, you will first need to do a few things -
On the Tomcat server, edit the tomcat-users.xml file. In that file you want to add a role group (if you don't have it) called "manager-script."
Add a user with the role manager-script... jot down the username and pass you gave it... you'll be adding that in the Grails configuration.
In Grails 2.2.4 Tomcat deployment plugin is already installed. What you need to do is configure it so that when you deploy via your IDE, it will do all the WAR generation and SCP to your Tomcat server, for you.
**NOTE: I'm having issues with Grails 2.3Pre Release at this time, with Tomcat deployments**
In Grails, you open your /conf/Config.groovy file and add this:
/** * TOMCAT DEPLOY CONFIG */ tomcat.deploy.username="grails" tomcat.deploy.password="pass" tomcat.deploy.url="http://[your Tomcat server]/manager/text" /** * END TOMCAT CONFIG */
That's it.
Now to deploy to Tomcat, you do this command: grails tomcat deploy
from the command line within the IDE.
Project: Making a SIP GUI test harness with Grails and SIPP - Part II
In Part I, we set up SIPP, SIPP Scenarios and created a Grails application with a controller and a domain class.
What we'll start with in Part II is the creation of a service.
But first... what is a service?
A service is a piece of functionality that coordinates logic with the Domain layer.
We'll use a service to run the all the logic that's needed for the application. By separating the code out into a service, we move the logic into a layer that isn't the same as the controller. If we didn't do this, the controller would be handling all the functionality.
Grails looks to split the core functionality and logic into a separate layer (i.e. Service layer) and leave the controller as handing the routing. In this model of development the controller is left to return values, render values or redirect to a specific view or other controller.
So again, the 3 R's of controllers are:
To recap:
We are:
This is pretty simple and it won't work. Not yet. We haven't added the REGEX.
The IF statement is saying, IF the dialer service return =~ (that funny symbol means we're going to use Regular expressions here) // (and what's inside the slashes is what we look for. If the regular expression matches, then we will assign the string value "PASS" to the Domain Class variable "pass" and if it fails the regular expression, we will assign the string "FAIL" to that Domain Class variable.
If you know SIPP output you know it's difficult to regex on. This is because SIPP outputs it's results like this:
Successful Call |0|1
Failed Call |0|0
That's pretty crazy right? Because if you make that a map, it would look like this:
Successful Call, 0, 1 Failed Call.
But it's not a failed call, it's a successful call! Silly SIPP output!
Successful call\s+([0])\s+1
It basically says, Look for the words Successful call, on the same line, find the 0, and the fond the 1.
That was my solution, yours might be better.
so now the controller should look like:
If you were to run this now, it will most likely fail. Either pass when it should fail, or fail when it should pass. The reason, is the Pipes in the SIPP output. I've tried a lot of solutions, including escaping the pipes in the Regex, but it still doesn't work right.
To fix it, we need to open the service and add this to the return:
So at the end of dialNum, add a
This removes the pipes in the output. So what's returned is returned without Pipes.
That should be it. It should work just like that. On to Part III (Deployment)
What we'll start with in Part II is the creation of a service.
But first... what is a service?
A service is a piece of functionality that coordinates logic with the Domain layer.
We'll use a service to run the all the logic that's needed for the application. By separating the code out into a service, we move the logic into a layer that isn't the same as the controller. If we didn't do this, the controller would be handling all the functionality.
Grails looks to split the core functionality and logic into a separate layer (i.e. Service layer) and leave the controller as handing the routing. In this model of development the controller is left to return values, render values or redirect to a specific view or other controller.
So again, the 3 R's of controllers are:
- Render
- Redirect
- Return
Render is not a common use of a controller. Returning values and Redirection is the most common activity of a controller.
So now that that's out there, what do we need to do with this service?
We need to put the logic in there, that handles the user input. Remember our tool offers a UI that a end user can hit, pull up a form and submit a phone number, carrier and proxy. We want those values to plug into SIPP, and let SIPP make the call and we'll validate if it's successful or not.
Service
To create a new service, in Intellij you can right click the project in the project tree, and then choose
New | Grails Service
Give it a name... like DialerService
Great, now in the project tree, open the services folder. You should see your new service listed there. Go ahead and double click it to open it up.
Let's go ahead and add some logic in here.
def sipp(phone,ipprox,carrier){ def dialNum = "sipp -s ${phone} ${ipprox} -sf /carrierTests/outbound/${carrier}_outbound_call.xml -l 1 -m 1 -d 5000".execute().text [dialNumResult:dialNum] } }
Let's review what's going on here.
The service has a method called sipp, which takes three parameters (phone number, the proxy ip and the carrier) - these three parameters are the ones being passed in by the end user on the form.
The sipp method has a piece of functionality defined as dialNum. dialNum basically runs our SIPP command line call. What's in quotes is the string we will execute. The ${} is a gString... or groovy string. Groovy strings are there to pull in those parameter values being passed through. The .text() may not seem logical, but I actually want it there. It will take the output from SIPP (with the call data) and convert that to text for us. That way if someone says "ok the call went through, but how do I know what it did?" they can have a record of the actual call process.
Lastly, what's that square bracket stuff?
The square bracket is a return. We are saying set up this variable dialNumResult to have the value of dialNum... and pass it back.
In Grails, the last line of a method is the last action returned. In this case, we're returning the value of dialNum.
Controller
Back in the controller, we want to pass the variables we are getting from the user input (via the form) through to the service we made.
So under def outboundCallInstance = new OutboundCall(params) add these lines:
def carrier = params.carrier
def phone = params.phone
def ipprox = params.ipProxy
Great, so do you see how it's working? We're taking the user input on the form, which is going to the controller... and the controller is routing it to the service. The service will do it's logic/functionality and return the result.
But this isn't quite done yet. We are grabbing the data from the form, sure enough, but we aren't passing it to the service.
Above the save method in the controller add this:
def dialerService
In Intellij, it will automatically know that this is a reference to the service we made and put a special icon next to it in the margin.
Why did we reference the service in camel case, when it's really named DialerService? Well that's just how you do it. We're referencing the service in a class and this is how we do it.
Below the lines we added (after the parmas.ipProxy line) add this line:
outboundCallInstance.results = dialerService.sipp(phone, ipprox, carrier).dialNumResult
If you set it up right it should auto complete a lot of that for you. We're saying, hey we defined this service, now lets talk to it... we want the outbound call instance's results to be the dialer service and pass into it the 3 variables:
- phone
- ipproxy
- carrier
...and we expect the dialNumResult returned (this is the output from sipp)
So it should look something like this:
def dialerService def save() { def outboundCallInstance = new OutboundCall(params) def carrier = params.carrier def phone= params.phone def ipprox = params.ipProxy outboundCallInstance.results = dialerService.sipp(phone, ipprox, carrier).dialNumRes ....There will be other code in there, just leave it as it was generated.
To recap:
We are:
- Collecting user input on a form
- passing that to a controller that sends it on to a Service
- The service does some logic on it and returns a result
- All this is saved in the db
Validation
If you were to run this, it should make a single call via SIPP to your phone number via a proxy of some sort. You can test this out by running the application again and filling out the form with valid data for your test environment.
But it's not really validating the phone call.
First I'll show how to do some simple validation of the SIPP call with some logic in the controller... later on I'll show a different way to validate with some logic in the service.
Back in the controller, let's go to that save method again, and under the outboundCallInstance.results = line, let's add this if statement:
if (dialerService.sipp(dnis,ipprox,carrier).dialNumRes =~ //){ outboundCallInstance.pass = "PASS" } else { outboundCallInstance.pass = "FAIL" }
This is pretty simple and it won't work. Not yet. We haven't added the REGEX.
The IF statement is saying, IF the dialer service return =~ (that funny symbol means we're going to use Regular expressions here) // (and what's inside the slashes is what we look for. If the regular expression matches, then we will assign the string value "PASS" to the Domain Class variable "pass" and if it fails the regular expression, we will assign the string "FAIL" to that Domain Class variable.
If you know SIPP output you know it's difficult to regex on. This is because SIPP outputs it's results like this:
Successful Call |0|1
Failed Call |0|0
That's pretty crazy right? Because if you make that a map, it would look like this:
Successful Call, 0, 1 Failed Call.
But it's not a failed call, it's a successful call! Silly SIPP output!
Regex Hell
After many hours of Regular Expression Hell, I created this regex that works for SIPP results:Successful call\s+([0])\s+1
It basically says, Look for the words Successful call, on the same line, find the 0, and the fond the 1.
That was my solution, yours might be better.
so now the controller should look like:
if (dialerService.sipp(dnis,ipprox,carrier).dialNumRes =~ /Successful call\s+([0])\s+1/){ outboundCallInstance.pass = "PASS" } else { outboundCallInstance.pass = "FAIL" }
If you were to run this now, it will most likely fail. Either pass when it should fail, or fail when it should pass. The reason, is the Pipes in the SIPP output. I've tried a lot of solutions, including escaping the pipes in the Regex, but it still doesn't work right.
To fix it, we need to open the service and add this to the return:
[dialNumRes:dialNum.replaceAll("\\|", "")]
So at the end of dialNum, add a
.replaceAll("\\|", "")
This removes the pipes in the output. So what's returned is returned without Pipes.
That should be it. It should work just like that. On to Part III (Deployment)
Project: Making a SIP GUI test harness with Grails and SIPP - Part I
Making a SIP GUI Test Harness - Part I
Introduction
Part I will cover:- The Introduction
- Setting up SIPP scenarios
- Creating a Grails project
- Creating Grails Domain Classes and Controllers
In VOIP, the term SIP refers to Session Initiation Protocol. It's a protocol that allows voice communication via online connections. Many phone calls are themselves SIP at some point.
If you currently test SIP, you probably use tools like SIPP. SIPP is the most common tool I know of. It's open source, very diverse in functionality, and has a large following of contributors. SIPP is typically used to generate load tests - like driving 50 calls per second, to a switch. But SIPP can also be used for functional tests as well.
Audience for this Tutorial
- SIPP is installed and running
- Familiar with some scripting/programming (I'll be very detailed on basics of groovy/grails)
- A basic understanding of automation
- A basic understanding of VOIP
- You have an environment with a soft switch (FreeSwitch, Asterix... or a sip server like Berkeke, OpenSIPS, etc.) that we can route calls through.
End Results
By the end of this tutorial, you should have a working UI that will take a phone number and proxy (sip server) - dial the number via the proxy on submit and validate if the call passed or failed. You will also have the foundation on which to build other test criteria, such as voice analysis and load testing.Basically by the end of this demonstration you should be able to do this with Grails:
- Be able to create a Grails Project
- Know how to create controllers
- Know how to create services and why they're cool
- Set up Domain Classes
...and you'll be able to do this with VOIP / SIP:
- Create SIPP XML scenarios for Outbound carrier tests
- Run SIPP as a Functional Test Tool
SIPP Basics
While SIPP automates these phone calls, it's use is manual. That is, you create a XML file that builds the scenario. You then type a command line, like:sipp [phone number] [switch] -sf [path to xml scenario file] -l [limit the amount of calls to this value] -r [limit the rate of calls] -trace_err
SIP Automation
That's great for one off tests, what if you want something run often by a chron job or by other people who may not know SIPP's depth of command line properties?What if you wanted to integrate functionality with SIPP to do things like Audio Analysis, or to iterate through a collection of tests?
That's why I created a test harness.
My first SIP Test Harness was written with shell scripts. It worked, but it put my work all over the place. I had one shell script to do a phone call, another to do complex audio analysis, another to do packet captures, etc.
I've improved on this, and in the process got a UI thrown in for free.
What is Grails and Why Use It
Grails is a MVC architecture for Groovy. MVC stands for Model View and Controller. It's a design system that offers a quick way to generate code. It makes use of domain models, hibernate, plugins and much more - to give a rich tool set.You'll get the UI for free. Grails will generate UI's for you and we can modify the static scaffolding to suite our needs.
Groovy and Grails (much like Ruby and Rails) is very simplistic and will allow you to write complex applications with very little code. You can parse JSON with a line of code, or generate CRUD (create, read, update, delete) actions instantly with a command line. It's fast and rapid development that truly suits tools development.
Why Grails? Why not Rails?
No particular reason. The benefit of Grails is that it compiles down to Java Byte Code so it runs where there is Java. Also Java developers can easily read it ... and there is the benefit of having more people who can update the code as needed.
Technology Used
- SIPP (make sure you have that installed - you'll need linux or MacOSX
- Tomcat (I'll use Tomcat, but you can feel free to use Jetty or something else)
- Grails (For grails you'll need Java installed, as well as groovy, and you'll want to have an IDE. A free IDE is the Groovy Grails Tools Suite from SpringSource: http://www.springsource.org/groovy-grails-tool-suite-download A better choice IMO is the commercial version of Intellij. But either will work. This tutorial will use Intellij.
- SoftSwitch that we can route calls through (Asterix, FreeSwitch, etc.)
Test Harness Requirements
First, what do we want the test harness to do?
For the purpose of this demonstration, I'm going to build out a test harness that will
- place calls
- validate if the call is a pass or failure.
- Then we'll build from there to iterate through a list of different call scenarios (like calling different carriers.)
We need to make sure the technology used is working... so:
- Make a test call with SIPP (dial a DNIS/phone number with an installed soft switch)
- Verify you have Tomcat or Jetty up and running
- Verify you have Grails avail., in a command prompt do a grails -version.
- Verify your IDE is integrated with Grails (I.e. you can make a grails project)
If you can check off the four items in the above list, then we're good to go.
So let's start....
Create a Grails Project
To create a Grails project, just go to a directory on your local file system and do a
grails create-app [name]
Example:
grails create-app siptesting
In your IDE load the project.
For example, in Intellij you would do a:
File | Import Project
Point it to the project you just created and accept the prompts it gives.
Once the project is loaded in the IDE, we'll create the domain class first. The domain class will set the data elements we care about, any validation rules on them and these values will be stored in the db.
Speaking of Db, we could use any db we want, but for this demo, we'll just the in memory db (h2) that Grails defaults to.
To create a domain class in Intellij, you right click the project name, then click the sub menu item: New | Grails Domain Class
Give it a name like: OutboundCall
In the project tree, you should see a structure like:
grails-app
> conf
> controllers
> domain
In domain there should be a class now, called OutboundCall, open that up.
It will probably look like this:
class OutboundCall { static constraints = { } }
Under the class reference, we're going to define some values. These values will tell Grails what to store. The info in a domain class is also used to create scaffolding elements like controllers and views.
By that I mean, if you define something in the Domain, Grails will generate all the actions to capture this data for you. Which saves from writing a lot of structure from scratch.
So lets define some things we want to capture...
Under class OutboundCall {
Date dateCreated String carrier String results String ipProxy String phone String pass
dateCreated is an internal function. It will add a timestamp to each save of data
carrier will be captured from user input
results will be a computed value
ipProxy will be captured from user input
phone will be captured from user input
pass will be a computed value
Below this, there is that closure called "static constraints" - we want to add some constraints to the above variables. For example, we are ok with date being nullable, and only be able to pick carriers from a defined list (dropdown...)
So in the static constraints closure, add the following:
dateCreated nullable: true carrier inList: ['Sprint','Verizon','Tcast'] results maxSize: 50000, nullable: true, display: false ipProxy blank: false phone blank: false pass nullable: true, display: false
What we're saying here is this:
dateCreated is nullable, but why? Because it's not a value on the user creation form. It's going to be calculated by Grails. I had to make this nullable because otherwise Grails expects a value.
carrier uses a inList. Everything in the square brackets is going to be a option in a dropdown in a form.
results - we are saying results will have a size limitation and it's also nullable, and it won't display. The reason is that this field is going to be the SIPP output. It can be large output and it's not user input. We don't want it to display, except when it's avail, so not until the results are shown.
ipProxy and phone - are user input fields and they can't be blank.
pass - will be a value based on some logic to determine if the call went through fine or not.
Basically the Domain Class should look like this:
class OutboundCall{ Date dateCreated String carrier String results String ipProxy String phone String pass static constraints = { dateCreated nullable: true carrier inList: ['Sprint','TCast','Verizon'] results maxSize: 5000000, nullable: true, display: false ipProxy blank: false phone blank: false pass nullable: true, display: false } static mapping = { results type: 'text' } }
Generating the Static Scaffolding
At this point, we've created the domain. Lets do our first generation of code. We want Grails to generate the Controllers (the part of the code that will route logic) and Views (the display portion of the code.)In Intellij, you can do a control+alt+G (windows) or Mac equivalent to pull up a command line for grails. In the box you will send:
grails generate-all [packag name].[class name]
For example:
grails generate-all siptests.OutboundCall
This will generate a controller for you called OutboundCallController (under the controllers folder in the project tree) and a set of views in a folder called "outboundCall."
Run the App
Let's see how our application looks.... go ahead and start it.
In Intellij we can click the green "play" like triangle at the top of the screen.
It should load without issue. Once it loads, it will give you a url, like http://localhost:8080/... click that link.
Iniitally you will land on a page that Grails generates to show you all your plugins used in the project and has links to your controllers. Click on the controller link.
This UI is all pre-built for you. You can "create" and it will load a UI with the fields we set in the Domain class. Go ahead and put some data in there and save it.
You'll be back at the List, showing your data you just saved.
It's not doing anything yet, other then capturing user input. But we can see that the applicaiton is up and running and taking user input.
Let's add some logic!
Controller
Go ahead and open up the controller we generated.You'll see a lot of code. There will be all your crud actions in there. You'll see things like
def create()
def save()
def show()
This is the basic functionality of creating, saving, showing your data....
Our goal here is to take a user's input, and pass it to SIPP and validate the results we get. So we want to
SIPP
Back in SIPP, lets create some test scenarios.... Grails is going to let users send the phone number, the proxy and the carrier for the test. So lets get SIPP scenarios set up to handle this.
on the command line do a:
sipp -sd uac > outbound_call.xml
The above command will export a base template from SIPP to the XML file specified.
Once we have that file, lets edit it. We need to specify the carriers. In our example, we are going to allow users to send calls using Sprint, Verizon and TCast. To make this simple, we'll create three XML scenario files - one for each carrier. In each one, we'll hard code the carrier into the SIP Invite header.
If setting the carrier is applicable to you, you can define the carrier in the XML scenario. If it isn't applicable to you, then just omit the carrier reference in Grails (in the domain class) and just capture the phone number and proxy.
Some people can hard code a carrier value like:
Carrier: SPRINT
which is mapped to rules in their Sip Server (Berkeke or OpenSIPS) to send the call to the appropriate carrier.
If that's of interest, then lets do it.... let's copy that template as three files:
sprint_outbound_call.xml, verizon_outbound_call.xml, tcast_outbound_call.xml
Edit each one and modify the carrier value you read in from your sip server to be hard coded to this value.
Now when you run the sipp call to use this scenario it will be set to use this carrier - testing the carrier itself in handling the call.
Run a sipp scenario here that we made and make sure the call will go through to a phone number you have defined on the sip server.
If it works... then continue to Part II (Part II will cover setting up a Service and updating the Controller as well as Validating the results.)
19 August 2013
Grails - Creating an Auto Dialer - Refactored with Services
After getting some formal Grails training, I decided to refactor this tool I made for our internal provisioning team.
The Original tool was discussed previously here.
My previous version of the tool had all the work in the controller. I had one controller doing this:
The two service calls (dialerService.dialNumber(phone) and emailService.sendingEmail(emailTo), now clean up the controller quite a bit.
DialerService and EmailService
The Original tool was discussed previously here.
Don't Put Too Much Logic in the Controller
I learned that a good MVC practice is to not put too much logic/work in the controller - but to think of controllers as routers. The body of logic should be pulled out into services.My previous version of the tool had all the work in the controller. I had one controller doing this:
- Parsing a user submitted CSV file
- Dialing each number in the CSV
- Emailing the results of each number being active or disconnected, to the requested email
The Refactored Controller
The controller now looks like this:@Grab('com.xlson.groovycsv:groovycsv:1.0') import static com.xlson.groovycsv.CsvParser.parseCsv class CsvImportController { def emailService def dialerService def save() { def emailTo = params.emailAdd if (emailTo =~ /@myinternaldomain/) { def csv = request.getFile('myfile').inputStream.text def data = parseCsv(csv) runAsync{ for(line in data) { def phone = line.Phone dialerService.dialNumber(phone) } emailService.sendingEmail(emailTo) } }else{ render(view:"error") { div(id:"error", "E-mail Format Error: E-mail must be from @myinternaldomain.com") } } } }
The two service calls (dialerService.dialNumber(phone) and emailService.sendingEmail(emailTo), now clean up the controller quite a bit.
Services
There are two new services now:DialerService and EmailService
The DailerService looks like this:
class DialerService { def dialNumber(String phone) { println "Trying... $phone" def dialNum = "sipcli/sipcli.exe $phone -d [proxy ip goes here] -o 4 -t \"This is a test. this is a test. this is a test. this is a test\"-l 3".execute() def outFile = new File("grails-app/test.txt") if (dialNum.text =~ /success/){ outFile << ("PASS on number $phone\r\n") } else { System.getProperty("line.separator") outFile.append("FAIL on number $phone\r\n") } } }
The EmailService looks like this:
class EmailService { def sendingEmail(String emailTo) { println "Emailing Report To: " + emailTo sendMail { multipart true to "${emailTo}" from "brian@someemail.com" subject "Provisioning Report" body 'Please find the attached Provisioning Report...' attachBytes 'grails-app/test.txt','text/csv', new File('grails-app/test.txt').readBytes() } println "Attempting to delete results file..." def delFile = new File("grails-app/test.txt").delete() } }
Subscribe to:
Posts (Atom)