Novell Identity Manager has a lot of available drivers to connect to other systems. I have written about a number of them in the past.

Some are very generic like the JDBC driver, which in one driver supports many different databases or the LDAP driver where one driver can connect to many different LDAP systems.

Others connect to quite specific systems, like the Active Directory or Lotus Notes driver.

I have not done a lot with the GroupWise driver yet, so lots of functionality is new to me. When I start using a new driver and find things that are new to me, I like to write about it, so others can get the benefit of my learning experience.

I have found a number of Cool Solution articles that have been helpful and I wanted to contribute some more.

A really interesting article I found, was by David Gersic, (a prolific poster in the Support Forums at http://forums.novell.com in the Identity Manager Engine and Drivers forums).

Using the GroupWise Post Office Record Count with IDM

In the article David addresses how he load balances the placement of users across multiple post offices in his GroupWise system. In David’s case, he has a specific need, which is to top up each of his post offices and make sure they hit 1000 users, and never more.

He wants to fill up the first one then move on to the next and so on.

I needed a more generic and a more load balanced in the traditional sense of load balancing. That is, I wanted to check each Post Office, figure out which one currently has the fewest users, and then place the user in that minimum user Post Office. This way we get something of a smoother load balance across the various Post Offices. Do note, that I do not intend to somehow move users around to force a leveling after the fact. I only want to be sure that each new user gets placed in the least loaded Post Office. I have to figure out how to do that leveling, let me see what I can figure out and maybe that will be a later article.

Additionally David wrote his article based on working code in an Novell Identity Manager 3.0 implementation. I really love the new features that got added in Novell Identity Manager 3.5, 3.51 and 3.6 and want to leverage them to make all sorts of things easier and better.

On top of that I really want to stay away from XSLT when not needed, and in Identity Manager 3.0 to do what David needed, you really had no other choice. The good news, and in fact David noted it in his original article that it should be easy to do in Identity Manager 3.5 and higher. He is quite correct and it is really easy.

Basically the issue is, how do you find out how many users are in a GroupWise Post Office? Well the docs actually have the answer in section 5.3.6 of the Identity Manager 3.6 documents.

5.3.6 Getting a Record Count from a Query

I am not sure how well the link will work as linking to Docs sometimes disappear, thus look for section 5.3.6 in the Identity Manager Drivers documents for GroupWise, for version 3.6. That should work for a few more years.

The Query that needs to be generated, looks something like:

<nds dtdversion="1.1" ndsversion="8.6">
	<input>
		<query event-id="query-groupwise" scope="subtree">
			<search-class class-name="User"/>
				<search-attr attr-name="50035">
	                            <value>DOMAINNAME</value>
			</search-attr>
			<search-attr attr-name="50062">
                              <value>PO-NAME</value>
                 	</search-attr>
			<read-attr attr-name="Record Count"/>
		</query>
	</input>
</nds>

It’s worth noting that I grabbed that from the docs, and as I did that, I noticed there was a typo in the docs, the last <search-attr> node is not actually closed, they missed a forward slash. Just worth noting, I commented on the documentation page and they likely will fix it eventually.

Here we have to ask for a class name = User (even though we are really looking at the Post Office object, I found this part confusing, but such is the way it is!), where the attribute 50035 in GroupWise has the DOMAINNAME value, and the attribute 50062 in GroupWise has the PO-NAME value.

What David Gersic noted in his article is that in Identity Manager 3.0 you can use the Java Command Processor to send a Query event, but that really only takes a single search-attr value. That looks something like the line from his article:

<if-xpath op="true">query:search($destQueryProcessor, "subtree", "", "", "User", "50062", "PO2Dev", "Record Count") < 1000</if-xpath>

Now David uses it in a condition, and then tests if the value returned is less than 1000, so the simplest view of that query is probably:

query:search($destQueryProcessor, "subtree", "", "", "User", "50062", "PO2Dev", "Record Count"

Then he needs some XSLT to add on a node to this event to add the query for the <search-attr attr-name=”50035″> that I want to avoid needing.

The good news is that in Identity Manager 3.5 and higher, with the Query token (you can see more about this token at: The Query token in Identity Manager ) you can specify multiple search attributes, so that gets replaced with a much easier to use GUI, in either Designer or iManager, that looks something like:

<token-query class-name="User">
	<arg-match-attr name="50062">
		<arg-value type="string">
			<token-local-variable name="current-node"/>
		</arg-value>
	</arg-match-attr>
	<arg-match-attr name="50035">
		<arg-value type="string">
			<token-global-variable name="gw.Domain"/>
		</arg-value>
	</arg-match-attr>
	<arg-string>
		<token-text xml:space="preserve">Record Count</token-text>
	</arg-string>
</token-query>

While that is certainly more verbose than the old way, using a GUI to generate and maintain it is hugely easier and more maintainable to my mind.

That solves one issue David had raised as being easier in newer versions of Identity Manager. He had to add a second piece of code in an XSLT stylesheet elsewhere in the driver to look for this type of query, and add in the second <arg-match-attr> node since only one can be queried for easily in Identity Manager 3.0. After working through this problem, I think I know how I would do it in Identity Manager 3.0 without the stylesheet, but there is no need to go into that now since in 3.5 and higher it is so much easier.

The next problem is more of a scaleability thing. I want to be able to support as many Post offices as needed, I need to be able to add new ones to the load balancing ring when I want too, and finally I do not want to modify any code to it.

In David’s example, the use case he is following is to make sure each Post Office does not have more than 1000 users. As soon as the first one is full (aka 1000 users) he moves on the next one and so on.

I want a more traditional load balancing model, where I have a pool of several Post Offices and I fill the emptiest one, and so on.

Here is a mostly complete rule that I put together to do that, based on storing an attribute on the driver (which is defined in a Global Configuration Value, so that if I use this at multiple clients I just change the attribute name in the GCV. I usually name custom attributes with some unique tag at the beginning to guarantee unique schema names across the board. Thus the custom attribute will be different each time I use this rule, so I wanted to abstract that as well.). I like using this model, since attributes on an object are easy to read, parse, and manipulate in Identity Manager. We have a default GCV available called, dirxml.auto.driverdn which always returns the current drivers Distinguished Name (DN), which is enough to know what object to read from.

Put all that together and it is pretty trivial to store data on the Driver object with an auxiliary class that has this custom attribute, and you are good to go. The attribute should be a Case Ignore String syntax, and multi valued. For more about Schema types and syntaxes you can read these articles:

<rule>
	<description>[acme] PO Load balancing placement rule</description>
	<comment xml:space="preserve">
	
I want this to be generic and I store the name of the attr that we use to list the PO's as a GCV.

Set PO-LIST to values of the attr in the GCV that stores the attr name, read it off the driver object.
If there are no values, use the default value the driver ships with.
If there is a value returned, then set MIN to a high number.

Loop through the PO-LIST nodeset and query where 50062 is the current nodes value (Current PO name we are trying), and the 50035 value is our domain.  Read the Record Count attr which should show how many users are in this PO.

f the count is less than MIN, this is our new min.  Set MIN to PO-COUNT, set MIN-PO to the current nodes value.

When done looping, set the op'n dest dn to the MIN-PO value. </comment>

	<conditions>
		<and>
			<if-class-name mode="nocase" op="equal">User</if-class-name>
		</and>
	</conditions>
	<actions>
		<do-set-local-variable name="PO-LIST" scope="policy">
			<arg-node-set>
				<token-src-attr name="~gw.POListAttrName~">
					<arg-dn>
						<token-global-variable name="dirxml.auto.driverdn"/>
					</arg-dn>
				</token-src-attr>
			</arg-node-set>
		</do-set-local-variable>
		<do-if>
			<arg-conditions>
				<and>
					<if-xpath op="true">count($PO-LIST) > 0</if-xpath>
				</and>
			</arg-conditions>
			<arg-actions>
				<do-set-local-variable name="MIN" scope="policy">
					<arg-string>
						<token-text xml:space="preserve">99999999999</token-text>
					</arg-string>
				</do-set-local-variable>
				<do-for-each>
					<arg-node-set>
						<token-local-variable name="PO-LIST"/>
					</arg-node-set>
					<arg-actions>
						<do-set-local-variable name="PO-COUNT" scope="policy">
							<arg-string>
								<token-query class-name="User">
									<arg-match-attr name="50062">
										<arg-value type="string">
											<token-local-variable name="current-node"/>
										</arg-value>
									</arg-match-attr>
									<arg-match-attr name="50035">
										<arg-value type="string">
											<token-global-variable name="gw.Domain"/>
										</arg-value>
									</arg-match-attr>
									<arg-string>
										<token-text xml:space="preserve">Record Count</token-text>
									</arg-string>
								</token-query>
							</arg-string>
						</do-set-local-variable>
						<do-if>
							<arg-conditions>
								<and>
									<if-xpath op="true">number($PO-COUNT) < number($MIN)</if-xpath>
								</and>
							</arg-conditions>
							<arg-actions>
								<do-set-local-variable name="MIN" scope="policy">
									<arg-string>
										<token-local-variable name="PO-COUNT"/>
									</arg-string>
								</do-set-local-variable>
								<do-set-local-variable name="MIN-PO" scope="policy">
									<arg-string>
										<token-local-variable name="current-node"/>
									</arg-string>
								</do-set-local-variable>
							</arg-actions>
							<arg-actions/>
						</do-if>
						<do-set-local-variable name="PO-REPORT" scope="policy">
							<arg-string>
								<token-local-variable name="PO-REPORT"/>
								<token-text xml:space="preserve">
PO Name: </token-text>
								<token-local-variable name="current-node"/>
								<token-text xml:space="preserve">      Number of users:  </token-text>
								<token-local-variable name="PO-COUNT"/>
							</arg-string>
						</do-set-local-variable>
					</arg-actions>
				</do-for-each>
				<do-set-local-variable name="PO-REPORT" scope="policy">
					<arg-string>
						<token-local-variable name="PO-REPORT"/>
						<token-text xml:space="preserve">
 and we added the user to the PO:  </token-text>
						<token-local-variable name="MIN-PO"/>
					</arg-string>
				</do-set-local-variable>
				<do-set-op-dest-dn>
					<arg-dn>
						<token-local-variable name="MIN-PO"/>
					</arg-dn>
				</do-set-op-dest-dn>
				<do-trace-message>
					<arg-string>
						<token-text xml:space="preserve">Shazam!  </token-text>
						<token-local-variable name="PO-REPORT"/>
					</arg-string>
				</do-trace-message>
			</arg-actions>
			<arg-actions>
				<do-set-op-dest-dn>
					<arg-dn>
						<token-global-variable name="driver.gw.SubSyncDestLocation"/>
					</arg-dn>
				</do-set-op-dest-dn>
			</arg-actions>
		</do-if>
	</actions>
</rule>

Now to explain what is going on in the rule. (Personally I dislike it when people post rules in an article and leave it at, “Magic happens here”. I want to see an explanation of what is going on, and the interesting things being done. I even have this argument with my boss at times. He very often does clever and sneaky things in rules, that when I read it the first time I say, “That won’t work” or “Thats wrong” and by the third reading and thinking through, I realize he was being clever. Be nice if he documented that in the description of the rule.)

First we get our nodeset variable, PO-LIST which is a list of the Post Office names.

<do-set-local-variable name="PO-LIST" scope="policy">
	<arg-node-set>
		<token-src-attr name="~gw.POListAttrName~">
			<arg-dn>
				<token-global-variable name="dirxml.auto.driverdn"/>
			</arg-dn>
		</token-src-attr>
	</arg-node-set>
</do-set-local-variable>

We do this by reading the attribute named in the GCV gw.POListAttrname. (Note we can do that, via the use of tildes (~) on either side of the GCV name. Neat eh? You can see more about this in these articles about using GCV’s, and incidentally how to change all explicit references in the Active Directory to DN’s in eDirectory or Active Directory, at the same time! Read and learn:

Get the values of the attribute from the driver object (using the dirxml.auto.driverdn GCV to get us the object to read, the driver itself) and store them into our nodeset variable, PO-LIST.

Next a quick data validation, lets make sure we actually got some values, (thats the XPATH expression of count($PO-LOIST) > 0 part), and if not just use the default Post office value from the GCV you configured when you imported the driver. (The ELSE action of the IF statement).

Lets assume we got at least one value back for PO-LIST. First set a variable MIN to some large number.

Then lets loop through the PO-LIST nodeset, and each time through, the current-node variable stores a PO name we need to test, so we do our query we defined above, but for PO-NAME, we replace it with the local variable value from the current node, and we get the Domain name from a GCV gw.Domain.

<do-set-local-variable name="PO-COUNT" scope="policy">
	<arg-string>
		<token-query class-name="User">
			<arg-match-attr name="50062">
				<arg-value type="string">
					<token-local-variable name="current-node"/>
				</arg-value>
			</arg-match-attr>
			<arg-match-attr name="50035">
				<arg-value type="string">
					<token-global-variable name="gw.Domain"/>
				</arg-value>
			</arg-match-attr>
			<arg-string>
				<token-text xml:space="preserve">Record Count</token-text>
			</arg-string>
		</token-query>
	</arg-string>
</do-set-local-variable>

We should have gotten a numeric value back, and then check with some simple XPATH math (see this article for more on using XPATH for things like math:
XPATH and math) and see if the value we retrieved was less than the MIN variable. First time round it should always be true since we set MIN to some very high number.

If this is our current minimum Post Office, then we set MIN to this value, and MIN-PO to the name of this Post Office.

I like to report these things into a string, so that while testing I can email myself copies on each test as an easier way to see search results than searching the trace output. If you paste each step into a string variable that continues to grow, while it can be really slow for large loops, it does make it easier to see what is going on in trace.

Finally, once we are done with the loop, we write another report value, as to which Post Office had the fewest, and then to make it easy to find in trace, we trace out the work Shazam! and then the PO-REPORT variable we had been following.

Now that we have the Post Office we care about, we can set the operation destination DN on the current User event document to match, and we have just placed the user in the emptiest of the Post Offices.

There are a number of ways I would like to improve this rule. First off, probably storing the value as a custom attribute on the driver object is a bit of overkill. Sure it is easy in iManager, ConsoleOne or LDAP to modify the values, but there ought to be a better way. I can think of two and in part two of this article I will work through those two other options. As a hint I will say, a GCV of all the values, or a mapping table.

The second issue is that I found out after I did this, that often clients will have more than one Domain and that I would probably need to track which Domain each Post Office is part of, for the query to properly work. There are a couple of ways to do that and I will go into them more in Part 2 as well.

0 votes, average: 0.00 out of 50 votes, average: 0.00 out of 50 votes, average: 0.00 out of 50 votes, average: 0.00 out of 50 votes, average: 0.00 out of 5 (0 votes, average: 0.00 out of 5)
You need to be a registered member to rate this post.
Loading...Loading...

Disclaimer: As with everything else at NetIQ Cool Solutions, this content is definitely not supported by NetIQ, so Customer Support will not be able to help you if it has any adverse effect on your environment.  It just worked for at least one person, and perhaps it will be useful for you too.  Be sure to test in a non-production environment.

Leave a Reply

No Comments
geoffc
By: geoffc
Mar 4, 2009
12:13 pm
Reads:
1,072
Score:
Unrated