CME AXL XML API - Provisioning and RIS

By Aaron Harrison : Development Engineer at IPCommute UK

Recently I wanted to do a little feasibility check into what would be involved in developing our Phone Customiser so that it could be used to manage images on a Cisco Communications Manager Express (CME) system. I've always liked CME for some perverse reason, though my experience is typically with the Enterprise CUCM system rather than the little brother. How much demand there would be for this application on CME is debatable:

  • I would estimate that there are a lot more router deployed running CME than there are CUCM clusters in the world. We have customers running literally hundreds.
  • Since a lot more of these would be in SMEs, the administrators of these devices may prefer to get a simple app for these rather than spend time playing with XML, images and scripts.
  • On the downside, once you have the XML and TFTP set up, it's much quicker to run round a 50-phone CME system and deploy the image than it is to push images out to thousands of handsets in geographically diverse locations as you might find with CUCM.

The main reason I wanted to do this however, is probably curiosity. It's often not until you start experimenting with the APIs that you find out how well they are implemented, and once you 'get' them they can get you out of lots of little corners.

So - the first thing I did was google 'CME SDK', and pretty quickly turned up links to this : http://developer.cisco.com/web/ucxapi/home

I browsed the documentation, set it up in my IDE of choice (Netbeans 7) and spent some time trying to get the example working. I had a few issues with those examples, my lab router isn't man enough to run IOS 15.0, and I fairly quickly realised that this API wasn't what I was looking for! It covers CTI as well as provisioning, and I didn't want that extra complexity.

Another quick search around turned up the final chapter in the CME Administration Guide (http://www.cisco.com/en/US/docs/voice_ip_comm/cucme/admin/configuration/guide/cmeapi.html). This looked much more promising!

I dived straight in and put together some basic Java code to make a test SOAP call to my lab router. Firstly I applied the configuration to set up authentication and the XML interface to my router like so:

! turn on http server. 
ip http server 
! enable the xml APIs via http
ixi transport http
 response size 8
 request outstanding 2 
 request timeout 30 
 no shutdown
! enable the CME API (presumably there's other APIs available that work similarly!)
ixi application cme
 response timeout 30 
 no shutdown
! finally, set a username and password for the XML API
telephony-service 
 xml user xml password password 15

The I put together a Java console app with a simple main method like so:

public static void main(String[] args) {
	// Here we create a new instance of a SOAP Connection:
	SOAPConnection axlSoapConn; 
        SOAPConnectionFactory scf;
        try {
            scf = SOAPConnectionFactory.newInstance();
            axlSoapConn = scf.createConnection();
        } catch (SOAPException ex) {
            throw new IllegalStateException("SOAP Connection Factory failed", ex);
        }
	// This is the URL of my lab router; the path is the standard path to the API on the router
	String axlUrl = "http://192.168.0.10/ios_xml_app/cme";
       
	// This method call generates my SOAP query
	SOAPMessage axlQuery = createSoapTest();      
	
	// And here we send out new SOAP query off to the CME router!
	SOAPMessage axlReply = null;
	try {
            axlReply = axlSoapConn.call(axlQuery, axlUrl);            
        } catch (SOAPException ex) {
            System.out.println(ex.getMessage());
        }
}

I wanted to do a simple 'ISgetGlobal' query as this was a simple, no parameter type request that I imagined would return some useful information and provide a good way to validate a connection to a router at the start of a program.

The request would look like this:

<request xsi:type="ISgetGlobal"> 
<isgetglobal></isgetglobal>
</request>

To this end, I put together a little function like so:

private static SOAPMessage createSoapTest() {

	SOAPMessage axlQuery;
        try {
            // Create a new SOAP Message:
            final MessageFactory soapMF = MessageFactory.newInstance();
            axlQuery = soapMF.createMessage();

	    // Create the root element
	    SOAPElement xsi = axlQuery.getSOAPBody().addChildElement("request");
	    // Add the ISgetGlobal bit		 
            SOAPElement gd = xsi.addChildElement("ISgetGlobal");
            
	    // Add the username/password - using the org.apache.commons.codec.binary.Base64 utility class for Base64 encoding	
            final MimeHeaders headers = axlQuery.getMimeHeaders();
            headers.addHeader("Authorization", " Basic " + (Base64.encodeBase64String("xml:password".getBytes()).trim()));

        } catch (SOAPException ex) {
            throw new IllegalStateException("Doh!", ex);

        }
        return axlQuery;
}

So I hit go, and when the request is sent, I get an empty response! Great. I ran a quick Wireshark trace with a simple http filter, and sure enough there's no data at the HTTP/XML level in the response, though my request is being sent nicely. Playing around with the router config (e.g. changing the xml user password) tended to make things worse (i.e. I got a 401 error as the password was now wrong) so it all looks good, and there's something wrong with my call.

Another look at the Cisco admin guide showed that one of the examples had another XML element wrapping the 'request' element, and also that there were namespace references in there. I guess I would certainly have to do this new wrapper element, but hoped I could ignore the namespaces.

So I revised my method above...

private static SOAPMessage createSoapTest() {
	SOAPMessage axlQuery;
        try {
            // Create a new SOAP Message:
            final MessageFactory soapMF = MessageFactory.newInstance();
            axlQuery = soapMF.createMessage();

	    // Create the root element 'axl'
	    SOAPElement axl = axlQuery.getSOAPBody().addChildElement("axl");
	    // Now the 'request' element...
            SOAPElement xsi = axl.addChildElement("request");
	    // Add the ISgetGlobal bit		 
            SOAPElement gd = xsi.addChildElement("ISgetGlobal");
            
	    // Add the username/password - using the org.apache.commons.codec.binary.Base64 utility class for Base64 encoding	
            final MimeHeaders headers = axlQuery.getMimeHeaders();
            headers.addHeader("Authorization", " Basic " + (Base64.encodeBase64String("xml:password".getBytes()).trim()));

        } catch (SOAPException ex) {
            throw new IllegalStateException("Doh!", ex);

        }
        return axlQuery;       
}

And this time... I got nothing. Again. At this point I thought I'd try something new - I read about this a while ago and it sounded like a great idea. Basically it's an add-on for FireFox named 'Poster' which simply allows you to fire stuff at a web server. It allows you to easily do a 'POST' of some XML, which is what I needed to do to test this little API without having to keep messing with my code.

The add-on is available here : https://addons.mozilla.org/en-US/firefox/addon/poster/

Once installed, Tools/Poster will fire it up. I simply entered the username/password I set up for my XML user, and then pasted in the three-line chunk of XML then hit POST. Immediately I got back some XML, as shown in this screen grab. Result!

I wanted to know if I needed to specify the namespaces and so on, so I removed those and simplified it to:

<request> 
<isgetglobal></isgetglobal>
</request>

And this worked OK. I tried adding the additional wrapping element, and this also worked. Cue much head scratching... what was wrong with my code? Being able to use Poster was a big help, I could try various things and then compare the actual XML content of the requests sent by Poster and those from my app. Eventually the only difference I could see as the presence of SOAP Header info in my message... empty, but.... I tried this in my createSoapTest() method:

// Create a new SOAP Message:
final MessageFactory soapMF = MessageFactory.newInstance();
axlQuery = soapMF.createMessage();
// Get the SOAP header and bin it:
axlQuery.getSOAPHeader().detachNode();

Finally! Got a result... Now myself and the API were starting to talk, things would get easier.

I next wanted to see if I could get back the info about phone registration status. Firing a ISgetDevice at the router like so:

<request> 
<isgetdevice>
<isdevname>SEP000011112222</isdevname>
</isgetdevice>
</request>

Got me back info for one phone. Not a lot of info.. for example 'ISDevType' simply shows 'others', and the IP address was 0.0.0.0. Hmm. I got a phone registered (actually a CIPC), and this info got updated to the correct IP and the ISDevType became 'IP Phone CIPC'. This was getting more useful, if a little odd. For example, this phone I'd just registered as a CIPC was actually configured as a 7912. That wouldn't work on a full CUCM system at all...

Next I wanted to get all devices - I didn't want to have to start with a list of device names from elsewhere to get the info. This request should do it:

<request> 
<isgetdevice>
<isdevname>all</isdevname>
</isgetdevice>
</request>

Now.. when I did this, I started to get problems. I lost connection to the router, couldn't even ping it for a minute or so. I played some more and kept losing connection. After blaming my home wireless and tampering with that a little, I realised the only thing that wasn't responding was the CME router, and only when I tried to run this XML. More head scratching, then a look at the router's logging buffer turned up this error:

Jan 16 21:41:55.287: Not enough buffer for response, please config a larger one.
ixi transport http
 response size 64

Looks like for some reason we need to extend the response buffer for these big requests. I wonder how stable this API would be on a big router with hundreds of phones?

Now I was really getting somewhere. I was able to retrieve the phone IPs, and with the following command under telephony-service I could use my existing classes to retrieve info from the phone web server (e.g. serial numbers etc):

telephony-service
service phone webAccess 0

This was quite pleasing as it meant that I had enough functionality to make our Phone Inventory tool work with CME to some degree.

Next I wanted to be able to push commands to the phone. I tried an XML background push (this process is documented very well here : http://www.netcraftsmen.net/component/content/article/70-unified-communications/791-pushing-backgrounds-to-a-cisco-ip-phone-using-xml.html) and got an authentication failure. To get arounds this I properly configured my CME routers' auth URL:

telephony-service
url authentication http://192.168.0.10/CCMCIP/authenticate.asp
authentication credential xml password

Setting this the same username/password I'm using to retrieve the device info makes my life easier! I got past the auth error, and now get told that 'Personalization is disabled'. Of course it is! So:

telephony-service
service phone phonePersonalization 1

A reset of the phone... and.. personalization still disabled!!! I did a little compare of an XML config file from here with one from a CUCM server, and of course, my hopes and plans were all dashed. The 'service phone' command allows you to enter any 'Product Specific Configuration' element as used on CUCM, such as webAccess. However, phonePersonalization is present outside the product specific config in the XML structure.. and I can't find a way to enter this element into the config files on CME without complicating administration of the system (such as going for an external TFTP server). Looks like I have to wait for Cisco to introduce the command to CME, or perhaps go down the route of applying the image files to the CME TFTP server and remotely controlling the phone to control the download.

At least I've learnt a little about this CME API, and have a way forward even if it's not quite as easy as I'd hoped...

By Aaron Harrison : Development Engineer at IPCommute UK

Continue