/* * 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 legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * 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 legal-notices/CDDLv1_0.txt. * 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 2015 ForgeRock AS */ package org.opends.server.backends.pluggable; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; import static org.opends.server.ConfigurationMock.*; import static org.opends.server.util.CollectionUtils.*; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import org.forgerock.opendj.config.server.ConfigException; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.util.promise.NeverThrowsException; import org.forgerock.util.promise.PromiseImpl; import org.opends.server.DirectoryServerTestCase; import org.opends.server.TestCaseUtils; import org.opends.server.admin.std.meta.BackendIndexCfgDefn.IndexType; import org.opends.server.admin.std.server.BackendIndexCfg; import org.opends.server.admin.std.server.PDBBackendCfg; import org.opends.server.backends.pdb.PDBStorage; import org.opends.server.backends.pluggable.spi.AccessMode; import org.opends.server.backends.pluggable.spi.Cursor; import org.opends.server.backends.pluggable.spi.ReadOperation; import org.opends.server.backends.pluggable.spi.ReadableTransaction; import org.opends.server.backends.pluggable.spi.SequentialCursor; import org.opends.server.backends.pluggable.spi.TreeName; import org.opends.server.backends.pluggable.spi.WriteOperation; import org.opends.server.backends.pluggable.spi.WriteableTransaction; import org.opends.server.core.DirectoryServer; import org.opends.server.core.MemoryQuota; import org.opends.server.core.ServerContext; import org.opends.server.extensions.DiskSpaceMonitor; import org.opends.server.types.DN; import org.opends.server.types.DirectoryException; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @Test(groups = { "precommit", "pluggablebackend" }, sequential = true) public class DN2IDTest extends DirectoryServerTestCase { private final TreeName dn2IDTreeName = new TreeName("base-dn", "index-id"); private DN baseDN; private DN2ID dn2ID; private PDBStorage storage; // FIXME: This is required since PDBStorage is now using // DirectoryServer static method. @BeforeClass public void startServer() throws Exception { TestCaseUtils.startServer(); } @BeforeMethod public void setUp() throws Exception { ServerContext serverContext = mock(ServerContext.class); when(serverContext.getMemoryQuota()).thenReturn(new MemoryQuota()); when(serverContext.getDiskSpaceMonitor()).thenReturn(mock(DiskSpaceMonitor.class)); storage = new PDBStorage(createBackendCfg(), serverContext); storage.open(AccessMode.READ_WRITE); storage.write(new WriteOperation() { @Override public void run(WriteableTransaction txn) throws Exception { txn.openTree(dn2IDTreeName, true); } }); baseDN = dn("dc=example, dc=com"); dn2ID = new DN2ID(dn2IDTreeName, baseDN); } @AfterMethod public void tearDown() { storage.close(); storage.removeStorageFiles(); } private void populate() throws DirectoryException, Exception { final String[] dns = { "dc=example,dc=com", "ou=Devices,dc=example,dc=com", "cn=dev0,ou=Devices,dc=example,dc=com", "ou=People,dc=example,dc=com", "cn=foo,ou=People,dc=example,dc=com", "cn=barbar,ou=People,dc=example,dc=com", "cn=foofoo,ou=People,dc=example,dc=com", "cn=bar,ou=People,dc=example,dc=com", "cn=dev0,cn=bar,ou=People,dc=example,dc=com", "cn=dev1,cn=bar,ou=People,dc=example,dc=com" }; for (int i = 0; i < dns.length; i++) { put(dn(dns[i]), i + 1); } } @Test public void testCanAddDN() throws Exception { populate(); assertThat(get("dc=example,dc=com")).isEqualTo(id(1)); assertThat(get("ou=People,dc=example,dc=com")).isEqualTo(id(4)); assertThat(get("cn=dev1,cn=bar,ou=People,dc=example,dc=com")).isEqualTo(id(10)); } @Test public void testIsChild() throws DirectoryException, Exception { put(dn("dc=example,dc=com"), 1); put(dn("ou=People,dc=example,dc=com"), 2); put(dn("uid=user.0,ou=People,dc=example,dc=com"), 3); put(dn("uid=user.1,ou=People,dc=example,dc=com"), 4); put(dn("uid=user.10,ou=People,dc=example,dc=com"), 5); storage.read(new ReadOperation<Void>() { @Override public Void run(ReadableTransaction txn) throws Exception { try (final Cursor<ByteString, ByteString> cursor = txn.openCursor(dn2ID.getName())) { cursor.next(); final ByteString rootDN = cursor.getKey(); cursor.next(); final ByteString parentDN = cursor.getKey(); cursor.next(); assertThat(DN2ID.isChild(rootDN, parentDN)).isTrue(); final ByteString childDN = cursor.getKey(); assertThat(DN2ID.isChild(parentDN, childDN)).isTrue(); cursor.next(); final ByteString otherChildDN = cursor.getKey(); assertThat(DN2ID.isChild(parentDN, otherChildDN)).isTrue(); assertThat(DN2ID.isChild(childDN, otherChildDN)).isFalse(); final ByteString lastChildDN = cursor.getKey(); assertThat(DN2ID.isChild(parentDN, lastChildDN)).isTrue(); assertThat(DN2ID.isChild(otherChildDN, lastChildDN)).isFalse(); assertThat(DN2ID.isChild(childDN, lastChildDN)).isFalse(); } return null; } }); } @Test public void testGetNonExistingDNReturnNull() throws Exception { assertThat(get("dc=non,dc=existing")).isNull(); } @Test public void testCanRemove() throws Exception { populate(); assertThat(get("ou=People,dc=example,dc=com")).isNotNull(); assertThat(remove("ou=People,dc=example,dc=com")).isTrue(); assertThat(get("ou=People,dc=example,dc=com")).isNull(); } @Test public void testRemoveNonExistingEntry() throws Exception { assertThat(remove("dc=non,dc=existing")).isFalse(); } @Test public void testTraverseChildren() throws Exception { populate(); assertThat(traverseChildren("ou=People,dc=example,dc=com")) .containsExactly( get("cn=bar,ou=People,dc=example,dc=com"), get("cn=barbar,ou=People,dc=example,dc=com"), get("cn=foo,ou=People,dc=example,dc=com"), get("cn=foofoo,ou=People,dc=example,dc=com")); } @Test public void testTraverseSubordinates() throws Exception { populate(); assertThat(traverseSubordinates("ou=People,dc=example,dc=com")) .containsExactly( get("cn=bar,ou=People,dc=example,dc=com"), get("cn=dev0,cn=bar,ou=People,dc=example,dc=com"), get("cn=dev1,cn=bar,ou=People,dc=example,dc=com"), get("cn=barbar,ou=People,dc=example,dc=com"), get("cn=foo,ou=People,dc=example,dc=com"), get("cn=foofoo,ou=People,dc=example,dc=com")); } private EntryID get(final String dn) throws Exception { return storage.read(new ReadOperation<EntryID>() { @Override public EntryID run(ReadableTransaction txn) throws Exception { return dn2ID.get(txn, dn(dn)); } }); } private List<EntryID> traverseChildren(final String dn) throws Exception { return storage.read(new ReadOperation<List<EntryID>>() { @Override public List<EntryID> run(ReadableTransaction txn) throws Exception { try (final SequentialCursor<Void, EntryID> cursor = dn2ID.openChildrenCursor(txn, dn(dn))) { return getAllIDs(cursor); } } }); } private List<EntryID> traverseSubordinates(final String dn) throws Exception { return storage.read(new ReadOperation<List<EntryID>>() { @Override public List<EntryID> run(ReadableTransaction txn) throws Exception { try (final SequentialCursor<Void, EntryID> cursor = dn2ID.openSubordinatesCursor(txn, dn(dn))) { return getAllIDs(cursor); } } }); } private static <K, V> List<V> getAllIDs(SequentialCursor<K, V> cursor) { final List<V> values = new ArrayList<>(); while(cursor.next()) { values.add(cursor.getValue()); } return values; } private void put(final DN dn, final long id) throws Exception { storage.write(new WriteOperation() { @Override public void run(WriteableTransaction txn) throws Exception { dn2ID.put(txn, dn, new EntryID(id)); } }); } private boolean remove(final String dn) throws Exception { final PromiseImpl<Boolean, NeverThrowsException> p = PromiseImpl.create(); storage.write(new WriteOperation() { @Override public void run(WriteableTransaction txn) throws Exception { p.handleResult(dn2ID.remove(txn, dn(dn))); } }); return p.get(10, TimeUnit.SECONDS); } private static DN dn(String dn) throws DirectoryException { return DN.valueOf(dn); } private static EntryID id(long id) { return new EntryID(id); } private static PDBBackendCfg createBackendCfg() throws ConfigException, DirectoryException { String homeDirName = "pdb_test"; PDBBackendCfg backendCfg = legacyMockCfg(PDBBackendCfg.class); when(backendCfg.getBackendId()).thenReturn("persTest" + homeDirName); when(backendCfg.getDBDirectory()).thenReturn(homeDirName); when(backendCfg.getDBDirectoryPermissions()).thenReturn("755"); when(backendCfg.getDBCacheSize()).thenReturn(0L); when(backendCfg.getDBCachePercent()).thenReturn(20); when(backendCfg.getBaseDN()).thenReturn(newTreeSet(DN.valueOf("dc=test,dc=com"))); when(backendCfg.dn()).thenReturn(DN.valueOf("dc=test,dc=com")); when(backendCfg.listBackendIndexes()).thenReturn(new String[] { "sn" }); when(backendCfg.listBackendVLVIndexes()).thenReturn(new String[0]); BackendIndexCfg indexCfg = legacyMockCfg(BackendIndexCfg.class); when(indexCfg.getIndexType()).thenReturn(newTreeSet(IndexType.PRESENCE, IndexType.EQUALITY)); when(indexCfg.getAttribute()).thenReturn(DirectoryServer.getAttributeTypeOrNull("sn")); when(backendCfg.getBackendIndex("sn")).thenReturn(indexCfg); return backendCfg; } }