NetIQ has a series of drivers that are application specific, like Active Directory, GroupWise, etc. Others are more API level, like JDBC and SOAP. In the case of SOAP the shim is just a transport and you have to do everything else. By everything else I mean the whole kit and kaboodle. The kitchen sink. The bees knees. No wait, scratch that last one. The bees stinger. There you go.
In part 1 of this series I started discussing some of the vagaries of making calls (remember the proper SOAPAction header, Basic Auth header for authentication, and the difference between get* and list* functions).
I was discussing implementing Query functionality, since the driver needs these to do almost every operation, so best to get this done first.
Of course, in CUCM the SOAP implementation looks like they just exposed every table in the database to a set of function calls, without much of an attempt to make it easier to use. In fact, there is even an explicit function called executeSQLQuery and executeSQLUpdate. I guess if you are familiar with the underlying database structure (Which I never looked at, but maybe is documented better than the SOAP functions, so could have been an easier way to handle this) this would be a much easier way to handle it.
In fact, looking at it now as I write this, I wonder if the approach I took in the Salesforce.com SOAP driver, where I mapped generic Query functionality to their custom SQL based language (SOQL) would have been easier. Darn, I hate when that happens. Second thoughts... Oh well. My way currently works well, maybe if I have to refactor this driver again, I will think about a SQL based approach.
You can read about that approach in this series I wrote a few years ago.
The reason I think a SQL approach might have been simpler is that there are something like 159 getXXXXX functions in the WSDL that I counted (very roughly, by hand, so don't hold me to that number) for which there also seem to be 159 listXXXXX classes, so mapping the Query functionality is harder than it needs to be. That is, you really need to implement an XDS transform from <query> to <getXXXXXX> for each object class. Worse, the SOAP XML is different for most every class, beyond just the name of the function. The different naming components differ per class.
The getXXXX functions return a single, properly identified object. The listXXXX functions are used to query to find objects using wildcards. There are also the addXXXX functions so you can create an object of the specific class, as well as removeXXXX to map to deletes. There are updateXXXXX for modifies.
This part all should map nicely to XDS, except for the annoyance of the each class having its own set of unique SOAP functions.
Thus it might have been simpler to learn the database layout, maybe store some definitions for each class of interest in a DirXML-Resource object so when it comes time to query or modify an object, get the fields from that resource object, use it to build a proper SQL string (like what fields are required, and maybe where to get the data from the XDS document). At some levels, did I not just describe the Filter and Schema Map functionality in a JDBC driver? But in this case, you would need to build the SQL command in policy, instead of letting the shim do the conversion from XDS to SQL. Thus I guess that is sort of reproducing the JDBC driver shim functionality. The problem is, there are many mandatory fields here so I imagine, I would need more configuration information than is stored in the filter and schema map.
Regardless of the approach you take, you need someway to handle making queries, and you need to implement it for each object class you need to query.
My focus had been on Mobility Profiles, which are a special case of a Device Profile. A Device Profile is assigned to a physical phone and specifies how the buttons and look and feel are configured. A User can be assigned a specific Device Profile, which I guess implies a specific physical phone to use. A Mobility Profile is like that, but when you sit down at a phone, you login with your username in CUCM and your password. Imagine logging in, with firstname.lastname and a normal complex password on a phone keypad. Now the phone has all your settings and your phone number will ring there. See a bit later on LDAP integration for some options there for username and password options.
Thus the function call should be obvious right? Well turns out there is no getMobilityDeviceProfile, but there is a getMobilityProfile, just to be confusing. That function is for reading the definition of how Mobility is enabled in your instance, not for a profile you could assign to a user. Confused yet? You should be! It is a rabbit hole you hop down as you start working on this product!
Thus you need to deal with Device Profile objects, and just assign the Mobility Profile service to it, at some point. The key there is to realize that Mobility is assigned to a profile in the <services> node and down, which also is happy with a name not a GUID, else we would need to be able to get those GUIDs possibly through implementing a getMobilityProfile query function so we could migrate them into the IDV. (Or hard code strings, but I hate having people hard code strings like this, better to get it yourself in the driver).
Now it turns out that when you add a Device Profile (adDeviceProfile call) you need to assign the Line objects (addLine to create them, and called Directory Number objects in the web GUI) at the same time. Though I guess you could add them later in an updateDeviceProfile. As I noted in part 1 of this series, you can refer to the Line object by its pattern (name, usually the number itself) and Route Partition Name, when you create the Device Profile via an addDeviceProfile call. When you go to modify a Device Profile with updateDeviceProfile, I never could get it to work with anything other than describing the Line objects by their GUID.
Thus you think that you would want to sync in Line objects to the IDV, and then use them to assign to the Device profiles via a DN reference or somesuch. But in my case it turned out that naming of the two lines was basically statically defined so instead I just used the two GUIDs from the Line objects together in the Association value.
That is another interesting thing about the SOAP driver. In an Active Directory driver, the shim itself calculates the Association value. For example, every event on the Publisher channel in an Active Directory driver will have an <association>someGUIDValue</association> come with it. The driver is coded to get the objectGUID from Active Directory and always provide that in event documents. The same is true for most shims. In the case of an API shim like SOAP it is your problem to provide the association value, and deal with it. Do be careful, once you are out of the Input Transform, the engine starts using the association you provided. I ran into this issue in my case, where my SOAP to XDS mapping of the responses was adding an association value, but I forgot to properly format it, so strange merge events were happening. I needed the GUID value, but not as the association yet. I was processing it after the Input Transform, and the engine was merging stuff I did not want based on that.
There is an entire other area of concern in CUCM with Users. The problem is that Cisco has free LDAP User integration. Just like many other apps (Heck like Zenworks Configuration Management, Vibe, Filr even) do, you specify a LDAP user source and either it does pass through authentication against it when needed for user login, and reads back basic User information into its own internal database. The thing is, in those applications when you enable LDAP integration you can usually modify the User information in the local database a little bit. In CUCM once you enable the LDAP integration you must use it for all Users and you cannot change the user. This is one of those check box level features. You know, in the magazine reviews there is a tick box in the feature list, called external LDAP, and this feature basically suffices to meet that tick box, but does very little else.
You cannot easily assign phones to users via the LDAP integration, nor configure much of anything else with it, just basic naming information. You could use it to pass through authentication to the phone, but seriously, you want someone trying to type in their complex passwords on a phone pad? Will that ever succeed? Heck who wants to try and login as their full username to their phone every morning either.
For the password problem. there can be an addition PIN just for CUCM that is numeric only that can be set via the SOAP API (updateUser, the field is <pin> in the SOAP call) and it can be managed via the Phone and the web interface. But not through the LDAP integration. You can imagine a simple workflow to set this in User Application, or else a helpdesk reset function, or the like, where they would not need any CUCM admin console access, just a standard workflow or even a raw attribute in the IDV holding the value. Maybe generate a random password in the PIN namespace and email them the new value? You can see lots of value add here if you wanted.
For the username issue, when you configure the LDAP integration you get asked what attribute you want to use for naming the CUCM User object. The choices are very limited. When used against Active Directory there are three choices:
samAccountName (Since it is guaranteed to be unique within the domain. I suppose if you had multiple domains in CUCM you could have collisions. Dunno what happens then).
ipPhone (single valued in AD, otherIpPhone is the overflow attribute, but CUCM only looks at ipPhone)
telephoneNumber (same as ipPhone, single valued, overflows into otherTelephone)
Thus you object can be named in CUCM geoffc, or maybe 55595 or maybe 555-5595, since often Cisco implementors tell people, use the 10 digit number in telephoneNumber, and put the last 5 digits (Assuming those are the extension, however you map it) so LDAP directory lookups and say Exchange Address book lookups look good with dialable numbers. But then use ipPhone as the naming attribute in CUCM so you can login to the phone as 55595 which is much easier.
The other main problem with the LDAP integration is that it is a scheduled process, say every 6 hours, and until you run an LDAP sync, new users created will not be in CUCM, and there is no way to add them manually. (There is a doLdapSync API call, but you obviously do not want to force a resync every event, and you can only start a complete sync, not force the sync of a single object which in a normal IDM environment might kill the CUCM side, I am not sure.)
I suppose you could have the LDAP Sync scheduled on the CUCM side, and track the number of <add> events and maybe force a sync a bit early, when events are pending. That might be easy enough to add in.