Toolkit Rules in Identity Manager Part 3

0 Likes

Check out part 1 Toolkit Rules in Identity Manager Part 1 and part 2 Toolkit Rules in Identity Manager Part 2 of this series on the process of building a toolkit rule in Novell Identity Manager.



The goal of the toolkit rule we are discussing here is to clean up users, where each user has an EmployeeID attribute, and the HR system provides a DirectorEID which is the EmployeeID of the Director.



When the user is created we are supposed to set the DirectorDN with the DN of the director, since DN syntax attributes are much better to use in eDirectory than say, just an EmployeeID to identify the object. Finally we want to report on, and if wrong, to make sure the DirectorDN has the correct DN value.



When we left off in Part 2, we had a rule that triggers based on a job, or on an attribute changing to 42 (To report the users that need to be fixed) or 43 (to report and fix the users that are incorrectly set). We counted how many of each case existed, and sent an email with the results report that had no hard coded dependencies outside of some Global Configuration Values (GVC).



The rule looks like this so far:



<rule>
<description>[CoolSolutions] Toolkit Rule to fix DirectorDN</description>
<conditions>
<and>
<if-operation mode="case" op="equal">trigger</if-operation>
<if-op-property mode="nocase" name="source" op="equal">JobName</if-op-property>
</and>
<and>
<if-op-attr mode="nocase" name="SomeTriggerAttribute" op="changing-to">42</if-op-attr>
</and>
<and>
<if-op-attr mode="nocase" name="SomeTriggerAttribute" op="changing-to">43</if-op-attr>
</and>
</conditions>
<actions>
<do-set-local-variable name="DIR-CORRECT-COUNT" scope="policy">
<arg-string>
<token-xpath expression="number(0)"/>
</arg-string>
</do-set-local-variable>
<do-set-local-variable name="DIR-WRONG-COUNT" scope="policy">
<arg-string>
<token-xpath expression="number(0)"/>
</arg-string>
</do-set-local-variable>
<do-set-local-variable name="DIR-NOTFOUND-COUNT" scope="policy">
<arg-string>
<token-xpath expression="number(0)"/>
</arg-string>
</do-set-local-variable>
<do-set-local-variable name="USERS" scope="policy">
<arg-node-set>
<token-query class-name="User" datastore="src">
<arg-string>
<token-text xml:space="preserve">DirectorEID</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">DirectorDN</token-text>
</arg-string>
<arg-string>
<token-text xml:space="preserve">EmployeeID</token-text>
</arg-string>
</token-query>
</arg-node-set>
</do-set-local-variable>
<do-for-each>
<arg-node-set>
<token-local-variable name="USERS"/>
</arg-node-set>
<arg-actions>
<do-set-local-variable name="CURR-USER" scope="policy">
<arg-string>
<token-xpath expression="$current-node/@src-dn"/>
</arg-string>
</do-set-local-variable>
<do-set-local-variable name="DIR-DN" scope="policy">
<arg-node-set>
<token-query class-name="User" datastore="src">
<arg-match-attr name="EmployeeID">
<arg-value type="string">
<token-xpath expression="$current-node/attr[@attr-name=DirectorEID]/value/text()"/>
</arg-value>
</arg-match-attr>
</token-query>
</arg-node-set>
</do-set-local-variable>
<do-set-local-variable name="DIR-DN" scope="policy">
<arg-string>
<token-xpath expression="$DIR-DN/@src-dn"/>
</arg-string>
</do-set-local-variable>
<do-if>
<arg-conditions>
<and>
<if-local-variable mode="nocase" name="DIR-DN" op="not-equal"/>
</and>
</arg-conditions>
<arg-actions>
<do-if>
<arg-conditions>
<and>
<if-xpath op="not-true">$current-node/attr[@name=DirectorDN]/value/text()=$DIR-DN</if-xpath>
</and>
</arg-conditions>
<arg-actions>
<do-if>
<arg-conditions>
<and>
<if-op-attr mode="nocase" name="SomeTriggerAttribute" op="changing-to">43</if-op-attr>
</and>
</arg-conditions>
<arg-actions>
<do-set-src-attr-value name="DirectorDN">
<arg-dn>
<token-local-variable name="CURR-USER"/>
</arg-dn>
<arg-value>
<token-local-variable name="DIR-DN"/>
</arg-value>
</do-set-src-attr-value>
</arg-actions>
<arg-actions/>
</do-if>
<do-set-local-variable name="MESSAGE" scope="policy">
<arg-string>
<token-local-variable name="MESSAGE"/>
<token-text xml:space="preserve">
</token-text>
<token-text xml:space="preserve">User </token-text>
<token-local-variable name="CURR-USER"/>
<token-text xml:space="preserve"> had a DirectorDN of </token-text>
<token-xpath expression="$current-node/attr[@attr-name=DirectorDN]/value/text()"/>
<token-text xml:space="preserve"> and we changed it to: </token-text>
<token-local-variable name="DIR-DN"/>
</arg-string>
</do-set-local-variable>
<do-set-local-variable name="DIR-WRONG-COUNT" scope="policy">
<arg-string>
<token-xpath expression="number($DIR-WRONG-COUNT) 1"/>
</arg-string>
</do-set-local-variable>
</arg-actions>
<arg-actions>
<do-set-local-variable name="DIR-CORRECT-COUNT" scope="policy">
<arg-string>
<token-xpath expression="number($DIR-CORRECT-COUNT) 1"/>
</arg-string>
</do-set-local-variable>
</arg-actions>
</do-if>
</arg-actions>
<arg-actions>
<do-set-local-variable name="MESSAGE" scope="policy">
<arg-string>
<token-local-variable name="MESSAGE"/>
<token-text xml:space="preserve">
</token-text>
<token-text xml:space="preserve">User had DirectorEID of </token-text>
<token-xpath expression='$current-node/attr[@attr-name="DirectorEID"]/value/text()'/>
<token-text xml:space="preserve"> but we could not find a user with that EID in the system.</token-text>
</arg-string>
</do-set-local-variable>
<do-set-local-variable name="DIR-NOTFOUND-COUNT" scope="policy">
<arg-string>
<token-xpath expression="number($DIR-NOTFOUND-COUNT) 1"/>
</arg-string>
</do-set-local-variable>
</arg-actions>
</do-if>
</arg-actions>
</do-for-each>
<do-send-email server="~smtpServer~" type="text">
<arg-string name="to">
<token-global-variable name="toErrorEmails"/>
</arg-string>
<arg-string name="from">
<token-global-variable name="fromEmail"/>
</arg-string>
<arg-string name="cc">
<token-global-variable name="toCCErrorEmails"/>
</arg-string>
<arg-string name="subject">
<token-time format="!LONG.DATETIME" tz="EST5EDT"/>
<token-text xml:space="preserve">: For driver </token-text>
<token-global-variable name="dirxml.auto.driverdn"/>
<token-text xml:space="preserve"> DirectorEID/DN toolkit rule report </token-text>
<token-op-attr name="SomeTriggerAttribute"/>
<token-text xml:space="preserve"> </token-text>
<token-op-property name="source"/>
</arg-string>
<arg-string name="message">
<token-local-variable name="MESSAGE"/>
</arg-string>
</do-send-email>
</actions>
</rule>





Lets continue adding some neat features and tweaks to the rule. The goal of this series is to use a specific example to show you how to do general tasks that are useful and you can reuse in your own rules to fix things you need updated in your environment.



Memory used and node counts:



If you read the articles:





you will notice I discuss the amount of memory that a node in a node set uses up. This is good to know, so that you can estimate how much memory your query might eat up, and know if you need to boost the Java heap size before you start using it. Well it turns out I used this exact model of rule to figure all that data out. (I think it was a different specific example, but it was this style of rule that generated the data I used).



First off, lets start with seeing how many nodes the Query token returned. We will set a local variable, NODE-COUNT to the XPATH of count($USERS). This will count the number of nodes at the level of the context node, and thus return the number of <instance> nodes returned, which should be one per user found.



<do-set-local-variable name="NODE-COUNT" scope="policy">
<arg-string>
<token-xpath expression="count($USERS)"/>
</arg-string>
</do-set-local-variable>





I am sure there is an XPATH that will do a recursive count of all the nodes, but I do not know what it is. (If someone does, let me know, because that would be really neat and useful! The thing I find all the time with XPATH is that there is always an interesting way to do something I need, I just need to learn what it is.) So if I wanted to see all the nodes, I could do count($USERS/attr) to see how many <attr> nodes got returned, and count($USERS/attr/value) to see how many <value> nodes got returned. Usually those should be the only nodes (possibly an <association> node as well I suppose) so the total count should give you a feel for how many nodes.



For the purposes of this rule, I think we should be ok with just a count of how many users we find. In Part 2, I discussed the three counters we will maintain for the three cases of events. DIR-CORRECT-COUNT for users who have the correct version set. DIR-WRONG-COUNT for users whose DirectorDN is not correct, and finally DIR-NOTFOUND-COUNT for users where the DirectorEID on a User does not resolve to a DN.



This is a nice way to confirm how many users we got (NODE-COUNT) in the query, which ought to be the same as the sum of the other three counters. If not, those are yet another error case, probably users without DirectorEID values. We should probably account for that case as well, so lets add a test and a counter, initialize it first.



<do-set-local-variable name="DIR-NODIR-EID-COUNT" scope="policy">
<arg-string>
<token-xpath expression="number(0)"/>
</arg-string>
</do-set-local-variable>





Then lets test for this particular case, with an XPATH condition test of $current-node/attr[@attr-name="DirectorEID"] will be true if the node exists, (it will not be there if there is no value in eDirectory). Thus we test for not true, in which case we increment the counter, and add a line to the MESSAGE variable, and then pick up the rest of the rules that were just below it and paste them into the ELSE node of the IF test. I did not show them below, since it is not any new content.



<do-if>
<arg-conditions>
<and>
<if-xpath op="not-true">$current-node/attr[@attr-name="DirectorEID"]/value</if-xpath>
</and>
</arg-conditions>
<arg-actions>
<do-set-local-variable name="MESSAGE" scope="policy">
<arg-string>
<token-local-variable name="MESSAGE"/>
<token-text xml:space="preserve">
</token-text>
<token-text xml:space="preserve">User </token-text>
<token-local-variable name="CURR-USER"/>
<token-text xml:space="preserve"> does not have a value for DirectorEID.</token-text>
</arg-string>
</do-set-local-variable>
<do-set-local-variable name="DIR-NODIR-EID-COUNT" scope="policy">
<arg-string>
<token-xpath expression="number($DIR-NODIR-EID-COUNT) 1"/>
</arg-string>
</do-set-local-variable>
</arg-actions>
<arg-actions>
Bulk of the rule not shown, since it is the same as before, just pasted into this spot...
</arg-actions>
</do-if>





Next lets try and see how much memory this rule uses. The XPATH to call the Java function that will reveal this value is java.lang.Runtime:totalMemory((java.lang.Runtime:getRuntime())) and can be found in the article:
Reading and Displaying the Value of Java Heap in Identity Manager Rules



We need to grab the value at the beginning of the rule, and then at the end of the rule, just before the send email token.



At the beginning of the rule:



<do-set-local-variable name="START-MEMORY" scope="policy">
<arg-string>
<token-xpath expression="java.lang.Runtime:totalMemory((java.lang.Runtime:getRuntime()))"/>
</arg-string>
</do-set-local-variable>





Right near the end of the rule:



<do-set-local-variable name="FINISH-MEMORY" scope="policy">
<arg-string>
<token-xpath expression="java.lang.Runtime:totalMemory((java.lang.Runtime:getRuntime()))"/>
</arg-string>
</do-set-local-variable>





Then we should subtract the two to get the memory change:



<do-set-local-variable name="MEMORY-CHANGE" scope="policy">
<arg-string>
<token-xpath expression="number($FINISH-MEMORY) - number($START-MEMORY)"/>
</arg-string>
</do-set-local-variable>





Reporting the time taken:



Another really nice thing is to know how long the rule took to run. If it suddenly takes only a second, probably something went wrong, if it had previously been taking five minutes. Lets use one of my favorite tokens, the Time token for most of this. We will use CTIME time format, which is basically seconds since Jan 1, 1970, so that we can subtract the START-TIME from the FINISH-TIME to get the TOTAL-TIME taken.



A useful twist is to get the time the Query token took, since this potentially can be a real time waster if eDirectory is running slowly.



Lets get the start time:


<do-set-local-variable name="START-TIME" scope="policy">
<arg-string>
<token-time format="!CTIME" tz="UTC"/>
</arg-string>
</do-set-local-variable>





Then the end of query time:


<do-set-local-variable name="END-OF-QUERY-TIME" scope="policy">
<arg-string>
<token-time format="!CTIME" tz="UTC"/>
</arg-string>
</do-set-local-variable>





Lets get the finish time:


<do-set-local-variable name="FINISH-TIME" scope="policy">
<arg-string>
<token-time format="!CTIME" tz="UTC"/>
</arg-string>
</do-set-local-variable>





Do some math to get the TOTAL-TIME taken:



<do-set-local-variable name="TOTAL-TIME" scope="policy">
<arg-string>
<token-xpath expression="number($FINISH-TIME) - number($START-TIME)"/>
</arg-string>
</do-set-local-variable>





Do some more math to get the QUERY-DURATION:


<do-set-local-variable name="QUERY-DURATION" scope="policy">
<arg-string>
<token-xpath expression="number($END-OF-QUERY-TIME) - number($START-TIME)"/>
</arg-string>
</do-set-local-variable>





Now that we have finished counting and storing all the various interesting values before and after, lets get them all into a variable (HEADER) that we will send as an email address. We need to add this stuff at the very end, since we do not know most of it until almost the entire rule is complete, leaving just the email to go, so we will build the HEADER variable, and then modify the send email token to send be the HEADER local variable, a carriage return, and then the MESSAGE local variable. This of course personal preference, and feel free to make it look however you want it to look. I just want to get the data into the email.



<do-set-local-variable name="HEADER" scope="policy">
<arg-string>
<token-time format="!LONG.DATETIME" tz="EST5EDT"/>
<token-text xml:space="preserve">: Report of users with DirectorEID and DirectorDN values.

Start time: </token-text>
<token-convert-time dest-format="!LONG.DATETIME" dest-tz="EST5EDT" src-format="!CTIME" src-tz="UTC">
<token-local-variable name="START-TIME"/>
</token-convert-time>
<token-text xml:space="preserve">
Finish Time: </token-text>
<token-convert-time dest-format="!LONG.DATETIME" dest-tz="EST5EDT" src-format="!CTIME" src-tz="UTC">
<token-local-variable name="FINISH-TIME"/>
</token-convert-time>
<token-text xml:space="preserve">
The Query for </token-text>
<token-local-variable name="NODE-COUNT"/>
<token-text xml:space="preserve"> User objects, took </token-text>
<token-local-variable name="QUERY-DURATION"/>
<token-text xml:space="preserve"> seconds. The entire rule took </token-text>
<token-local-variable name="TOTAL-TIME"/>
<token-text xml:space="preserve"> seconds.

We found </token-text>
<token-local-variable name="DIR-CORRECT-COUNT"/>
<token-text xml:space="preserve"> users with the correct DirectorDN value. We found </token-text>
<token-local-variable name="DIR-WRONG-COUNT"/>
<token-text xml:space="preserve"> users with the wrong DirectorDN value. We found </token-text>
<token-local-variable name="DIR-NOTFOUND-COUNT"/>
<token-text xml:space="preserve"> users where we could not find the DirectorDN from the given DirectorEID. We found </token-text>
<token-local-variable name="DIR-NODIR-EID-COUNT"/>
<token-text xml:space="preserve"> users with no DirectorEID.

</token-text>
</arg-string>
</do-set-local-variable>





One subtlety that can be used to make this a little easier to read in the report would be to use a results variable for each case that we report, so that they can be shown in the report email in order, as opposed to this example where they are all interleaved together, basically in the same order that they got returned by the Query token from eDirectory.



Tuning:



Now for probably the most important part, tuning. If this takes two hours to run, how useful is it really?



It turns out that Dstrace has the greatest performance penalty. I was really surprised how MUCH of a performance penalty having Dstrace on can cost. I was at a client site that had a JDBC driver, running in Triggerless mode, where due to a bug in how the state file was maintained, would resync far more often than we would like. There were about 10,000 objects to resync, and with Dstrace running, showing level 3 trace, we found it took about 3-4 seconds to resync a user object. We turned Dstrace to level 0 for just that JDBC driver, and each user was taking 100-200 milliseconds. That is a factor of 20-30 times performance difference.



One approach is just turn Dstrace off entirely on this driver object. That serves our purpose, but makes it hard to watch what is going on and troubleshoot any issues that come up.



One neat feature in Identity Manager 3.5 and higher is that each condition, action, and token can be individually set to "no trace". Thus we can identify the expensive items in our rule and start disabling the trace on the slow tracing actions.



In general displaying a large node set is quite painful. If you have ever seen it in Dstrace, it does not usually show the node set as an XML document, but it shows a little bit of information about each node, and if you use this variable in a number of different actions, it gets shown in trace, and that really slows it down.



This also applies to large string variables, like all the times we add a value to the MESSAGE variable. Each time we do that, inside the big for-each loop, because we set MESSAGE equal to the MESSAGE local variable, plus the new value we want to add, Dstrace will show the entire MESSAGE variable first, each time we loop through that.



The Query itself, returns a large document (thousands of nodes probably) and that too takes time to trace.



Ultimately, if we can, turning of trace on the big for each loop is the best way to go. The problem is, we really need to watch that a few times to see what is going on and troubleshoot any errors that show up. Usually I find I need to retry a few times to get the XPATH just right, and to catch some dumb typos I invariably make.



To make this workable, what I usually do is copy the set local variable USERS to a Query token, disable the one that queries all users, and scope down the copy so it only returns a few dozen to a few hundred objects, so I can work through the loops in a reasonable amount of time, and get it all right. Once it looks to be working properly, I disable the scoped down rule, and enable the real rule, and test it out.



Thus for performance needs, I disabled trace on the Query token for the USERS variable, all the MESSAGE local variable actions, and the send email action, since it needs to show the entire message which is a bit slow.



The final rule is available in the attached XML file. Do not forget the GCVs we used in the send email action, and are needed for this rule to work.



Here are the GCV's needed with some nonesense values:



<definition display-name="IP name of the SMTP Server                   (smtpServer)" name="smtpServer" type="string">
<description/>
<value>smtp.acme.com</value>
</definition>
<definition display-name="Email address list, comma seperated to send error emails too. (toErrorEmails)" name="toErrorEmails" type="string">
<description/>
<value>idm-dev-tester@acme.com</value>
</definition>
<definition display-name="Email address list, comma seperated to send error emails too. (toCCErrorEmails)" name="toCCErrorEmails" type="string">
<description/>
<value>idm-dev-tester@acme.com</value>
</definition>
<definition display-name="Email address for the From: field, include tree name (fromEmail)" name="fromEmail" type="string">
<description/>
<value>idm-dev@acme.com</value>
</definition>






Stay tuned for part 4, when I discuss some other types of things you can do inside the for each loop, that start to get really cute! Like making sure uniqueID is actually unique. (Which can be modified for any attribute you care about).


Labels:

How To-Best Practice
Comment List
Related
Recommended