blob: 7f8829f5b882f99ad895ae2ffaab6d6bbb9d6b8a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.directory.server.operations.modifydn;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import org.apache.directory.api.ldap.model.cursor.CursorException;
import org.apache.directory.api.ldap.model.cursor.EntryCursor;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.message.AddRequest;
import org.apache.directory.api.ldap.model.message.AddResponse;
import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.name.Rdn;
import org.apache.directory.api.util.Network;
import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.apache.directory.ldap.client.api.search.FilterBuilder;
import org.apache.directory.ldap.client.template.ConnectionCallback;
import org.apache.directory.ldap.client.template.EntryMapper;
import org.apache.directory.ldap.client.template.LdapConnectionTemplate;
import org.apache.directory.ldap.client.template.RequestBuilder;
import org.apache.directory.server.annotations.CreateLdapConnectionPool;
import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
import org.apache.directory.server.core.annotations.ApplyLdifFiles;
import org.apache.directory.server.core.annotations.ContextEntry;
import org.apache.directory.server.core.annotations.CreateDS;
import org.apache.directory.server.core.annotations.CreateIndex;
import org.apache.directory.server.core.annotations.CreatePartition;
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
import org.apache.directory.server.core.integ.CreateLdapConnectionPoolRule;
import org.apache.directory.server.core.integ.FrameworkRunner;
import org.apache.directory.server.integ.ServerIntegrationUtils;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This Test case demonstrates an issue with the rename operation as
* describe in
* <a href='https://issues.apache.org/jira/browse/DIRSERVER-1974'>DIRSERVER-1974</a>.
*
* To run this test, remove the <code>@Ignore</code> annotation and run the test.
* Be patient, this test will take a while, but there should be some log messages
* to show progress. It may succeed, but most likely will fail at some point. To
* speed the test up, you could load an external instance with the data and run
* against it instead.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
@RunWith(FrameworkRunner.class)
@CreateDS( name = "classDS",
partitions = {
@CreatePartition(
//type = MavibotPartition.class,
name = "example",
suffix = "dc=example,dc=com",
contextEntry = @ContextEntry(
entryLdif =
"dn: dc=example,dc=com\n" +
"objectClass: domain\n" +
"objectClass: top\n" +
"dc: example\n\n"
),
indexes = {
@CreateIndex(
attribute = "objectClass"
),
@CreateIndex(
attribute = "dc"
),
@CreateIndex(
attribute = "ou"
),
@CreateIndex(
attribute = "uid"
)
}
)
} )
@CreateLdapServer(
transports = {
@CreateTransport( protocol = "LDAP" )
} )
@CreateLdapConnectionPool(
maxActive = 4,
maxIdle = 2,
minIdle = 1 )
@ApplyLdifFiles( {
"dirserver_1974_it.ldif"
} )
public class DIRSERVER_1974_IT extends AbstractLdapTestUnit
{
private static final Logger logger = LoggerFactory.getLogger( DIRSERVER_1974_IT.class );
private static final String BASE = "dc=example,dc=com";
/*
* Reduce dummy and loop count to speedup test. Original values for reproducing the bug:
* DUMMY_COUNT = 1000
* LOOP_COUNT = 100
*/
private static final int DUMMY_COUNT = 100;
private static final int LOOP_COUNT = 10;
private static final EntryMapper<Entry> DEFAULT_ENTRY_MAPPER = new EntryMapper<Entry>() {
@Override
public Entry map( Entry entry ) throws LdapException {
return entry;
}
};
@Test
public void testRenameWithALotOfDummiesAndSomeCustomAttributesAPI() throws LdapException, CursorException, IOException
{
LdapConnection connection = new LdapNetworkConnection( Network.LOOPBACK_HOSTNAME, getLdapServer().getPort() );
connection.bind( "uid=admin, ou=system", "secret" );
Dn peopleDn = new Dn( "ou=people," + BASE );
// Add the root entry
connection.add(
new DefaultEntry( peopleDn,
"objectClass: top",
"objectClass: organizationalUnit",
"ou: people" ) );
// Add N children
for ( int i = 0; i < DUMMY_COUNT; i++ )
{
String dn = "uid=uid-" + i + "," + peopleDn;
connection.add( new DefaultEntry( dn,
"objectClass: top",
"objectClass: person",
"objectClass: organizationalPerson",
"objectClass: inetOrgPerson",
"uid", "uid-" + i,
"cn", "cn-" + i,
"sn", "sn-" + i,
"description", i + " is a person." ) );
if ( i % 50 == 0 )
{
logger.debug( "Added person {}", i );
}
}
EntryCursor cursor;
int count = 0;
// Now test the rename
for ( int i = 0; i < LOOP_COUNT; i++ )
{
String oldDnString = "uid=myra-ellen-amos, " + peopleDn.getName();
String newDnString = "uid=tory-amos, " + peopleDn.getName();
// Add an entry
connection.add( new DefaultEntry( oldDnString,
"objectClass: top",
"objectClass: person",
"objectClass: organizationalPerson",
"objectClass: inetOrgPerson",
"objectClass: portalPerson",
"uid: myra-ellen-amos",
"cn: Myra Ellen Amo",
"sn: Amos",
"active", Boolean.TRUE.toString(),
"affiliation: Unknown",
"timeZone: America/New_York",
"description: Myra Ellen Amos is a person." ) );
// Check it has been added
Entry result = connection.lookup( oldDnString );
assertNotNull (result );
// Search for it
cursor = connection.search( peopleDn, "(sn=amos)", SearchScope.ONELEVEL );
count = 0;
while ( cursor.next() )
{
Entry amos = cursor.get();
assertEquals( "myra-ellen-amos", amos.get( "uid" ).getString() );
assertEquals( "uid=myra-ellen-amos", amos.getDn().getRdn().getName() );
count++;
}
assertEquals( 1, count );
cursor.close();
// Rename it
connection.rename( oldDnString, "uid=tory-amos" );
// Search for the old and renalme entry
assertNull( connection.lookup( oldDnString ) );
result = connection.lookup( newDnString );
assertNotNull( result );
// Search for the new entry
cursor = connection.search( peopleDn, "(sn=amos)", SearchScope.ONELEVEL );
count = 0;
while ( cursor.next() )
{
Entry amos = cursor.get();
assertEquals( "tory-amos", amos.get( "uid" ).getString() );
assertEquals( "uid=tory-amos", amos.getDn().getRdn().getName() );
count++;
}
assertEquals( 1, count );
cursor.close();
// Finally delete the new entry
connection.delete( newDnString );
}
connection.close();
}
@Test
@Ignore
public void testRenameWithALotOfDummiesAndSomeCustomAttributes() {
CreateLdapConnectionPoolRule connectionPool = new CreateLdapConnectionPoolRule();
LdapConnectionTemplate template = connectionPool.getLdapConnectionTemplate();
AddResponse response = null;
final String peopleOu = "people";
final String peopleRdn = "ou=" + peopleOu;
final String peopleDnString = peopleRdn + "," + BASE;
final Dn peopleDn = template.newDn( peopleDnString );
template.execute(
new ConnectionCallback<Void>() {
@Override
public Void doWithConnection( LdapConnection connection ) throws LdapException {
logger.debug( "Add {}", peopleDnString );
connection.add( new DefaultEntry( peopleDn,
"objectClass", "top",
"objectClass", "organizationalUnit",
"ou", peopleOu ) );
logger.debug( "Add {} dummy people", DUMMY_COUNT );
for ( int i = 1; i < DUMMY_COUNT; i++ )
{
String uid = "uid-" + i;
String dn = "uid=" + uid + "," + peopleDn;
connection.add( new DefaultEntry( dn,
"objectClass: top",
"objectClass: person",
"objectClass: organizationalPerson",
"objectClass: inetOrgPerson",
"uid", uid,
"cn", "cn-" + i,
"sn", "sn-" + i,
"description", i + " is a person." ) );
if ( i % 50 == 0 )
{
logger.debug( "Added person {}", i );
}
}
return null;
}
} );
for ( int i = 0; i < LOOP_COUNT; i++ ) {
logger.info( "round {}", i );
final String oldUid = "myra-ellen-amos";
final String oldCn = "Myra Ellen Amos";
final String oldRdn = "uid=" + oldUid;
final String oldDnString = oldRdn + ", " + peopleDnString;
final Dn oldDn = template.newDn( oldDnString );
final String newUid = "tory-amos";
final String newRdn = "uid=" + newUid;
final String newDnString = newRdn + "," + peopleDnString;
final Dn newDn = template.newDn( newDnString );
response = template.add( oldDn,
new RequestBuilder<AddRequest>() {
@Override
public void buildRequest( AddRequest request ) throws LdapException {
request.getEntry()
.add( "objectClass", "top", "person", "organizationalPerson", "inetOrgPerson", "portalPerson" )
.add( "uid", oldUid )
.add( "cn", oldCn )
.add( "sn", "Amos" )
.add( "active", Boolean.TRUE.toString() )
.add( "affiliation", "Unknown" )
.add( "timeZone", "America/New_York" )
.add( "description", oldCn + " is a person." );
}
} );
assertEquals( response.getLdapResult().getDiagnosticMessage(), ResultCodeEnum.SUCCESS, response.getLdapResult().getResultCode() );
assertNotNull( template.lookup( oldDn, DEFAULT_ENTRY_MAPPER ) );
Entry found = template.searchFirst( peopleDn, FilterBuilder.equal( "sn", "amos" ),
SearchScope.ONELEVEL, DEFAULT_ENTRY_MAPPER );
assertNotNull( found );
Rdn foundRdn = found.getDn().getRdn();
assertEquals( "uid", foundRdn.getType() );
assertEquals( oldUid, foundRdn.getValue() );
template.execute(
new ConnectionCallback<Void>() {
@Override
public Void doWithConnection( LdapConnection connection ) throws LdapException {
connection.rename( oldDnString, newRdn );
return null;
}
} );
assertNull( template.lookup( oldDn, DEFAULT_ENTRY_MAPPER ) );
assertNotNull( template.lookup( newDn, DEFAULT_ENTRY_MAPPER ) );
found = template.searchFirst( peopleDn, FilterBuilder.equal( "sn", "amos" ),
SearchScope.ONELEVEL, DEFAULT_ENTRY_MAPPER );
foundRdn = found.getDn().getRdn();
assertNotNull( found );
foundRdn = found.getDn().getRdn();
assertEquals( "uid", foundRdn.getType() );
assertEquals( newUid, foundRdn.getValue() );
template.delete( newDn );
}
}
/**
* Modify Rdn of an entry, delete its old rdn value and search before and
* after rename.
*/
@Test
public void testModifyRdnWithLotsOfDummies() throws Exception
{
String base = "dc=example,dc=com";
String people = "people";
String ouPeople = "ou=" + people;
String dnPeople = ouPeople + "," + base;
try (LdapConnection connection = ServerIntegrationUtils.getAdminConnection( getLdapServer() );)
{
connection.loadSchema();
logger.debug( "Add {}", dnPeople );
connection.add( new DefaultEntry( dnPeople,
"objectClass", "top",
"objectClass", "organizationalUnit",
"ou", people ) );
logger.debug( "Add {} dummy people", DUMMY_COUNT );
for ( int i = 1; i < DUMMY_COUNT; i++ )
{
String uid = "uid-" + i;
String dn = "uid=" + uid + "," + dnPeople;
connection.add( new DefaultEntry( dn,
"objectClass: top",
"objectClass: person",
"objectClass: organizationalPerson",
"objectClass: inetOrgPerson",
"uid", uid,
"cn", "cn-" + i,
"sn", "sn-" + i,
"description", i + " is a person." ) );
if ( i % 50 == 0 )
{
logger.debug( "Added person {}", i );
}
}
// Create a person, cn value is rdn
String oldUid = "mary-ellen-amos";
String oldCn = "Myra Ellen Amos";
String oldRdn = "uid=" + oldUid;
// Renamed...
String newUid = "tory-amos";
String newRdn = "uid=" + newUid;
String newDn = newRdn + "," + base;
String oldDn = oldRdn + ", " + base;
int i = 0;
try
{
for ( ; i < LOOP_COUNT; i++ )
{
rename( connection, base, oldDn, oldUid, oldCn, newRdn, newUid, newDn );
}
}
catch ( LdapException le )
{
System.out.println( "Error at loop " + i );
try
{
rename( connection, base, oldDn, oldUid, oldCn, newRdn, newUid, newDn );
}
catch ( LdapException le2 )
{
le.printStackTrace();
}
}
}
}
private void rename( LdapConnection connection, String base, String oldDn, String oldUid, String oldCn,
String newRdn, String newUid, String newDn ) throws LdapException
{
connection.add( new DefaultEntry( oldDn,
"objectClass: top",
"objectClass: person",
"objectClass: organizationalPerson",
"objectClass: inetOrgPerson",
"uid", oldUid,
"cn", oldCn,
"sn: Amos",
"description", oldCn + " is a person." ) );
Entry tori = connection.lookup( oldDn );
assertNotNull( tori );
assertTrue( tori.contains( "uid", "mary-ellen-amos" ) );
connection.rename( oldDn, newRdn, true );
assertNull( connection.lookup( oldDn ) );
tori = connection.lookup( newDn );
assertNotNull( tori );
// Check values of uid
assertTrue( tori.contains( "uid", newUid ) );
assertFalse( tori.contains( "uid", oldUid ) ); // old value is gone
assertEquals( 1, tori.get( "uid" ).size() );
// now try a search
Entry found = null;
for ( Entry result : connection.search( base, "(sn=amos)", SearchScope.ONELEVEL ) )
{
if ( found == null )
{
found = result;
}
else
{
fail( "Found too many results" );
}
}
assertNotNull( found );
Rdn foundRdn = found.getDn().getRdn();
assertEquals( "uid", foundRdn.getType() );
assertEquals( newUid, foundRdn.getValue() );
// Remove entry (use new rdn)
connection.delete( newDn );
}
}