mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

ugaston
14.20.2009 9ed2bbf2be91150b72bf8987bca63f01a8457ab5
First set of External Changelog functional tests
2 files added
3 files modified
3190 ■■■■■ changed files
opends/tests/staf-tests/functional-tests/testcases/replication/externalchangelog/externalchangelog.xml 2039 ●●●●● patch | view | raw | blame | history
opends/tests/staf-tests/functional-tests/testcases/replication/replication.xml 1 ●●●● patch | view | raw | blame | history
opends/tests/staf-tests/shared/functions/ldap.xml 323 ●●●●● patch | view | raw | blame | history
opends/tests/staf-tests/shared/functions/utils.xml 658 ●●●●● patch | view | raw | blame | history
opends/tests/staf-tests/shared/java/ldapjndi/modifyDn.java 169 ●●●●● patch | view | raw | blame | history
opends/tests/staf-tests/functional-tests/testcases/replication/externalchangelog/externalchangelog.xml
New file
@@ -0,0 +1,2039 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE stax SYSTEM "../../../../shared/stax.dtd">
<!--
 ! CDDL HEADER START
 !
 ! The contents of this file are subject to the terms of the
 ! Common Development and Distribution License, Version 1.0 only
 ! (the "License").  You may not use this file except in compliance
 ! with the License.
 !
 ! You can obtain a copy of the license at
 ! trunk/opends/resource/legal-notices/OpenDS.LICENSE
 ! or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 ! See the License for the specific language governing permissions
 ! and limitations under the License.
 !
 ! When distributing Covered Code, include this CDDL HEADER in each
 ! file and include the License file at
 ! trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 ! add the following below this CDDL HEADER, with the fields enclosed
 ! by brackets "[]" replaced with your own identifying information:
 !      Portions Copyright [yyyy] [name of copyright owner]
 !
 ! CDDL HEADER END
 !
 !      Copyright 2008-2009 Sun Microsystems, Inc.
 ! -->
<stax>
  <defaultcall function="replication_externalchangelog"/>
  <function name="replication_externalchangelog">
    <sequence>
      <block name="'externalchangelog'">
        <sequence>
          <script>
            if not CurrentTestPath.has_key('group'):
              CurrentTestPath['group']='replication'
            CurrentTestPath['suite']=STAXCurrentBlock
          </script>
          <call function="'testSuite_Preamble'"/>
          <!--- Test Suite information
          #@TestSuiteName       Replication External Changelog Tests
          #@TestSuitePurpose    Test the event notification system provided by
                                the External Changelog.
          #@TestSuiteID         External Changelog Tests
          #@TestSuiteGroup      External Changelog
          #@TestGroup           Replication
          #@TestScript          replication_externalchangelog.xml
          #@TestHTMLLink        http://opends.dev.java.net/
          -->
          <import machine="STAF_LOCAL_HOSTNAME"
                  file="'%s/testcases/replication/replication_setup.xml'
                        % (TESTS_DIR)"/>
          <call function="'replication_setup'">
            { 'dataFile' : 'Example.ldif' }
          </call>
          <script>
            # initialise the lastCookie value to that for the first ECL request
            lastCookie = ';'
            nextCookie = None
            addOperationalAttrs = """\* creatorsname createtimestamp entryuuid"""
            modOperationalAttrs = """\* modifiersname modifytimestamp entryuuid"""
            class Entry:
              def __init__(self, rdn):
                self.userDn = '%s,ou=People,%s' \
                              % (rdn, synchroSuffix)
                self.listAttr = []
                self.listAttr.append('objectclass:top')
                self.listAttr.append('objectclass:organizationalperson')
                self.listAttr.append('objectclass:inetorgperson')
                self.listAttr.append('objectclass:person')
              def getDn(self):
                return self.userDn
              def getAttrList(self):
                return self.listAttr
              def addAttr(self, attrType, attrValue):
                self.listAttr.append('%s:%s' % (attrType, attrValue))
          </script>
          <!--- Test Case information
          #@TestMarker          Replication External Changelog Tests
          #@TestName            Replication: External Changelog: Add_1
          #@TestID              Add_1
          #@TestPurpose         Verify add operation is recorded properly in the
                                external changelog on each replication server
          #@TestPreamble
          #@TestSteps           Add entry to server A
          #@TestSteps           Verify servers in sync
          #@TestSteps           Read entry from ldap server
          #@TestSteps           Read last changelog entry on each repl server
          #@TestSteps           Check changelog entry against expected values
          #@TestPostamble
          #@TestResult          Success if the server synchronised and the
                                changelog entry value check succeeds
          -->
          <testcase name="getTestCaseName('Add_1')">
            <sequence>
              <call function="'testCase_Preamble'"/>
              <message>
                'Replication: External Changelog: Add_1. \
                Verify add operation is recorded properly in the external \
                changelog on each replication server'
              </message>
              <!-- Add entry to "master" server -->
              <script>
                myEntry = Entry('cn=a')
                myEntry.addAttr('sn', 'a')
                myEntry.addAttr('description', '1')
                myEntry.addAttr('description', '2')
                myEntry.addAttr('uid', 'A')
                addedEntry = None
                eclEntry   = None
              </script>
              <call function="'addAnEntry'">
                { 'location'       : masterHost,
                  'dsPath'         : masterPath,
                  'dsInstanceHost' : masterHost,
                  'dsInstancePort' : master.getPort(),
                  'dsInstanceDn'   : master.getRootDn(),
                  'dsInstancePswd' : master.getRootPwd(),
                  'DNToAdd'        : myEntry.getDn(),
                  'listAttributes' : myEntry.getAttrList()
                }
              </call>
              <!-- Verify the synchronization of the trees among the servers in
                the topology -->
              <call function="'verifyTrees'">
                [ clientHost, clientPath, master, consumerList, synchroSuffix ]
              </call>
              <!-- Retrieve entry information for checkChangelogEntry's sake -->
              <call function="'ldapSearchWithScript'">
                { 'location'       : masterHost,
                  'dsPath'         : masterPath,
                  'dsInstanceHost' : masterHost,
                  'dsInstancePort' : master.getPort(),
                  'dsInstanceDn'   : master.getRootDn(),
                  'dsInstancePswd' : master.getRootPwd(),
                  'dsBaseDN'       : myEntry.getDn(),
                  'dsFilter'       : 'objectclass=*',
                  'dsAttributes'   : addOperationalAttrs
                }
              </call>
              <script>
                searchRC = STAXResult[0][0]
                searchResult = STAXResult[0][1]
              </script>
              <if expr="searchRC == 0">
                <sequence>
                  <message>
                    'This is the entry as read from server %s:%s: \n%s' % \
                    (masterHost, master.getPort(), searchResult)
                  </message>
                  <call function="'parseLdifEntry'">
                    { 'ldifEntry' : searchResult }
                  </call>
                  <script>
                    addedEntry = STAXResult
                    myTargetDN             = addedEntry['dn'][0]
                    myChangeType           = 'add'
                    myChangeTime           = addedEntry['createtimestamp'][0]
                    myTargetEntryUUID      = addedEntry['entryuuid'][0]
                    # myReplicationCSN     = ----> grab from where???
                    # myReplicatIdentifier = ----> grab from ldap server
                    myChanges              = {}
                    for attr in addedEntry.keys():
                      if (attr != 'dn') and (attr != 'changelogcookie') :
                        myChanges[attr] = addedEntry[attr]
                  </script>
                </sequence>
                <else>
                  <message log="1" level="'Error'">
                    'Could not read added entry %s from server %s:%s' % \
                    (myEntry.getDn(), masterHost, master.getPort())
                  </message>
                </else>
              </if>
              <!-- Search changelog in the various replication servers -->
              <iterate var="server"
                       in="_topologyServerList"
                       indexvar="i">
                <sequence>
                  <script>
                    if globalSplitServers:
                      replServer = _topologyReplServerList[i]
                    else:
                      replServer = server
                    replServerPath = '%s/%s' % (replServer.getDir(), OPENDSNAME)
                  </script>
                  <message>
                    'Reading changelog in replication server %s:%s from index: \
                    %s' % \
                    (replServer.getHostname(), replServer.getPort(), lastCookie)
                  </message>
                  <!-- Search for entry add -->
                  <call function="'SearchExternalChangelog'">
                    { 'location'        : replServer.getHostname(),
                      'dsPath'          : replServerPath,
                      'dsInstanceHost'  : replServer.getHostname(),
                      'dsInstancePort'  : replServer.getPort(),
                      'dsInstanceDn'    : replServer.getRootDn(),
                      'dsInstancePswd'  : replServer.getRootPwd(),
                      'dsScope'         : 'subordinate',
                      'dsBaseDN'        : 'cn=changelog',
                      'dsFilter'        : '(objectclass=*)',
                      'changelogCookie' : lastCookie
                    }
                  </call>
                  <script>
                    searchRC = STAXResult[0][0]
                    searchResult = STAXResult[0][1]
                  </script>
                  <if expr="searchRC == 0">
                    <sequence>
                    <message>
                      'This is the ECL entry as read from server %s:%s: \n%s' %\
                      (replServer.getHostname(), replServer.getPort(),
                       searchResult)
                    </message>
                      <call function="'parseLdifEntry'">
                        { 'ldifEntry' : searchResult }
                      </call>
                      <script>
                        eclEntry = STAXResult
                        nextCookie = eclEntry['changelogcookie'][0]
                      </script>
                      <message>
                        'Parsed changelog entry:  \n%s' % eclEntry
                      </message>
                      <message>
                        'Cookie to use for the next ECL search:  %s' % \
                        nextCookie
                      </message>
                    </sequence>
                    <else>
                      <message log="1" level="'Error'">
                        'Could not read changelog on replication server %s:%s' \
                        % (replServer.getHostname(), replServer.getPort())
                      </message>
                    </else>
                  </if>
                  <if expr="addedEntry and eclEntry">
                    <call function="'checkChangelogEntry'">
                      { 'location'        : masterHost,
                        'dsPath'          : masterPath,
                        'changelogEntry'  : eclEntry,
                        'targetDN'        : myTargetDN,
                        'changeType'      : myChangeType,
                        'changeTime'      : myChangeTime,
                        'targetEntryUUID' : myTargetEntryUUID,
                        'changes'         : myChanges
                      }
                    </call>
                    <else>
                      <sequence>
                        <message log="1" level="'Error'">
                          'Unable to check external changelog entry (failed to \
                          read entries %s and/or %s)' % \
                          (myEntry.getDn(), eclEntry['dn'][0])
                        </message>
                        <call function="'testFailed'"/>
                      </sequence>
                    </else>
                  </if>
                </sequence>
              </iterate>
              <script>
                # set the lastCookie for the next ECL search
                if nextCookie:
                  lastCookie = nextCookie
                # reset variables, just in case
                addedEntry = None
                eclEntry   = None
              </script>
              <call function="'testCase_Postamble'"/>
            </sequence>
          </testcase>
          <!--- Test Case information
          #@TestMarker          Replication External Changelog Tests
          #@TestName            Replication: External Changelog: Modify_add
          #@TestID              Modify_add
          #@TestPurpose         Verify modify-add operation is recorded properly
                                in the external changelog on each repl server
          #@TestPreamble
          #@TestSteps           Modify entry on server A
          #@TestSteps           Verify servers in sync
          #@TestSteps           Read entry from ldap server
          #@TestSteps           Read last changelog entry on each repl server
          #@TestSteps           Check changelog entry against expected values
          #@TestPostamble
          #@TestResult          Success if the server synchronised and the
                                changelog entry value check succeeds
          -->
          <testcase name="getTestCaseName('Modify_add')">
            <sequence>
              <call function="'testCase_Preamble'"/>
              <message>
                'Replication: External Changelog: Modify_add. \
                Verify modify-add operation is recorded properly in the \
                external changelog on each repl server'
              </message>
              <script>
                modEntry   = None
                eclEntry   = None
              </script>
              <!-- Modify entry on one of the servers -->
              <call function="'modifyAnAttribute'">
                { 'location'          : masterHost,
                  'dsPath'            : masterPath,
                  'dsInstanceHost'    : masterHost,
                  'dsInstancePort'    : master.getPort(),
                  'dsInstanceDn'      : master.getRootDn(),
                  'dsInstancePswd'    : master.getRootPwd(),
                  'DNToModify'        : myEntry.getDn(),
                  'attributeName'     : 'description',
                  'newAttributeValue' : '3',
                  'changetype'        : 'add'
                }
              </call>
              <!-- Verify the synchronization of the trees among the servers in
                the topology -->
              <call function="'verifyTrees'">
                [ clientHost, clientPath, master, consumerList, synchroSuffix ]
              </call>
              <!-- Retrieve entry information for checkChangelogEntry's sake -->
              <call function="'ldapSearchWithScript'">
                { 'location'       : masterHost,
                  'dsPath'         : masterPath,
                  'dsInstanceHost' : masterHost,
                  'dsInstancePort' : master.getPort(),
                  'dsInstanceDn'   : master.getRootDn(),
                  'dsInstancePswd' : master.getRootPwd(),
                  'dsBaseDN'       : myEntry.getDn(),
                  'dsFilter'       : 'objectclass=*',
                  'dsAttributes'   : modOperationalAttrs
                }
              </call>
              <script>
                searchRC = STAXResult[0][0]
                searchResult = STAXResult[0][1]
              </script>
              <if expr="searchRC == 0">
                <sequence>
                  <message>
                    'This is the entry as read from server %s:%s: \n%s' % \
                    (masterHost, master.getPort(), searchResult)
                  </message>
                  <call function="'parseLdifEntry'">
                    { 'ldifEntry' : searchResult }
                  </call>
                  <script>
                    modEntry = STAXResult
                    myTargetDN             = modEntry['dn'][0]
                    myChangeType           = 'modify'
                    myChangeTime           = modEntry['modifytimestamp'][0]
                    myTargetEntryUUID      = modEntry['entryuuid'][0]
                    # myReplicationCSN     = ----> grab from where???
                    # myReplicatIdentifier = ----> grab from ldap server
                    myModifiersName        = modEntry['modifiersname'][0]
                    myModifyTimestamp      = modEntry['modifytimestamp'][0]
                    myChanges              = []
                    myChanges.append(['add', 'description', '3'])
                    myChanges.append(['replace', 'modifiersname', myModifiersName])
                    myChanges.append(['replace', 'modifytimestamp', myModifyTimestamp])
                  </script>
                </sequence>
                <else>
                  <message log="1" level="'Error'">
                    'Could not read modified entry %s from server %s:%s' % \
                    (myEntry.getDn(), masterHost, master.getPort())
                  </message>
                </else>
              </if>
              <!-- Search changelog in the various replication servers -->
              <iterate var="server"
                       in="_topologyServerList"
                       indexvar="i">
                <sequence>
                  <script>
                    if globalSplitServers:
                      replServer = _topologyReplServerList[i]
                    else:
                      replServer = server
                    replServerPath = '%s/%s' % (replServer.getDir(), OPENDSNAME)
                  </script>
                  <message>
                    'Reading changelog in replication server %s:%s from index: \
                    %s' % \
                    (replServer.getHostname(), replServer.getPort(), lastCookie)
                  </message>
                  <!-- Search for entry modify -->
                  <call function="'SearchExternalChangelog'">
                    { 'location'        : replServer.getHostname(),
                      'dsPath'          : replServerPath,
                      'dsInstanceHost'  : replServer.getHostname(),
                      'dsInstancePort'  : replServer.getPort(),
                      'dsInstanceDn'    : replServer.getRootDn(),
                      'dsInstancePswd'  : replServer.getRootPwd(),
                      'dsScope'         : 'subordinate',
                      'dsBaseDN'        : 'cn=changelog',
                      'dsFilter'        : '(objectclass=*)',
                      'changelogCookie' : lastCookie
                    }
                  </call>
                  <script>
                    searchRC = STAXResult[0][0]
                    searchResult = STAXResult[0][1]
                  </script>
                  <if expr="searchRC == 0">
                    <sequence>
                      <message>
                        'This is the ECL entry as read from server %s:%s: \n%s'\
                         % (replServer.getHostname(), replServer.getPort(),
                            searchResult)
                      </message>
                      <call function="'parseLdifEntry'">
                        { 'ldifEntry' : searchResult }
                      </call>
                      <script>
                        eclEntry = STAXResult
                        nextCookie = eclEntry['changelogcookie'][0]
                      </script>
                      <message>
                        'Parsed changelog entry:  \n%s' % eclEntry
                      </message>
                      <message>
                        'Cookie to use for the next ECL search:  %s' % \
                        nextCookie
                      </message>
                    </sequence>
                    <else>
                      <message log="1" level="'Error'">
                        'Could not read changelog on replication server %s:%s' \
                        % (replServer.getHostname(), replServer.getPort())
                      </message>
                    </else>
                  </if>
                  <if expr="modEntry and eclEntry">
                    <call function="'checkChangelogEntry'">
                      { 'location'        : masterHost,
                        'dsPath'          : masterPath,
                        'changelogEntry'  : eclEntry,
                        'targetDN'        : myTargetDN,
                        'changeType'      : myChangeType,
                        'changeTime'      : myChangeTime,
                        'targetEntryUUID' : myTargetEntryUUID,
                        'changes'         : myChanges
                      }
                    </call>
                    <else>
                      <sequence>
                        <message log="1" level="'Error'">
                          'Unable to check external changelog entry (failed to \
                          read entries %s and/or %s)' % \
                          (myEntry.getDn(), eclEntry['dn'][0])
                        </message>
                        <call function="'testFailed'"/>
                      </sequence>
                    </else>
                  </if>
                </sequence>
              </iterate>
              <script>
                # set the lastCookie for the next ECL search
                if nextCookie:
                  lastCookie = nextCookie
                # reset variables, just in case
                modEntry  = None
                eclEntry  = None
              </script>
              <call function="'testCase_Postamble'"/>
            </sequence>
          </testcase>
          <!--- Test Case information
          #@TestMarker          Replication External Changelog Tests
          #@TestName            Replication: External Changelog: Modify_delete
          #@TestID              Modify_delete
          #@TestPurpose         Verify modify-del operation is recorded properly
                                in the external changelog on each repl server
          #@TestPreamble
          #@TestSteps           Modify entry on server A
          #@TestSteps           Verify servers in sync
          #@TestSteps           Read entry from ldap server
          #@TestSteps           Read last changelog entry on each repl server
          #@TestSteps           Check changelog entry against expected values
          #@TestPostamble
          #@TestResult          Success if the server synchronised and the
                                changelog entry value check succeeds
          -->
          <testcase name="getTestCaseName('Modify_delete')">
            <sequence>
              <call function="'testCase_Preamble'"/>
              <message>
                'Replication: External Changelog: Modify_delete. \
                Verify modify-del operation is recorded properly in the \
                external changelog on each repl server'
              </message>
              <script>
                modEntry   = None
                eclEntry   = None
                valuesToDelete = []
                valuesToDelete.append('description:1')
              </script>
              <!-- Modify entry on one of the servers -->
              <call function="'modifyAnAttribute'">
                { 'location'          : masterHost,
                  'dsPath'            : masterPath,
                  'dsInstanceHost'    : masterHost,
                  'dsInstancePort'    : master.getPort(),
                  'dsInstanceDn'      : master.getRootDn(),
                  'dsInstancePswd'    : master.getRootPwd(),
                  'DNToModify'        : myEntry.getDn(),
                  'listAttributes'    : valuesToDelete,
                  'changetype'        : 'delete'
                }
              </call>
              <!-- Verify the synchronization of the trees among the servers in
                the topology -->
              <call function="'verifyTrees'">
                [ clientHost, clientPath, master, consumerList, synchroSuffix ]
              </call>
              <!-- Retrieve entry information for checkChangelogEntry's sake -->
              <call function="'ldapSearchWithScript'">
                { 'location'       : masterHost,
                  'dsPath'         : masterPath,
                  'dsInstanceHost' : masterHost,
                  'dsInstancePort' : master.getPort(),
                  'dsInstanceDn'   : master.getRootDn(),
                  'dsInstancePswd' : master.getRootPwd(),
                  'dsBaseDN'       : myEntry.getDn(),
                  'dsFilter'       : 'objectclass=*',
                  'dsAttributes'   : modOperationalAttrs
                }
              </call>
              <script>
                searchRC = STAXResult[0][0]
                searchResult = STAXResult[0][1]
              </script>
              <if expr="searchRC == 0">
                <sequence>
                  <message>
                    'This is the entry as read from server %s:%s: \n%s' % \
                    (masterHost, master.getPort(), searchResult)
                  </message>
                  <call function="'parseLdifEntry'">
                    { 'ldifEntry' : searchResult }
                  </call>
                  <script>
                    modEntry = STAXResult
                    myTargetDN             = modEntry['dn'][0]
                    myChangeType           = 'modify'
                    myChangeTime           = modEntry['modifytimestamp'][0]
                    myTargetEntryUUID      = modEntry['entryuuid'][0]
                    # myReplicationCSN     = ----> grab from where???
                    # myReplicatIdentifier = ----> grab from ldap server
                    myModifiersName        = modEntry['modifiersname'][0]
                    myModifyTimestamp      = modEntry['modifytimestamp'][0]
                    myChanges              = []
                    myChanges.append(['delete', 'description', '1'])
                    myChanges.append(['replace', 'modifiersname', myModifiersName])
                    myChanges.append(['replace', 'modifytimestamp', myModifyTimestamp])
                  </script>
                </sequence>
                <else>
                  <message log="1" level="'Error'">
                    'Could not read modified entry %s from server %s:%s' % \
                    (myEntry.getDn(), masterHost, master.getPort())
                  </message>
                </else>
              </if>
              <!-- Search changelog in the various replication servers -->
              <iterate var="server"
                       in="_topologyServerList"
                       indexvar="i">
                <sequence>
                  <script>
                    if globalSplitServers:
                      replServer = _topologyReplServerList[i]
                    else:
                      replServer = server
                    replServerPath = '%s/%s' % (replServer.getDir(), OPENDSNAME)
                  </script>
                  <message>
                    'Reading changelog in replication server %s:%s from index: \
                    %s' % \
                    (replServer.getHostname(), replServer.getPort(), lastCookie)
                  </message>
                  <!-- Search for entry modify -->
                  <call function="'SearchExternalChangelog'">
                    { 'location'        : replServer.getHostname(),
                      'dsPath'          : replServerPath,
                      'dsInstanceHost'  : replServer.getHostname(),
                      'dsInstancePort'  : replServer.getPort(),
                      'dsInstanceDn'    : replServer.getRootDn(),
                      'dsInstancePswd'  : replServer.getRootPwd(),
                      'dsScope'         : 'subordinate',
                      'dsBaseDN'        : 'cn=changelog',
                      'dsFilter'        : '(objectclass=*)',
                      'changelogCookie' : lastCookie
                    }
                  </call>
                  <script>
                    searchRC = STAXResult[0][0]
                    searchResult = STAXResult[0][1]
                  </script>
                  <if expr="searchRC == 0">
                    <sequence>
                      <message>
                        'This is the ECL entry as read from server %s:%s: \n%s'\
                         % (replServer.getHostname(), replServer.getPort(),
                            searchResult)
                      </message>
                      <call function="'parseLdifEntry'">
                        { 'ldifEntry' : searchResult }
                      </call>
                      <script>
                        eclEntry = STAXResult
                        nextCookie = eclEntry['changelogcookie'][0]
                      </script>
                      <message>
                        'Parsed changelog entry:  \n%s' % eclEntry
                      </message>
                      <message>
                        'Cookie to use for the next ECL search:  %s' % \
                        nextCookie
                      </message>
                    </sequence>
                    <else>
                      <message log="1" level="'Error'">
                        'Could not read changelog on replication server %s:%s' \
                        % (replServer.getHostname(), replServer.getPort())
                      </message>
                    </else>
                  </if>
                  <if expr="modEntry and eclEntry">
                    <call function="'checkChangelogEntry'">
                      { 'location'        : masterHost,
                        'dsPath'          : masterPath,
                        'changelogEntry'  : eclEntry,
                        'targetDN'        : myTargetDN,
                        'changeType'      : myChangeType,
                        'changeTime'      : myChangeTime,
                        'targetEntryUUID' : myTargetEntryUUID,
                        'changes'         : myChanges
                      }
                    </call>
                    <else>
                      <sequence>
                        <message log="1" level="'Error'">
                          'Unable to check external changelog entry (failed to \
                          read entries %s and/or %s)' % \
                          (myEntry.getDn(), eclEntry['dn'][0])
                        </message>
                        <call function="'testFailed'"/>
                      </sequence>
                    </else>
                  </if>
                </sequence>
              </iterate>
              <script>
                # set the lastCookie for the next ECL search
                if nextCookie:
                  lastCookie = nextCookie
                # reset variables, just in case
                modEntry  = None
                eclEntry  = None
              </script>
              <call function="'testCase_Postamble'"/>
            </sequence>
          </testcase>
          <!--- Test Case information
          #@TestMarker          Replication External Changelog Tests
          #@TestName            Replication: External Changelog: Modify_replace
          #@TestID              Modify_replace
          #@TestPurpose         Verify modify-replace operation is well recorded
                                in the external changelog on each repl server
          #@TestPreamble
          #@TestSteps           Modify entry on server A
          #@TestSteps           Verify servers in sync
          #@TestSteps           Read entry from ldap server
          #@TestSteps           Read last changelog entry on each repl server
          #@TestSteps           Check changelog entry against expected values
          #@TestPostamble
          #@TestResult          Success if the server synchronised and the
                                changelog entry value check succeeds
          -->
          <testcase name="getTestCaseName('Modify_replace')">
            <sequence>
              <call function="'testCase_Preamble'"/>
              <message>
                'Replication: External Changelog: Modify_replace. \
                Verify modify-replace operation is recorded properly in the \
                external changelog on each repl server'
              </message>
              <script>
                modEntry   = None
                eclEntry   = None
              </script>
              <!-- Modify entry on one of the servers -->
              <call function="'modifyAnAttribute'">
                { 'location'          : masterHost,
                  'dsPath'            : masterPath,
                  'dsInstanceHost'    : masterHost,
                  'dsInstancePort'    : master.getPort(),
                  'dsInstanceDn'      : master.getRootDn(),
                  'dsInstancePswd'    : master.getRootPwd(),
                  'DNToModify'        : myEntry.getDn(),
                  'attributeName'     : 'description',
                  'newAttributeValue' : 'new',
                  'changetype'        : 'replace'
                }
              </call>
              <!-- Verify the synchronization of the trees among the servers in
                the topology -->
              <call function="'verifyTrees'">
                [ clientHost, clientPath, master, consumerList, synchroSuffix ]
              </call>
              <!-- Retrieve entry information for checkChangelogEntry's sake -->
              <call function="'ldapSearchWithScript'">
                { 'location'       : masterHost,
                  'dsPath'         : masterPath,
                  'dsInstanceHost' : masterHost,
                  'dsInstancePort' : master.getPort(),
                  'dsInstanceDn'   : master.getRootDn(),
                  'dsInstancePswd' : master.getRootPwd(),
                  'dsBaseDN'       : myEntry.getDn(),
                  'dsFilter'       : 'objectclass=*',
                  'dsAttributes'   : modOperationalAttrs
                }
              </call>
              <script>
                searchRC = STAXResult[0][0]
                searchResult = STAXResult[0][1]
              </script>
              <if expr="searchRC == 0">
                <sequence>
                  <message>
                    'This is the entry as read from server %s:%s: \n%s' % \
                    (masterHost, master.getPort(), searchResult)
                  </message>
                  <call function="'parseLdifEntry'">
                    { 'ldifEntry' : searchResult }
                  </call>
                  <script>
                    modEntry = STAXResult
                    myTargetDN             = modEntry['dn'][0]
                    myChangeType           = 'modify'
                    myChangeTime           = modEntry['modifytimestamp'][0]
                    myTargetEntryUUID      = modEntry['entryuuid'][0]
                    # myReplicationCSN     = ----> grab from where???
                    # myReplicatIdentifier = ----> grab from ldap server
                    myModifiersName        = modEntry['modifiersname'][0]
                    myModifyTimestamp      = modEntry['modifytimestamp'][0]
                    myChanges              = []
                    myChanges.append(['replace', 'description', 'new'])
                    myChanges.append(['replace', 'modifiersname', myModifiersName])
                    myChanges.append(['replace', 'modifytimestamp', myModifyTimestamp])
                  </script>
                </sequence>
                <else>
                  <message log="1" level="'Error'">
                    'Could not read modified entry %s from server %s:%s' % \
                    (myEntry.getDn(), masterHost, master.getPort())
                  </message>
                </else>
              </if>
              <!-- Search changelog in the various replication servers -->
              <iterate var="server"
                       in="_topologyServerList"
                       indexvar="i">
                <sequence>
                  <script>
                    if globalSplitServers:
                      replServer = _topologyReplServerList[i]
                    else:
                      replServer = server
                    replServerPath = '%s/%s' % (replServer.getDir(), OPENDSNAME)
                  </script>
                  <message>
                    'Reading changelog in replication server %s:%s from index: \
                    %s' % \
                    (replServer.getHostname(), replServer.getPort(), lastCookie)
                  </message>
                  <!-- Search for entry modify -->
                  <call function="'SearchExternalChangelog'">
                    { 'location'        : replServer.getHostname(),
                      'dsPath'          : replServerPath,
                      'dsInstanceHost'  : replServer.getHostname(),
                      'dsInstancePort'  : replServer.getPort(),
                      'dsInstanceDn'    : replServer.getRootDn(),
                      'dsInstancePswd'  : replServer.getRootPwd(),
                      'dsScope'         : 'subordinate',
                      'dsBaseDN'        : 'cn=changelog',
                      'dsFilter'        : '(objectclass=*)',
                      'changelogCookie' : lastCookie
                    }
                  </call>
                  <script>
                    searchRC = STAXResult[0][0]
                    searchResult = STAXResult[0][1]
                  </script>
                  <if expr="searchRC == 0">
                    <sequence>
                      <message>
                        'This is the ECL entry as read from server %s:%s: \n%s'\
                         % (replServer.getHostname(), replServer.getPort(),
                            searchResult)
                      </message>
                      <call function="'parseLdifEntry'">
                        { 'ldifEntry' : searchResult }
                      </call>
                      <script>
                        eclEntry = STAXResult
                        nextCookie = eclEntry['changelogcookie'][0]
                      </script>
                      <message>
                        'Parsed changelog entry:  \n%s' % eclEntry
                      </message>
                      <message>
                        'Cookie to use for the next ECL search:  %s' % \
                        nextCookie
                      </message>
                    </sequence>
                    <else>
                      <message log="1" level="'Error'">
                        'Could not read changelog on replication server %s:%s' \
                        % (replServer.getHostname(), replServer.getPort())
                      </message>
                    </else>
                  </if>
                  <if expr="modEntry and eclEntry">
                    <call function="'checkChangelogEntry'">
                      { 'location'        : masterHost,
                        'dsPath'          : masterPath,
                        'changelogEntry'  : eclEntry,
                        'targetDN'        : myTargetDN,
                        'changeType'      : myChangeType,
                        'changeTime'      : myChangeTime,
                        'targetEntryUUID' : myTargetEntryUUID,
                        'changes'         : myChanges
                      }
                    </call>
                    <else>
                      <sequence>
                        <message log="1" level="'Error'">
                          'Unable to check external changelog entry (failed to \
                          read entries %s and/or %s)' % \
                          (myEntry.getDn(), eclEntry['dn'][0])
                        </message>
                        <call function="'testFailed'"/>
                      </sequence>
                    </else>
                  </if>
                </sequence>
              </iterate>
              <script>
                # set the lastCookie for the next ECL search
                if nextCookie:
                  lastCookie = nextCookie
                # reset variables, just in case
                modEntry  = None
                eclEntry  = None
              </script>
              <call function="'testCase_Postamble'"/>
            </sequence>
          </testcase>
          <!--- Test Case information
          #@TestMarker          Replication External Changelog Tests
          #@TestName            Replication: External Changelog: Modrdn_deleteoldrdn_true
          #@TestID              Modrdn_deleteoldrdn_true
          #@TestPurpose         Verify modrdn operation is recorded properly
                                in the external changelog on each repl server
          #@TestPreamble
          #@TestSteps           Modrdn entry on server A
          #@TestSteps           Verify servers in sync
          #@TestSteps           Read entry from ldap server
          #@TestSteps           Read last changelog entry on each repl server
          #@TestSteps           Check changelog entry against expected values
          #@TestPostamble
          #@TestResult          Success if the server synchronised and the
                                changelog entry value check succeeds
          -->
          <testcase name="getTestCaseName('Modrdn_deleteoldrdn_true')">
            <sequence>
              <call function="'testCase_Preamble'"/>
              <message>
                'Replication: External Changelog: Modrdn_deleteoldrdn_true. \
                Verify modrdn operation is recorded properly in the \
                external changelog on each repl server'
              </message>
              <script>
                modEntry = None
                eclEntry = None
                oldEntry = myEntry
                newRdn   = 'cn=b'
                newEntry = Entry(newRdn)
              </script>
              <!-- Modify entry on one of the servers -->
              <call function="'modifyDn'">
                { 'location'       : masterHost,
                  'dsPath'         : masterPath,
                  'dsInstanceHost' : masterHost,
                  'dsInstancePort' : master.getPort(),
                  'dsInstanceDn'   : master.getRootDn(),
                  'dsInstancePswd' : master.getRootPwd(),
                  'DNToModify'     : oldEntry.getDn(),
                  'newRDN'         : newRdn
                }
              </call>
              <!-- Verify the synchronization of the trees among the servers in
                the topology -->
              <call function="'verifyTrees'">
                [ clientHost, clientPath, master, consumerList, synchroSuffix ]
              </call>
              <!-- Retrieve entry information for checkChangelogEntry's sake -->
              <call function="'ldapSearchWithScript'">
                { 'location'       : masterHost,
                  'dsPath'         : masterPath,
                  'dsInstanceHost' : masterHost,
                  'dsInstancePort' : master.getPort(),
                  'dsInstanceDn'   : master.getRootDn(),
                  'dsInstancePswd' : master.getRootPwd(),
                  'dsBaseDN'       : newEntry.getDn(),
                  'dsFilter'       : 'objectclass=*',
                  'dsAttributes'   : modOperationalAttrs
                }
              </call>
              <script>
                searchRC = STAXResult[0][0]
                searchResult = STAXResult[0][1]
              </script>
              <if expr="searchRC == 0">
                <sequence>
                  <message>
                    'This is the entry as read from server %s:%s: \n%s' % \
                    (masterHost, master.getPort(), searchResult)
                  </message>
                  <call function="'parseLdifEntry'">
                    { 'ldifEntry' : searchResult }
                  </call>
                  <script>
                    modEntry = STAXResult
                    myTargetDN             = oldEntry.getDn()
                    myChangeType           = 'modrdn'
                    myChangeTime           = modEntry['modifytimestamp'][0]
                    myTargetEntryUUID      = modEntry['entryuuid'][0]
                    # myReplicationCSN     = ----> grab from where???
                    # myReplicatIdentifier = ----> grab from ldap server
                    myNewRDN               = newRdn
                    myDeleteOldRDN         = 'true'
                  </script>
                </sequence>
                <else>
                  <message log="1" level="'Error'">
                    'Could not read added entry %s from server %s:%s' % \
                    (newEntry.getDn(), masterHost, master.getPort())
                  </message>
                </else>
              </if>
              <!-- Search changelog in the various replication servers -->
              <iterate var="server"
                       in="_topologyServerList"
                       indexvar="i">
                <sequence>
                  <script>
                    if globalSplitServers:
                      replServer = _topologyReplServerList[i]
                    else:
                      replServer = server
                    replServerPath = '%s/%s' % (replServer.getDir(), OPENDSNAME)
                  </script>
                  <message>
                    'Reading changelog in replication server %s:%s from index: \
                    %s' % \
                    (replServer.getHostname(), replServer.getPort(), lastCookie)
                  </message>
                  <!-- Search for entry modify -->
                  <call function="'SearchExternalChangelog'">
                    { 'location'        : replServer.getHostname(),
                      'dsPath'          : replServerPath,
                      'dsInstanceHost'  : replServer.getHostname(),
                      'dsInstancePort'  : replServer.getPort(),
                      'dsInstanceDn'    : replServer.getRootDn(),
                      'dsInstancePswd'  : replServer.getRootPwd(),
                      'dsScope'         : 'subordinate',
                      'dsBaseDN'        : 'cn=changelog',
                      'dsFilter'        : '(objectclass=*)',
                      'changelogCookie' : lastCookie
                    }
                  </call>
                  <script>
                    searchRC = STAXResult[0][0]
                    searchResult = STAXResult[0][1]
                  </script>
                  <if expr="searchRC == 0">
                    <sequence>
                      <message>
                        'This is the ECL entry as read from server %s:%s: \n%s'\
                         % (replServer.getHostname(), replServer.getPort(),
                            searchResult)
                      </message>
                      <call function="'parseLdifEntry'">
                        { 'ldifEntry' : searchResult }
                      </call>
                      <script>
                        eclEntry = STAXResult
                        nextCookie = eclEntry['changelogcookie'][0]
                      </script>
                      <message>
                        'Parsed changelog entry:  \n%s' % eclEntry
                      </message>
                      <message>
                        'Cookie to use for the next ECL search:  %s' % \
                        nextCookie
                      </message>
                    </sequence>
                    <else>
                      <message log="1" level="'Error'">
                        'Could not read changelog on replication server %s:%s' \
                        % (replServer.getHostname(), replServer.getPort())
                      </message>
                    </else>
                  </if>
                  <if expr="modEntry and eclEntry">
                    <call function="'checkChangelogEntry'">
                      { 'location'        : masterHost,
                        'dsPath'          : masterPath,
                        'changelogEntry'  : eclEntry,
                        'targetDN'        : myTargetDN,
                        'changeType'      : myChangeType,
                        'changeTime'      : myChangeTime,
                        'targetEntryUUID' : myTargetEntryUUID,
                        'newRDN'          : myNewRDN,
                        'deleteOldRDN'    : myDeleteOldRDN
                      }
                    </call>
                    <else>
                      <sequence>
                        <message log="1" level="'Error'">
                          'Unable to check external changelog entry (failed to \
                          read entries %s and/or %s)' % \
                          (newEntry.getDn(), eclEntry['dn'][0])
                        </message>
                        <call function="'testFailed'"/>
                      </sequence>
                    </else>
                  </if>
                </sequence>
              </iterate>
              <script>
                # set the lastCookie for the next ECL search
                if nextCookie:
                  lastCookie = nextCookie
                # reset variables, just in case
                modEntry  = None
                eclEntry  = None
              </script>
              <call function="'testCase_Postamble'"/>
            </sequence>
          </testcase>
          <!--- Test Case information
          #@TestMarker          Replication External Changelog Tests
          #@TestName            Replication: External Changelog: Modrdn_deleteoldrdn_false
          #@TestID              Modrdn_deleteoldrdn_false
          #@TestPurpose         Verify modrdn operation is recorded properly
                                in the external changelog on each repl server
          #@TestPreamble
          #@TestSteps           Modrdn entry on server A
          #@TestSteps           Verify servers in sync
          #@TestSteps           Read entry from ldap server
          #@TestSteps           Read last changelog entry on each repl server
          #@TestSteps           Check changelog entry against expected values
          #@TestPostamble
          #@TestResult          Success if the server synchronised and the
                                changelog entry value check succeeds
          -->
          <testcase name="getTestCaseName('Modrdn_deleteoldrdn_false')">
            <sequence>
              <call function="'testCase_Preamble'"/>
              <message>
                'Replication: External Changelog: Modrdn_deleteoldrdn_false. \
                Verify modrdn operation is recorded properly in the \
                external changelog on each repl server'
              </message>
              <script>
                modEntry = None
                eclEntry = None
                oldEntry = newEntry
                newRdn   = 'cn=c'
                newEntry = Entry(newRdn)
              </script>
              <!-- Modify entry on one of the servers -->
              <call function="'modifyDn'">
                { 'location'       : masterHost,
                  'dsPath'         : masterPath,
                  'dsInstanceHost' : masterHost,
                  'dsInstancePort' : master.getPort(),
                  'dsInstanceDn'   : master.getRootDn(),
                  'dsInstancePswd' : master.getRootPwd(),
                  'DNToModify'     : oldEntry.getDn(),
                  'newRDN'         : newRdn,
                  'deleteOldRDN'   : 0
                }
              </call>
              <!-- Verify the synchronization of the trees among the servers in
                the topology -->
              <call function="'verifyTrees'">
                [ clientHost, clientPath, master, consumerList, synchroSuffix ]
              </call>
              <!-- Retrieve entry information for checkChangelogEntry's sake -->
              <call function="'ldapSearchWithScript'">
                { 'location'       : masterHost,
                  'dsPath'         : masterPath,
                  'dsInstanceHost' : masterHost,
                  'dsInstancePort' : master.getPort(),
                  'dsInstanceDn'   : master.getRootDn(),
                  'dsInstancePswd' : master.getRootPwd(),
                  'dsBaseDN'       : newEntry.getDn(),
                  'dsFilter'       : 'objectclass=*',
                  'dsAttributes'   : modOperationalAttrs
                }
              </call>
              <script>
                searchRC = STAXResult[0][0]
                searchResult = STAXResult[0][1]
              </script>
              <if expr="searchRC == 0">
                <sequence>
                  <message>
                    'This is the entry as read from server %s:%s: \n%s' % \
                    (masterHost, master.getPort(), searchResult)
                  </message>
                  <call function="'parseLdifEntry'">
                    { 'ldifEntry' : searchResult }
                  </call>
                  <script>
                    modEntry = STAXResult
                    myTargetDN             = oldEntry.getDn()
                    myChangeType           = 'modrdn'
                    myChangeTime           = modEntry['modifytimestamp'][0]
                    myTargetEntryUUID      = modEntry['entryuuid'][0]
                    # myReplicationCSN     = ----> grab from where???
                    # myReplicatIdentifier = ----> grab from ldap server
                    myNewRDN               = newRdn
                    myDeleteOldRDN         = 'false'
                  </script>
                </sequence>
                <else>
                  <message log="1" level="'Error'">
                    'Could not read added entry %s from server %s:%s' % \
                    (newEntry.getDn(), masterHost, master.getPort())
                  </message>
                </else>
              </if>
              <!-- Search changelog in the various replication servers -->
              <iterate var="server"
                       in="_topologyServerList"
                       indexvar="i">
                <sequence>
                  <script>
                    if globalSplitServers:
                      replServer = _topologyReplServerList[i]
                    else:
                      replServer = server
                    replServerPath = '%s/%s' % (replServer.getDir(), OPENDSNAME)
                  </script>
                  <message>
                    'Reading changelog in replication server %s:%s from index: \
                    %s' % \
                    (replServer.getHostname(), replServer.getPort(), lastCookie)
                  </message>
                  <!-- Search for entry modify -->
                  <call function="'SearchExternalChangelog'">
                    { 'location'        : replServer.getHostname(),
                      'dsPath'          : replServerPath,
                      'dsInstanceHost'  : replServer.getHostname(),
                      'dsInstancePort'  : replServer.getPort(),
                      'dsInstanceDn'    : replServer.getRootDn(),
                      'dsInstancePswd'  : replServer.getRootPwd(),
                      'dsScope'         : 'subordinate',
                      'dsBaseDN'        : 'cn=changelog',
                      'dsFilter'        : '(objectclass=*)',
                      'changelogCookie' : lastCookie
                    }
                  </call>
                  <script>
                    searchRC = STAXResult[0][0]
                    searchResult = STAXResult[0][1]
                  </script>
                  <if expr="searchRC == 0">
                    <sequence>
                      <message>
                        'This is the ECL entry as read from server %s:%s: \n%s'\
                         % (replServer.getHostname(), replServer.getPort(),
                            searchResult)
                      </message>
                      <call function="'parseLdifEntry'">
                        { 'ldifEntry' : searchResult }
                      </call>
                      <script>
                        eclEntry = STAXResult
                        nextCookie = eclEntry['changelogcookie'][0]
                      </script>
                      <message>
                        'Parsed changelog entry:  \n%s' % eclEntry
                      </message>
                      <message>
                        'Cookie to use for the next ECL search:  %s' % \
                        nextCookie
                      </message>
                    </sequence>
                    <else>
                      <message log="1" level="'Error'">
                        'Could not read changelog on replication server %s:%s' \
                        % (replServer.getHostname(), replServer.getPort())
                      </message>
                    </else>
                  </if>
                  <if expr="modEntry and eclEntry">
                    <call function="'checkChangelogEntry'">
                      { 'location'        : masterHost,
                        'dsPath'          : masterPath,
                        'changelogEntry'  : eclEntry,
                        'targetDN'        : myTargetDN,
                        'changeType'      : myChangeType,
                        'changeTime'      : myChangeTime,
                        'targetEntryUUID' : myTargetEntryUUID,
                        'newRDN'          : myNewRDN,
                        'deleteOldRDN'    : myDeleteOldRDN
                      }
                    </call>
                    <else>
                      <sequence>
                        <message log="1" level="'Error'">
                          'Unable to check external changelog entry (failed to \
                          read entries %s and/or %s)' % \
                          (newEntry.getDn(), eclEntry['dn'][0])
                        </message>
                        <call function="'testFailed'"/>
                      </sequence>
                    </else>
                  </if>
                </sequence>
              </iterate>
              <script>
                # set the lastCookie for the next ECL search
                if nextCookie:
                  lastCookie = nextCookie
                # reset variables, just in case
                modEntry  = None
                eclEntry  = None
              </script>
              <call function="'testCase_Postamble'"/>
            </sequence>
          </testcase>
          <!--- Test Case information
          #@TestMarker          Replication External Changelog Tests
          #@TestName            Replication: External Changelog: Delete_1
          #@TestID              Delete_1
          #@TestPurpose         Verify delete operation is recorded properly
                                in the external changelog on each repl server
          #@TestPreamble
          #@TestSteps           Delete entry on server A
          #@TestSteps           Verify servers in sync
          #@TestSteps           Read entry from ldap server
          #@TestSteps           Read last changelog entry on each repl server
          #@TestSteps           Check changelog entry against expected values
          #@TestPostamble
          #@TestResult          Success if the server synchronised and the
                                changelog entry value check succeeds
          -->
          <testcase name="getTestCaseName('Delete_1')">
            <sequence>
              <call function="'testCase_Preamble'"/>
              <message>
                'Replication: External Changelog: Delete_1. \
                Verify delete operation is recorded properly in the \
                external changelog on each repl server'
              </message>
              <script>
                delEntry = None
                eclEntry = None
              </script>
              <!-- Retrieve entry information for checkChangelogEntry's sake -->
              <call function="'ldapSearchWithScript'">
                { 'location'       : masterHost,
                  'dsPath'         : masterPath,
                  'dsInstanceHost' : masterHost,
                  'dsInstancePort' : master.getPort(),
                  'dsInstanceDn'   : master.getRootDn(),
                  'dsInstancePswd' : master.getRootPwd(),
                  'dsBaseDN'       : newEntry.getDn(),
                  'dsFilter'       : 'objectclass=*',
                  'dsAttributes'   : modOperationalAttrs
                }
              </call>
              <script>
                searchRC = STAXResult[0][0]
                searchResult = STAXResult[0][1]
              </script>
              <if expr="searchRC == 0">
                <sequence>
                  <message>
                    'This is the entry as read from server %s:%s: \n%s' % \
                    (masterHost, master.getPort(), searchResult)
                  </message>
                  <call function="'parseLdifEntry'">
                    { 'ldifEntry' : searchResult }
                  </call>
                  <script>
                    delEntry = STAXResult
                    myTargetDN             = delEntry['dn'][0]
                    myChangeType           = 'delete'
                    myTargetEntryUUID      = delEntry['entryuuid'][0]
                    # myReplicationCSN     = ----> grab from where???
                    # myReplicatIdentifier = ----> grab from ldap server
                  </script>
                </sequence>
                <else>
                  <message log="1" level="'Error'">
                    'Could not read added entry %s from server %s:%s' % \
                    (newEntry.getDn(), masterHost, master.getPort())
                  </message>
                </else>
              </if>
              <!-- Delete entry on one of the servers -->
              <call function="'DeleteEntry'">
                { 'location'       : masterHost,
                  'dsPath'         : masterPath,
                  'dsInstanceHost' : masterHost,
                  'dsInstancePort' : master.getPort(),
                  'dsInstanceDn'   : master.getRootDn(),
                  'dsInstancePswd' : master.getRootPwd(),
                  'dsBaseDN'       : newEntry.getDn()
                }
              </call>
              <!-- Check delete worked on "master" server -->
              <call function="'ldapSearchWithScript'">
                { 'location'       : masterHost,
                  'dsPath'         : masterPath,
                  'dsInstanceHost' : masterHost,
                  'dsInstancePort' : master.getPort(),
                  'dsInstanceDn'   : master.getRootDn(),
                  'dsInstancePswd' : master.getRootPwd(),
                  'dsBaseDN'       : newEntry.getDn(),
                  'dsFilter'       : 'objectclass=*',
                  'expectedRC'     : 32
                }
              </call>
              <!-- Verify the synchronization of the trees among the servers in
                the topology -->
              <call function="'verifyTrees'">
                [ clientHost, clientPath, master, consumerList, synchroSuffix ]
              </call>
              <!-- Search changelog in the various replication servers -->
              <iterate var="server"
                       in="_topologyServerList"
                       indexvar="i">
                <sequence>
                  <script>
                    if globalSplitServers:
                      replServer = _topologyReplServerList[i]
                    else:
                      replServer = server
                    replServerPath = '%s/%s' % (replServer.getDir(), OPENDSNAME)
                  </script>
                  <message>
                    'Reading changelog in replication server %s:%s from index: \
                    %s' % \
                    (replServer.getHostname(), replServer.getPort(), lastCookie)
                  </message>
                  <!-- Search for entry modify -->
                  <call function="'SearchExternalChangelog'">
                    { 'location'        : replServer.getHostname(),
                      'dsPath'          : replServerPath,
                      'dsInstanceHost'  : replServer.getHostname(),
                      'dsInstancePort'  : replServer.getPort(),
                      'dsInstanceDn'    : replServer.getRootDn(),
                      'dsInstancePswd'  : replServer.getRootPwd(),
                      'dsScope'         : 'subordinate',
                      'dsBaseDN'        : 'cn=changelog',
                      'dsFilter'        : '(objectclass=*)',
                      'changelogCookie' : lastCookie
                    }
                  </call>
                  <script>
                    searchRC = STAXResult[0][0]
                    searchResult = STAXResult[0][1]
                  </script>
                  <if expr="searchRC == 0">
                    <sequence>
                      <message>
                        'This is the ECL entry as read from server %s:%s: \n%s'\
                         % (replServer.getHostname(), replServer.getPort(),
                            searchResult)
                      </message>
                      <call function="'parseLdifEntry'">
                        { 'ldifEntry' : searchResult }
                      </call>
                      <script>
                        eclEntry = STAXResult
                        nextCookie = eclEntry['changelogcookie'][0]
                      </script>
                      <message>
                        'Parsed changelog entry:  \n%s' % eclEntry
                      </message>
                      <message>
                        'Cookie to use for the next ECL search:  %s' % \
                        nextCookie
                      </message>
                    </sequence>
                    <else>
                      <message log="1" level="'Error'">
                        'Could not read changelog on replication server %s:%s' \
                        % (replServer.getHostname(), replServer.getPort())
                      </message>
                    </else>
                  </if>
                  <if expr="delEntry and eclEntry">
                    <call function="'checkChangelogEntry'">
                      { 'location'        : masterHost,
                        'dsPath'          : masterPath,
                        'changelogEntry'  : eclEntry,
                        'targetDN'        : myTargetDN,
                        'changeType'      : myChangeType,
                        'targetEntryUUID' : myTargetEntryUUID
                      }
                    </call>
                    <else>
                      <sequence>
                        <message log="1" level="'Error'">
                          'Unable to check external changelog entry (failed to \
                          read entries %s and/or %s)' % \
                          (newEntry.getDn(), eclEntry['dn'][0])
                        </message>
                        <call function="'testFailed'"/>
                      </sequence>
                    </else>
                  </if>
                </sequence>
              </iterate>
              <script>
                # set the lastCookie for the next ECL search
                if nextCookie:
                  lastCookie = nextCookie
                # reset variables, just in case
                delEntry  = None
                eclEntry  = None
              </script>
              <call function="'testCase_Postamble'"/>
            </sequence>
          </testcase>
          <!--- Test Case information
          #@TestMarker          Replication External Changelog Tests
          #@TestName            Replication: External Changelog: Add_2
          #@TestID              Add_2
          #@TestPurpose         Verify add operation (of an entry with
                                userpassword) is recorded properly in the
                                external changelog on each replication server
          #@TestPreamble
          #@TestSteps           Add entry to server A (with userpassword)
          #@TestSteps           Verify servers in sync
          #@TestSteps           Read entry from ldap server
          #@TestSteps           Read last changelog entry on each repl server
          #@TestSteps           Check changelog entry against expected values
          #@TestPostamble
          #@TestResult          Success if the server synchronised and the
                                changelog entry value check succeeds
          -->
          <testcase name="getTestCaseName('Add_2')">
            <sequence>
              <call function="'testCase_Preamble'"/>
              <message>
                'Replication: External Changelog: Add_2. \
                Verify add operation (of and entry with userpassword) is \
                recorded properly in the external changelog on each \
                replication server'
              </message>
              <!-- Add entry to "master" server -->
              <script>
                myEntry = Entry('cn=user')
                myEntry.addAttr('sn', 'user')
                myEntry.addAttr('userpassword', 'password')
                addedEntry = None
                eclEntry   = None
              </script>
              <call function="'addAnEntry'">
                { 'location'       : masterHost,
                  'dsPath'         : masterPath,
                  'dsInstanceHost' : masterHost,
                  'dsInstancePort' : master.getPort(),
                  'dsInstanceDn'   : master.getRootDn(),
                  'dsInstancePswd' : master.getRootPwd(),
                  'DNToAdd'        : myEntry.getDn(),
                  'listAttributes' : myEntry.getAttrList()
                }
              </call>
              <!-- Verify the synchronization of the trees among the servers in
                the topology -->
              <call function="'verifyTrees'">
                [ clientHost, clientPath, master, consumerList, synchroSuffix ]
              </call>
              <!-- Retrieve entry information for checkChangelogEntry's sake -->
              <call function="'ldapSearchWithScript'">
                { 'location'       : masterHost,
                  'dsPath'         : masterPath,
                  'dsInstanceHost' : masterHost,
                  'dsInstancePort' : master.getPort(),
                  'dsInstanceDn'   : master.getRootDn(),
                  'dsInstancePswd' : master.getRootPwd(),
                  'dsBaseDN'       : myEntry.getDn(),
                  'dsFilter'       : 'objectclass=*',
                  'dsAttributes'   : '%s pwdChangedTime' % addOperationalAttrs
                }
              </call>
              <script>
                searchRC = STAXResult[0][0]
                searchResult = STAXResult[0][1]
              </script>
              <if expr="searchRC == 0">
                <sequence>
                  <message>
                    'This is the entry as read from server %s:%s: \n%s' % \
                    (masterHost, master.getPort(), searchResult)
                  </message>
                  <call function="'parseLdifEntry'">
                    { 'ldifEntry' : searchResult }
                  </call>
                  <script>
                    addedEntry = STAXResult
                    myTargetDN             = addedEntry['dn'][0]
                    myChangeType           = 'add'
                    myChangeTime           = addedEntry['createtimestamp'][0]
                    myTargetEntryUUID      = addedEntry['entryuuid'][0]
                    # myReplicationCSN     = ----> grab from where???
                    # myReplicatIdentifier = ----> grab from ldap server
                    myChanges              = {}
                    for attr in addedEntry.keys():
                      if (attr != 'dn') and (attr != 'changelogcookie') :
                        myChanges[attr] = addedEntry[attr]
                  </script>
                </sequence>
                <else>
                  <message log="1" level="'Error'">
                    'Could not read added entry %s from server %s:%s' % \
                    (myEntry.getDn(), masterHost, master.getPort())
                  </message>
                </else>
              </if>
              <!-- Search changelog in the various replication servers -->
              <iterate var="server"
                       in="_topologyServerList"
                       indexvar="i">
                <sequence>
                  <script>
                    if globalSplitServers:
                      replServer = _topologyReplServerList[i]
                    else:
                      replServer = server
                    replServerPath = '%s/%s' % (replServer.getDir(), OPENDSNAME)
                  </script>
                  <message>
                    'Reading changelog in replication server %s:%s from index: \
                    %s' % \
                    (replServer.getHostname(), replServer.getPort(), lastCookie)
                  </message>
                  <!-- Search for entry add -->
                  <call function="'SearchExternalChangelog'">
                    { 'location'        : replServer.getHostname(),
                      'dsPath'          : replServerPath,
                      'dsInstanceHost'  : replServer.getHostname(),
                      'dsInstancePort'  : replServer.getPort(),
                      'dsInstanceDn'    : replServer.getRootDn(),
                      'dsInstancePswd'  : replServer.getRootPwd(),
                      'dsScope'         : 'subordinate',
                      'dsBaseDN'        : 'cn=changelog',
                      'dsFilter'        : '(objectclass=*)',
                      'changelogCookie' : lastCookie
                    }
                  </call>
                  <script>
                    searchRC = STAXResult[0][0]
                    searchResult = STAXResult[0][1]
                  </script>
                  <if expr="searchRC == 0">
                    <sequence>
                    <message>
                      'This is the ECL entry as read from server %s:%s: \n%s' %\
                      (replServer.getHostname(), replServer.getPort(),
                       searchResult)
                    </message>
                      <call function="'parseLdifEntry'">
                        { 'ldifEntry' : searchResult }
                      </call>
                      <script>
                        eclEntry = STAXResult
                        nextCookie = eclEntry['changelogcookie'][0]
                      </script>
                      <message>
                        'Parsed changelog entry:  \n%s' % eclEntry
                      </message>
                      <message>
                        'Cookie to use for the next ECL search:  %s' % \
                        nextCookie
                      </message>
                    </sequence>
                    <else>
                      <message log="1" level="'Error'">
                        'Could not read changelog on replication server %s:%s' \
                        % (replServer.getHostname(), replServer.getPort())
                      </message>
                    </else>
                  </if>
                  <if expr="addedEntry and eclEntry">
                    <call function="'checkChangelogEntry'">
                      { 'location'        : masterHost,
                        'dsPath'          : masterPath,
                        'changelogEntry'  : eclEntry,
                        'targetDN'        : myTargetDN,
                        'changeType'      : myChangeType,
                        'changeTime'      : myChangeTime,
                        'targetEntryUUID' : myTargetEntryUUID,
                        'changes'         : myChanges
                      }
                    </call>
                    <else>
                      <sequence>
                        <message log="1" level="'Error'">
                          'Unable to check external changelog entry (failed to \
                          read entries %s and/or %s)' % \
                          (myEntry.getDn(), eclEntry['dn'][0])
                        </message>
                        <call function="'testFailed'"/>
                      </sequence>
                    </else>
                  </if>
                </sequence>
              </iterate>
              <script>
                # set the lastCookie for the next ECL search
                if nextCookie:
                  lastCookie = nextCookie
                # reset variables, just in case
                addedEntry = None
                eclEntry   = None
              </script>
              <call function="'testCase_Postamble'"/>
            </sequence>
          </testcase>
          <!--- Test Case information
          #@TestMarker          Replication External Changelog Tests
          #@TestName            Replication: External Changelog: Modify_replace_2
          #@TestID              Modify_replace_2
          #@TestPurpose         Verify modify-replace operation as non directory
                                manager is well recorded in the external
                                changelog on each repl server
          #@TestPreamble
          #@TestSteps           Modify entry on server A (as non dir manager)
          #@TestSteps           Verify servers in sync
          #@TestSteps           Read entry from ldap server
          #@TestSteps           Read last changelog entry on each repl server
          #@TestSteps           Check changelog entry against expected values
          #@TestPostamble
          #@TestResult          Success if the server synchronised and the
                                changelog entry value check succeeds
          -->
          <testcase name="getTestCaseName('Modify_replace_2')">
            <sequence>
              <call function="'testCase_Preamble'"/>
              <message>
                'Replication: External Changelog: Modify_replace_2. \
                Verify modify-replace operation as non directory manager is \
                recorded properly in the external changelog on each repl server'
              </message>
              <script>
                modEntry   = None
                eclEntry   = None
              </script>
              <!-- Modify entry on one of the servers -->
              <call function="'modifyAnAttribute'">
                { 'location'          : masterHost,
                  'dsPath'            : masterPath,
                  'dsInstanceHost'    : masterHost,
                  'dsInstancePort'    : master.getPort(),
                  'dsInstanceDn'      : myEntry.getDn(),
                  'dsInstancePswd'    : 'password',
                  'DNToModify'        : myEntry.getDn(),
                  'attributeName'     : 'userpassword',
                  'newAttributeValue' : 'newpass',
                  'changetype'        : 'replace'
                }
              </call>
              <!-- Verify the synchronization of the trees among the servers in
                the topology -->
              <call function="'verifyTrees'">
                [ clientHost, clientPath, master, consumerList, synchroSuffix ]
              </call>
              <!-- Retrieve entry information for checkChangelogEntry's sake -->
              <call function="'ldapSearchWithScript'">
                { 'location'       : masterHost,
                  'dsPath'         : masterPath,
                  'dsInstanceHost' : masterHost,
                  'dsInstancePort' : master.getPort(),
                  'dsInstanceDn'   : master.getRootDn(),
                  'dsInstancePswd' : master.getRootPwd(),
                  'dsBaseDN'       : myEntry.getDn(),
                  'dsFilter'       : 'objectclass=*',
                  'dsAttributes'   : '%s pwdChangedTime' % modOperationalAttrs
                }
              </call>
              <script>
                searchRC = STAXResult[0][0]
                searchResult = STAXResult[0][1]
              </script>
              <if expr="searchRC == 0">
                <sequence>
                  <message>
                    'This is the entry as read from server %s:%s: \n%s' % \
                    (masterHost, master.getPort(), searchResult)
                  </message>
                  <call function="'parseLdifEntry'">
                    { 'ldifEntry' : searchResult }
                  </call>
                  <script>
                    modEntry = STAXResult
                    myTargetDN             = modEntry['dn'][0]
                    myChangeType           = 'modify'
                    myChangeTime           = modEntry['modifytimestamp'][0]
                    myTargetEntryUUID      = modEntry['entryuuid'][0]
                    # myReplicationCSN     = ----> grab from where???
                    # myReplicatIdentifier = ----> grab from ldap server
                    myModifiersName        = modEntry['modifiersname'][0]
                    myModifyTimestamp      = modEntry['modifytimestamp'][0]
                    myUserPassword         = modEntry['userpassword'][0]
                    myPwdChangedTime       = modEntry['pwdchangedtime'][0]
                    myChanges              = []
                    myChanges.append(['replace', 'userpassword', myUserPassword])
                    myChanges.append(['replace', 'pwdchangedtime', myPwdChangedTime])
                    myChanges.append(['replace', 'modifiersname', myModifiersName])
                    myChanges.append(['replace', 'modifytimestamp', myModifyTimestamp])
                  </script>
                </sequence>
                <else>
                  <message log="1" level="'Error'">
                    'Could not read modified entry %s from server %s:%s' % \
                    (myEntry.getDn(), masterHost, master.getPort())
                  </message>
                </else>
              </if>
              <!-- Search changelog in the various replication servers -->
              <iterate var="server"
                       in="_topologyServerList"
                       indexvar="i">
                <sequence>
                  <script>
                    if globalSplitServers:
                      replServer = _topologyReplServerList[i]
                    else:
                      replServer = server
                    replServerPath = '%s/%s' % (replServer.getDir(), OPENDSNAME)
                  </script>
                  <message>
                    'Reading changelog in replication server %s:%s from index: \
                    %s' % \
                    (replServer.getHostname(), replServer.getPort(), lastCookie)
                  </message>
                  <!-- Search for entry modify -->
                  <call function="'SearchExternalChangelog'">
                    { 'location'        : replServer.getHostname(),
                      'dsPath'          : replServerPath,
                      'dsInstanceHost'  : replServer.getHostname(),
                      'dsInstancePort'  : replServer.getPort(),
                      'dsInstanceDn'    : replServer.getRootDn(),
                      'dsInstancePswd'  : replServer.getRootPwd(),
                      'dsScope'         : 'subordinate',
                      'dsBaseDN'        : 'cn=changelog',
                      'dsFilter'        : '(objectclass=*)',
                      'changelogCookie' : lastCookie
                    }
                  </call>
                  <script>
                    searchRC = STAXResult[0][0]
                    searchResult = STAXResult[0][1]
                  </script>
                  <if expr="searchRC == 0">
                    <sequence>
                      <message>
                        'This is the ECL entry as read from server %s:%s: \n%s'\
                         % (replServer.getHostname(), replServer.getPort(),
                            searchResult)
                      </message>
                      <call function="'parseLdifEntry'">
                        { 'ldifEntry' : searchResult }
                      </call>
                      <script>
                        eclEntry = STAXResult
                        nextCookie = eclEntry['changelogcookie'][0]
                      </script>
                      <message>
                        'Parsed changelog entry:  \n%s' % eclEntry
                      </message>
                      <message>
                        'Cookie to use for the next ECL search:  %s' % \
                        nextCookie
                      </message>
                    </sequence>
                    <else>
                      <message log="1" level="'Error'">
                        'Could not read changelog on replication server %s:%s' \
                        % (replServer.getHostname(), replServer.getPort())
                      </message>
                    </else>
                  </if>
                  <if expr="modEntry and eclEntry">
                    <call function="'checkChangelogEntry'">
                      { 'location'        : masterHost,
                        'dsPath'          : masterPath,
                        'changelogEntry'  : eclEntry,
                        'targetDN'        : myTargetDN,
                        'changeType'      : myChangeType,
                        'changeTime'      : myChangeTime,
                        'targetEntryUUID' : myTargetEntryUUID,
                        'changes'         : myChanges
                      }
                    </call>
                    <else>
                      <sequence>
                        <message log="1" level="'Error'">
                          'Unable to check external changelog entry (failed to \
                          read entries %s and/or %s)' % \
                          (myEntry.getDn(), eclEntry['dn'][0])
                        </message>
                        <call function="'testFailed'"/>
                      </sequence>
                    </else>
                  </if>
                </sequence>
              </iterate>
              <script>
                # set the lastCookie for the next ECL search
                if nextCookie:
                  lastCookie = nextCookie
                # reset variables, just in case
                modEntry  = None
                eclEntry  = None
              </script>
              <call function="'testCase_Postamble'"/>
            </sequence>
          </testcase>
          <import machine="STAF_LOCAL_HOSTNAME"
                  file="'%s/testcases/replication/replication_cleanup.xml'
                        % (TESTS_DIR)"/>
          <call function="'replication_cleanup'" />
          <call function="'testSuite_Postamble'"/>
        </sequence>
      </block>
    </sequence>
  </function>
</stax>
opends/tests/staf-tests/functional-tests/testcases/replication/replication.xml
@@ -58,6 +58,7 @@
            testList.append('encryption')
            testList.append('changelog')
            testList.append('conflict')
            testList.append('externalchangelog')
            
            globalAssuredReplication = None
            
opends/tests/staf-tests/shared/functions/ldap.xml
@@ -3083,6 +3083,151 @@
    </sequence>
  </function>
  
  <!-- Rename an entry -->
  <function name="modifyDn">
    <function-prolog>
      This function renames an entry, either by a modRDN or by a modDN
    </function-prolog>
    <function-map-args>
      <function-arg-def name="location"
                        type="optional"
                        default="STAF_REMOTE_HOSTNAME">
        <function-arg-description>
          Location of target host
        </function-arg-description>
        <function-arg-property name="type" value="hostname"/>
      </function-arg-def>
      <function-arg-def name="dsPath"
                        type="optional"
                        default="'%s/%s' % (DIRECTORY_INSTANCE_DIR,OPENDSNAME)">
        <function-arg-description>
          Pathname to installation root
        </function-arg-description>
        <function-arg-property name="type" value="filepath"/>
      </function-arg-def>
      <function-arg-def name="dsInstanceHost"
                        type="optional"
                        default="STAF_REMOTE_HOSTNAME">
        <function-arg-description>
          Directory server hostname or IP address
        </function-arg-description>
        <function-arg-property name="type" value="hostname"/>
      </function-arg-def>
      <function-arg-def name="dsInstancePort" type="required">
        <function-arg-description>
          Directory server port number
        </function-arg-description>
        <function-arg-property name="type" value="Port number"/>
      </function-arg-def>
      <function-arg-def name="dsInstanceDn" type="required">
        <function-arg-description>
          Bind DN
        </function-arg-description>
        <function-arg-property name="type" value="DN"/>
      </function-arg-def>
      <function-arg-def name="dsInstancePswd" type="required">
        <function-arg-description>
          Bind password
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
      <function-arg-def name="DNToModify" type="required">
        <function-arg-description>
          DN to modify
        </function-arg-description>
        <function-arg-property name="type" value="DN"/>
      </function-arg-def>
      <function-arg-def name="newRDN"
                        type="optional"
                        default="None">
        <function-arg-description>
          New rdn name : ie uid=jdoe_bis. If not passed, the newRDN will be the
          same old rdn
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
      <function-arg-def name="deleteOldRDN"
                        type="optional"
                        default="None">
        <function-arg-description>
          1 : delete old rdn, 0 do not delete old rdn, default value is 1
        </function-arg-description>
        <function-arg-property name="type" value="integer"/>
      </function-arg-def>
      <function-arg-def name="newSuperior"
                        type="optional"
                        default="None">
        <function-arg-description>
          New parent entry for the entry
        </function-arg-description>
        <function-arg-property name="type" value="DN"/>
      </function-arg-def>
      <function-arg-def name="expectedRC" type="optional" default="0">
        <function-arg-description>
          Expected return code value. Default value is 0
          Wildcard 'noCheck' to not check the RC
        </function-arg-description>
        <function-arg-property name="type" value="integer"/>
      </function-arg-def>
      <function-arg-def name="knownIssue" type="optional" default="None">
        <function-arg-description>
          Known issue. Corresponds to an issue number.
        </function-arg-description>
        <function-arg-property name="type" value="string" />
      </function-arg-def>
    </function-map-args>
    <sequence>
      <!-- Local variables -->
      <script>
        mylocation=location
        #Build the Command
        STAFCmdParamsList=[]
        STAFCmdParams=''
        STAFCmd=''
      </script>
      <!-- Set common ldap arguments -->
      <call function="'_ldapCommonArgs'" />
      <script>
        if DNToModify:
          STAFCmdParamsList.append('-d "%s"' % DNToModify)
        if newRDN:
          STAFCmdParamsList.append('-e "%s"' % newRDN)
        if deleteOldRDN == 0:
          STAFCmdParamsList.append('-f "false"')
        elif deleteOldRDN == 1:
          STAFCmdParamsList.append('-f "true"')
        if newSuperior:
          STAFCmdParamsList.append('-g "%s"' % newSuperior)
        STAFCmdParams=' '.join(STAFCmdParamsList)
        STAFCmd='modifyDn'
      </script>
      <call function="'runCommand'" >
        { 'name'      : 'Modify DN',
          'command'   : '%s/bin/java' % JAVA_HOME,
          'arguments' : '%s %s' % (STAFCmd,STAFCmdParams),
          'location'  : location,
          'envCmd'    : ['CLASSPATH=%s/../%s/ldapjndi' % (dsPath,remote.reljavadir)],
          'path'      : '%s/../%s/ldapjndi' % (dsPath,remote.reljavadir),
          'expectedRC': expectedRC,
          'knownIssue': knownIssue
        }
      </call>
      <return>
        STAXResult
      </return>
    </sequence>
  </function>
  <!-- Modify an attribute -->
  <function name="saslSearch">
    <function-prolog>
@@ -3299,4 +3444,182 @@
    
  </function>  
  
  <function name="SearchExternalChangelog">
    <function-prolog>
      This function searches the external changelog
    </function-prolog>
    <function-map-args>
      <function-arg-def name="location" type="optional" default="STAF_REMOTE_HOSTNAME">
        <function-arg-description>
          Location of target host
        </function-arg-description>
        <function-arg-property name="type" value="hostname"/>
      </function-arg-def>
      <function-arg-def name="dsPath" type="optional" default="'%s/%s' % (DIRECTORY_INSTANCE_BIN,OPENDSNAME)">
        <function-arg-description>
          Pathname to installation root
        </function-arg-description>
        <function-arg-property name="type" value="pathname"/>
      </function-arg-def>
      <function-arg-def name="dsInstanceHost" type="optional">
        <function-arg-description>
          Directory Server Host Name
        </function-arg-description>
        <function-arg-property name="type" value="hostname"/>
      </function-arg-def>
      <function-arg-def name="dsInstancePort" type="optional">
        <function-arg-description>
          Directory Server Port Number
        </function-arg-description>
        <function-arg-property name="type" value="integer"/>
      </function-arg-def>
      <function-arg-def name="dsInstanceDn" type="optional">
        <function-arg-description>
          Directory Server Manager DN
        </function-arg-description>
        <function-arg-property name="type" value="dn"/>
      </function-arg-def>
      <function-arg-def name="dsInstancePswd" type="optional">
        <function-arg-description>
          Directory Server Manager Password
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
      <function-arg-def name="dsScope" type="optional">
        <function-arg-description>
          The scope of the search operation
        </function-arg-description>
        <function-arg-property name="type" value="dn"/>
      </function-arg-def>
      <function-arg-def name="dsBaseDN" type="optional">
        <function-arg-description>
          The baseDN for the search operation
        </function-arg-description>
        <function-arg-property name="type" value="dn"/>
      </function-arg-def>
      <function-arg-def name="dsFilter" type="required">
        <function-arg-description>
          The filter for the search operation
        </function-arg-description>
        <function-arg-property name="type" value="filter"/>
      </function-arg-def>
      <function-arg-def name="extraParams" type="optional">
        <function-arg-description>
          Optional extra parameters for specific test cases
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
      <function-arg-def name="attributes" type="optional">
        <function-arg-description>
          Optional Attributes to return
        </function-arg-description>
        <function-arg-property name="type" value="attributes"/>
      </function-arg-def>
      <function-arg-def name="changelogCookie"
                        type="optional"
                        default="None">
        <function-arg-description>
          Changelog cookie of the last change retrieved.
          (OpenDS external changelog => non draft-compatible mode)
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
      <function-arg-def name="changeNumberFilter"
                        type="optional"
                        default="None">
        <function-arg-description>
          Changenumber corresponding to the last change retrieved.
          (Changelog draft-compatible mode)
        </function-arg-description>
        <function-arg-property name="type" value="filter"/>
      </function-arg-def>
      <function-arg-def name="expectedRC" type="optional" default="0">
        <function-arg-description>
          Expected return code value. Default value is 0.
          Wildcard 'noCheck' to not check the RC
        </function-arg-description>
        <function-arg-property name="type" value="integer"/>
      </function-arg-def>
      <function-arg-def name="knownIssue" type="optional" default="None">
        <function-arg-description>
          Known issue. Corresponds to an issue number.
        </function-arg-description>
        <function-arg-property name="type" value="string" />
      </function-arg-def>
    </function-map-args>
    <sequence>
      <!-- Local variables -->
      <script>
        mylocation=location
      </script>
      <!-- Build the Command -->
      <script>
        STAFCmdParamsList=[]
        STAFCmdParams=''
        if dsPath:
          dsBinPath='%s/%s' % (dsPath,fileFolder)
          STAFCmd='%s/ldapsearch%s' % (dsBinPath,fileExt)
      </script>
      <!-- Set common ldap arguments -->
      <call function="'_ldapCommonArgs'"/>
      <script>
        if changelogCookie:
          control = '1.3.6.1.4.1.26027.1.5.4:false:%s' % changelogCookie
          STAFCmdParamsList.append('--control "%s"' % control)
        if dsScope:
          STAFCmdParamsList.append('-s %s' % dsScope)
        if extraParams:
          STAFCmdParamsList.append('%s' % extraParams)
        if dsBaseDN:
          STAFCmdParamsList.append('-b "%s"' % dsBaseDN)
        if dsFilter:
          if changeNumberFilter:
            searchFilter = '&amp;%s%s' % (changeNumberFilter, dsFilter)
          else:
            searchFilter = dsFilter
          STAFCmdParamsList.append('"%s"' % searchFilter)
        else:
          if changeNumberFilter:
            STAFCmdParamsList.append('"%s"' % changeNumberFilter)
        if attributes:
          STAFCmdParamsList.append('%s' % attributes)
        else:
          STAFCmdParamsList.append('@changeLogEntry')
        STAFCmdParams=' '.join(STAFCmdParamsList)
      </script>
      <message>
        '%s %s' % (STAFCmd, STAFCmdParams)
      </message>
      <call function="'runCommand'">
        { 'command'   : STAFCmd,
          'arguments' : STAFCmdParams,
          'location'  : location,
          'name'      : 'ldapsearch',
          'expectedRC': expectedRC,
          'knownIssue': knownIssue
        }
      </call>
      <return>
        STAXResult
      </return>
    </sequence>
  </function>
</stax>
opends/tests/staf-tests/shared/functions/utils.xml
@@ -242,6 +242,15 @@
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
      <function-arg-def name="searchType"
                        type="optional"
                        default="'substring'">
        <function-arg-description>
          the type of the search: substring, exact-case-insensitive or
          exact-case-sensitive
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
      <function-arg-def name="knownIssue" type="optional" default="None">
        <function-arg-description>
          Known issue. Corresponds to an issue number.
@@ -280,12 +289,21 @@
        </sequence>
      </if>
      <script>
        if searchType == 'substring':
          searchResult = (re.search(searchre, returnString) != None)
        elif searchType == 'exact-case-sensitive':
          searchResult = (expectedString == returnString)
        elif searchType == 'exact-case-insensitive':
          searchResult = (expectedString.lower() == returnString.lower())
      </script>
      <!-- Search for the expectedString -->
      <if expr='re.search(searchre, returnString) != None'>
      <if expr='searchResult'>
        <sequence>
          <message log="1">
            'Found substring, %s, in the return string' \
            % (expectedString)
            'Search type: %s  Found substring, %s, in the return string' \
            % (searchType, expectedString)
          </message>
          <script>
            myRC = 0
@@ -295,8 +313,8 @@
        <else>
          <sequence>
            <message log="1">
              'Did not find substring, %s, in the return string, %s' \
              % (expectedString, returnString)
              'Search type: %s  Did not find substring, %s, in the return \
              string, %s' % (searchType, expectedString, returnString)
            </message>
            <script>
              myRC = 1
@@ -2939,4 +2957,634 @@
    </sequence>
  </function>
  <!-- This function parses an ldif entry -->
  <function name="parseLdifEntry">
    <function-prolog>
      This function parses an ldif entry and returns a dictionary, e.g.:
      {'dn':['o=example'],'objectclass':['top','organization'],'o':['example']}
    </function-prolog>
    <function-map-args>
      <function-arg-def name="ldifEntry" type="required">
        <function-arg-description>
          Ldif entry to parse (single string).
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
    </function-map-args>
    <sequence>
      <script>
        parsedEntry = {}
        prevAttr    = None
        prevVal     = None
        for line in ldifEntry.splitlines():
          notBlank = (len(line.strip()) != 0)
          if notBlank and (not line.startswith(' ')):
            # line corresponds to an attr:val
            attr = line[:line.find(':')].strip().lower()
            val  = line[line.find(':') + 1:].lstrip()
            if val.startswith(':'):
              val = val[1:].lstrip()
            if attr == 'objectclass':
              val = val.lower()
            if not (attr in parsedEntry.keys()):
              # This is the first occurrence of this attr
              parsedEntry[attr] = [val]
            else:
              # There is already some value for this attr
              parsedEntry[attr].append(val)
            prevAttr = attr
            prevVal  = val
          elif notBlank:
            # line corresponds to a trailing value
            parsedEntry[prevAttr].remove(prevVal)
            val = prevVal + line.lstrip()
            parsedEntry[prevAttr].append(val)
            prevVal = val
      </script>
      <return> parsedEntry </return>
    </sequence>
  </function>
  <!-- This function parses an ldif change -->
  <function name="parseLdifChange">
    <function-prolog>
      This function parses an ldif change and returns a list, e.g.:
      [ ['replace','l','London'], ['add','description','This is a test'] ]
    </function-prolog>
    <function-map-args>
      <function-arg-def name="ldifChange" type="required">
        <function-arg-description>
          Ldif change to parse (single string).
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
    </function-map-args>
    <sequence>
      <script>
        parsedChange = []
        mod          = []
        prevAttr     = None
        prevVal      = None
        for line in ldifChange.splitlines():
          notBlank = (len(line.strip()) != 0)
          if notBlank and (not line.startswith(' ')) and (not line.startswith('-')):
            # line corresponds to an attr:val
            attr = line[:line.find(':')].strip().lower()
            val  = line[line.find(':') + 1:].lstrip()
            if val.startswith(':'):
              val = val[1:].lstrip()
            if attr == 'objectclass':
              val = val.lower()
            if (prevVal != None) and (attr == prevVal.lower()):
              # attr represents indeed an attribute type, so we may assume the
              # mod already has [mod_type,attr_type]
              mod.append(val)
            else:
              # attr represents the mod_type, and val the attr_type
              mod.append(attr)
              mod.append(val.lower())
            prevAttr = attr
            prevVal  = val
          elif notBlank and line.startswith(' '):
            # line corresponds to a trailing value
            mod.remove(prevVal)
            val = prevVal + line.lstrip()
            mod.append(val)
            prevVal = val
          else:
            # line is empty or line starts with '-'; this means that
            # the mod is complete, so we can add it to the parsedChange list
            parsedChange.append(mod)
            mod = []
        if len(mod) != 0:
          # add the trailing mod to the parsedChange list
          parsedChange.append(mod)
      </script>
      <return> parsedChange </return>
    </sequence>
  </function>
  <!-- This function checks the content of an external changelog entry -->
  <function name="checkChangelogEntry">
    <function-prolog>
      This function checks the content of an external changelog entry
    </function-prolog>
    <function-map-args>
      <function-arg-def name="location"
                        type="optional"
                        default="STAF_REMOTE_HOSTNAME">
        <function-arg-description>
          Location of target host
        </function-arg-description>
        <function-arg-property name="type" value="hostname"/>
      </function-arg-def>
      <function-arg-def name="dsPath"
                        type="optional"
                        default="'%s/%s' % (DIRECTORY_INSTANCE_BIN,OPENDSNAME)">
        <function-arg-description>
          Pathname to installation root
        </function-arg-description>
        <function-arg-property name="type" value="filepath"/>
      </function-arg-def>
      <function-arg-def name="changelogEntry" type="required">
        <function-arg-description>
          External changelog entry (as an output of parseLdifEntry)
        </function-arg-description>
        <function-arg-property name="type" value="dictionary"/>
      </function-arg-def>
      <function-arg-def name="targetDN" type="required">
        <function-arg-description>
          DN of the target entry for the change
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
      <function-arg-def name="changeType" type="required">
        <function-arg-description>
          Change type (e.g. add, delete, modify)
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
      <function-arg-def name="changeTime"
                        type="optional"
                        default="None">
        <function-arg-description>
          Change time
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
      <function-arg-def name="changeNumber"
                        type="optional"
                        default="None">
        <function-arg-description>
          Changenumber (only for changelog draft-compatible mode)
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
      <function-arg-def name="replicationCSN"
                        type="optional"
                        default="None">
        <function-arg-description>
          Replication CSN
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
      <function-arg-def name="targetEntryUUID"
                        type="optional"
                        default="None">
        <function-arg-description>
          Target entry uuid
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
      <function-arg-def name="replicaIdentifier"
                        type="optional"
                        default="None">
        <function-arg-description>
          Replica Identifier
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
      <function-arg-def name="newRDN"
                        type="optional"
                        default="None">
        <function-arg-description>
          NewRDN
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
      <function-arg-def name="deleteOldRDN"
                        type="optional"
                        default="None">
        <function-arg-description>
          DeleteOldRDN
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
      <function-arg-def name="newSuperior"
                        type="optional"
                        default="None">
        <function-arg-description>
          NewSuperior
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
      <function-arg-def name="changes"
                        type="optional"
                        default="None">
        <function-arg-description>
          Changes. If changetype == add, changes should be a map, e.g.:
          {'dn':['o=example'],'objectclass':['top','organization'],'o':['example']}
          If changetype == modify, changes should be a list, e.g.:
          [ ['replace','l','London'], ['add','description','This is a test'] ]
        </function-arg-description>
        <function-arg-property name="type" value="string"/>
      </function-arg-def>
      <function-arg-def name="knownIssue" type="optional" default="None">
        <function-arg-description>
          Known issue. Corresponds to an issue number.
        </function-arg-description>
        <function-arg-property name="type" value="string" />
      </function-arg-def>
    </function-map-args>
    <sequence>
      <script>
        myLocation            = location
        myPath                = dsPath
        # Mandatory attributes in a changeLogEntry
        ecl_DN                = changelogEntry['dn'][0]
        ecl_targetDN          = changelogEntry['targetdn'][0]
        ecl_changeType        = changelogEntry['changetype'][0]
        ecl_changeTime        = changelogEntry['changetime'][0]
        ecl_changeNumber      = changelogEntry['changenumber'][0]
        # Optional attributes
        ecl_replicationCSN    = None
        ecl_replicaIdentifier = None
        ecl_targetEntryUUID   = None
        ecl_newRDN            = None
        ecl_deleteOldRDN      = None
        ecl_newSuperior       = None
        ecl_changes           = None
        if 'replicationcsn' in changelogEntry.keys():
          ecl_replicationCSN = changelogEntry['replicationcsn'][0]
        if 'replicaidentifier' in changelogEntry.keys():
          ecl_replicaIdentifier = changelogEntry['replicaidentifier'][0]
        if 'targetentryuuid' in changelogEntry.keys():
          ecl_targetEntryUUID = changelogEntry['targetentryuuid'][0]
        if 'newrdn' in changelogEntry.keys():
          ecl_newRDN = changelogEntry['newrdn'][0]
        if 'deleteoldrdn' in changelogEntry.keys():
          ecl_deleteOldRDN = changelogEntry['deleteoldrdn'][0]
        if 'newsuperior' in changelogEntry.keys():
          ecl_newSuperior = changelogEntry['newsuperior'][0]
        if 'changes' in changelogEntry.keys():
          ecl_changes = changelogEntry['changes'][0]
      </script>
      <message>
        'checkChangelogEntry: Checking changelog entry %s against expected \
        values' % ecl_DN
      </message>
      <message>
        'checkChangelogEntry: Checking targetDN'
      </message>
      <call function="'searchString'">
        { 'returnString'   : ecl_targetDN,
          'expectedString' : targetDN,
          'searchType'     : 'exact-case-insensitive'
        }
      </call>
      <message>
        'checkChangelogEntry: Checking changeType'
      </message>
      <call function="'searchString'">
        { 'returnString'   : ecl_changeType,
          'expectedString' : changeType,
          'searchType'     : 'exact-case-insensitive'
        }
      </call>
      <if expr="changeTime">
        <sequence>
          <message>
            'checkChangelogEntry: Checking changeTime'
          </message>
          <call function="'searchString'">
            { 'returnString'   : ecl_changeTime,
              'expectedString' : changeTime,
              'searchType'     : 'exact-case-insensitive'
            }
          </call>
        </sequence>
      </if>
      <if expr="changeNumber">
        <sequence>
          <message>
            'checkChangelogEntry: Checking changeNumber'
          </message>
          <call function="'searchString'">
            { 'returnString'   : ecl_changeNumber,
              'expectedString' : changeNumber,
              'searchType'     : 'exact-case-sensitive'
            }
          </call>
        </sequence>
      </if>
      <if expr="replicationCSN">
        <if expr="ecl_replicationCSN">
          <sequence>
            <message>
              'checkChangelogEntry: Checking replicationCSN'
            </message>
            <call function="'searchString'">
              { 'returnString'   : ecl_replicationCSN,
                'expectedString' : replicationCSN,
                'searchType'     : 'exact-case-insensitive'
              }
            </call>
          </sequence>
          <else>
            <sequence>
              <message log="1" level="'Error'">
                'No replicationCSN could be found in the changelog entry'
              </message>
              <call function="'testFailed'"/>
            </sequence>
          </else>
        </if>
      </if>
      <if expr="replicaIdentifier">
        <if expr="ecl_replicaIdentifier">
          <sequence>
            <message>
              'checkChangelogEntry: Checking replicaIdentifier'
            </message>
            <call function="'searchString'">
              { 'returnString'   : ecl_replicaIdentifier,
                'expectedString' : replicaIdentifier,
                'searchType'     : 'exact-case-sensitive'
              }
            </call>
          </sequence>
          <else>
            <sequence>
              <message log="1" level="'Error'">
                'No replicaIdentifier could be found in the changelog entry'
              </message>
              <call function="'testFailed'"/>
            </sequence>
          </else>
        </if>
      </if>
      <if expr="targetEntryUUID">
        <if expr="ecl_targetEntryUUID">
          <sequence>
            <message>
              'checkChangelogEntry: Checking targetEntryUUID'
            </message>
            <call function="'searchString'">
              { 'returnString'   : ecl_targetEntryUUID,
                'expectedString' : targetEntryUUID,
                'searchType'     : 'exact-case-insensitive'
              }
            </call>
          </sequence>
          <else>
            <sequence>
              <message log="1" level="'Error'">
                'No targetEntryUUID could be found in the changelog entry'
              </message>
              <call function="'testFailed'"/>
            </sequence>
          </else>
        </if>
      </if>
      <if expr="newRDN">
        <if expr="ecl_newRDN">
          <sequence>
            <message>
              'checkChangelogEntry: Checking newRDN'
            </message>
            <call function="'searchString'">
              { 'returnString'   : ecl_newRDN,
                'expectedString' : newRDN,
                'searchType'     : 'exact-case-insensitive'
              }
            </call>
          </sequence>
          <else>
            <sequence>
              <message log="1" level="'Error'">
                'No newRDN could be found in the changelog entry'
              </message>
              <call function="'testFailed'"/>
            </sequence>
          </else>
        </if>
      </if>
      <if expr="deleteOldRDN">
        <if expr="ecl_deleteOldRDN">
          <sequence>
            <message>
              'checkChangelogEntry: Checking deleteOldRDN'
            </message>
            <call function="'searchString'">
              { 'returnString'   : ecl_deleteOldRDN,
                'expectedString' : deleteOldRDN,
                'searchType'     : 'exact-case-sensitive'
              }
            </call>
          </sequence>
          <else>
            <sequence>
              <message log="1" level="'Error'">
                'No deleteOldRDN could be found in the changelog entry'
              </message>
              <call function="'testFailed'"/>
            </sequence>
          </else>
        </if>
      </if>
      <if expr="newSuperior">
        <if expr="ecl_newSuperior">
          <sequence>
            <message>
              'checkChangelogEntry: Checking newSuperior'
            </message>
            <call function="'searchString'">
              { 'returnString'   : ecl_newSuperior,
                'expectedString' : newSuperior,
                'searchType'     : 'exact-case-insensitive'
              }
            </call>
          </sequence>
          <else>
            <sequence>
              <message log="1" level="'Error'">
                'No newSuperior could be found in the changelog entry'
              </message>
              <call function="'testFailed'"/>
            </sequence>
          </else>
        </if>
      </if>
      <if expr="changes">
        <if expr="ecl_changes">
          <sequence>
            <!-- Decode the changes that are encoded in base64 -->
            <message>
              'checkChangelogEntry: Decode external changelog entry changes'
            </message>
            <call function="'Base64WithScript'">
              { 'location'    : myLocation,
                'dsPath'      : myPath,
                'subcommand'  : 'decode',
                'encodedData' : ecl_changes
              }
            </call>
            <!-- STAXResult is not always a list-->
            <script>
              try:
                decodeRC, decodedChanges = STAXResult[0]
              except AttributeError, details:
                decodedChanges = 'AttributeError: can not parse STAXResult %s' \
                                 % details
                decodeRC = '1'
            </script>
            <message>
              'checkChangelogEntry: Decoded changes:\n%s' % decodedChanges
            </message>
            <message>
              'checkChangelogEntry: Checking changes'
            </message>
            <if expr="decodeRC == 0 and changeType == 'add'">
              <!-- If changetype:add, the changes look like a sequence of
               !   attribute:value, so we may parse them as an ldif entry -->
              <sequence>
                <call function="'parseLdifEntry'">
                  { 'ldifEntry' : decodedChanges }
                </call>
                <script>
                  ecl_changesMap = STAXResult
                </script>
                <message>
                  'Parsed changelog entry changes:  \n%s' % ecl_changesMap
                </message>
                <iterate var="attr" in="changes.keys()">
                  <sequence>
                    <script>
                      valueList     = changes[attr]
                      ecl_valueList = None
                      if attr in ecl_changesMap.keys():
                        ecl_valueList = ecl_changesMap[attr]
                        ecl_valueList.sort()
                        valueList.sort()
                    </script>
                    <if expr="ecl_valueList != None">
                      <sequence>
                        <message>
                          'checkChangelogEntry: Checking changes: %s' % attr
                        </message>
                        <if expr="valueList == ecl_valueList">
                          <message>
                            'Found expected values in changes: %s' % valueList
                          </message>
                          <else>
                            <sequence>
                              <message log="1" level="'Error'">
                                'Expected values %s could not be found in %s' \
                                 % (valueList, ecl_valueList)
                              </message>
                              <call function="'testFailed'"/>
                            </sequence>
                          </else>
                        </if>
                      </sequence>
                      <else>
                        <sequence>
                          <message log="1" level="'Error'">
                            'No %s could be found in the changes' % attr
                          </message>
                          <call function="'testFailed'"/>
                        </sequence>
                      </else>
                    </if>
                  </sequence>
                </iterate>
              </sequence>
              <elseif expr="decodeRC == 0">
                <!-- If changetype:modify, the changes look like a sequence of
                 !     mod_type:attribute
                 !     attribute:value
                 !   so we need to treat them differently -->
                <sequence>
                  <call function="'parseLdifChange'">
                    { 'ldifChange' : decodedChanges }
                  </call>
                  <script>
                    ecl_changesList = STAXResult
                  </script>
                  <message>
                    'Parsed changelog entry changes:  \n%s' % ecl_changesList
                  </message>
                  <iterate var="mod" in="changes">
                    <sequence>
                      <script>
                        mod_type = mod[0]
                        mod_attr = mod[1]
                        mod_val  = None
                        if len(mod) == 3:
                          mod_val = mod[2]
                      </script>
                      <message>
                        'checkChangelogEntry: Checking changes: %s' % mod
                      </message>
                      <if expr="mod in ecl_changesList">
                        <message>
                          'Found expected change:\n %s: %s\n %s: %s\n' \
                           % (mod_type, mod_attr, mod_attr, mod_val)
                        </message>
                        <else>
                          <sequence>
                            <message log="1" level="'Error'">
                              'Expected change %s could not be found in %s'\
                               % (mod, ecl_changesList)
                            </message>
                            <call function="'testFailed'"/>
                          </sequence>
                        </else>
                      </if>
                    </sequence>
                  </iterate>
                </sequence>
              </elseif>
            </if>
          </sequence>
          <else>
            <sequence>
              <message log="1" level="'Error'">
                'No changes could be found in the changelog entry'
              </message>
              <call function="'testFailed'"/>
            </sequence>
          </else>
        </if>
      </if>
    </sequence>
  </function>
</stax>
opends/tests/staf-tests/shared/java/ldapjndi/modifyDn.java
New file
@@ -0,0 +1,169 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 */
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.CommunicationException;
import java.util.HashSet;
import java.util.Iterator;
/**
 *  Modify the DN of an entry.
 *  The operation can be a modDN (tree move) or a modRDN.
 *  The function returns the ldap error code
 */
public class modifyDn {
  /**
   * Main.
   *
   * @param args arguments
   */
  public static void main(String[] args) {
    String hostname=null;
    String ldapPort=null;
    String principal=null;
    String credential=null;
    String dnToModify=null;
    String newRDN=null;
    String deleteOldRDN=null;
    String newSuperior=null;
    String errorCode=null;
    String errorMessage=null;
    Hashtable envLdap  = new Hashtable();
    LdapContext ctx;
    LdapName nameToModify;
    LdapName targetName;
    String targetDn;
    for (int k=0; k< args.length; k++) {
      String opt1 = args[k];
      String val1 = args[k+1];
      if (opt1.equals("-h")) {
        hostname = val1;
      }
      if (opt1.equals("-p")) {
        ldapPort = val1;
      }
      if (opt1.equals("-D")) {
        principal = val1;
      }
      if (opt1.equals("-w")) {
        credential = val1;
      }
      if (opt1.equals("-d")) {
        dnToModify = val1;
      }
      if (opt1.equals("-e")) {
        newRDN = val1;
      }
      if (opt1.equals("-f")) {
        deleteOldRDN = val1;
      }
      if (opt1.equals("-g")) {
        newSuperior = val1;
      }
      k++;
    }
    String provider = "ldap://"  + hostname + ":" + ldapPort  + "/";
    envLdap.put("java.naming.factory.initial",
                "com.sun.jndi.ldap.LdapCtxFactory");
    envLdap.put(Context.SECURITY_AUTHENTICATION, "simple");
    envLdap.put(Context.SECURITY_PRINCIPAL,  principal);
    envLdap.put(Context.SECURITY_CREDENTIALS, credential);
    envLdap.put(Context.PROVIDER_URL, provider);
    // Whether the old RDN attribute values are to be retained
    // as attributes of the entry, or deleted from the entry.
    // Default is 'true'
    if (deleteOldRDN != null) {
      envLdap.put("java.naming.ldap.deleteRDN", deleteOldRDN);
    }
    try {
      System.out.println("Modify the entry " + dnToModify);
      if ( newRDN == null ) {
        // newRDN = same as old rdn
        nameToModify = new LdapName(dnToModify);
        newRDN = (String) nameToModify.remove(nameToModify.size() - 1);
      }
      if ( newSuperior != null ) {
        // modDN operation => new parent = newSuperior
        targetName = new LdapName(newSuperior);
      } else {
        // modRDN operation => new parent = old parent
        targetName = new LdapName(dnToModify);
        targetName.remove(targetName.size() - 1);
      }
      targetName.add(newRDN);
      targetDn = targetName.toString();
      // connect to server
      ctx = new InitialLdapContext(envLdap, null);
      // rename the entry
      ctx.rename(dnToModify, targetDn);
      ctx.close();
    } catch (CommunicationException e1) {
      errorMessage = e1.getMessage();
    } catch (NamingException e2) {
      errorMessage = e2.getMessage();
    } catch (Exception e3) {
      errorMessage= e3.getMessage();
    }
    // No error, the modify is success
    if ( errorMessage == null ) {
      errorCode="0";
    } else {
      System.out.println(errorMessage);
      int ind=errorMessage.indexOf("-");
      if ( ind > 0 ) {
        errorCode=errorMessage.substring(18, ind-1);
      } else errorCode="0";
    }
    int RC = Integer.parseInt(errorCode);
    System.exit(RC);
  }
}