/* * 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 2008-2010 Sun Microsystems, Inc. * Portions Copyright 2011-2015 ForgeRock AS */ package org.opends.server.replication.plugin; import java.util.LinkedList; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; import org.assertj.core.api.Assertions; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.opendj.ldap.Assertion; import org.forgerock.opendj.config.server.ConfigException; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.ldap.ConditionResult; import org.forgerock.opendj.ldap.schema.CoreSchema; import org.forgerock.opendj.ldap.schema.MatchingRule; import org.forgerock.opendj.ldap.schema.SchemaBuilder; import org.opends.server.TestCaseUtils; import org.opends.server.admin.std.meta.ReplicationDomainCfgDefn.AssuredType; import org.opends.server.core.DirectoryServer; import org.opends.server.replication.ReplicationTestCase; import org.opends.server.replication.common.CSN; import org.opends.server.replication.protocol.*; import org.opends.server.replication.server.ReplServerFakeConfiguration; import org.opends.server.replication.server.ReplicationServer; import org.opends.server.replication.service.ReplicationBroker; import org.opends.server.types.*; import org.opends.server.util.TimeThread; import org.testng.annotations.Test; import static org.opends.server.TestCaseUtils.*; import static org.testng.Assert.*; /** * Test the usage of the historical data of the replication. */ @SuppressWarnings("javadoc") public class HistoricalCsnOrderingTest extends ReplicationTestCase { private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); private final int serverId = 123; private final SortedSet<String> replServers = new TreeSet<>(); private static class TestBroker extends ReplicationBroker { private final List<ReplicationMsg> list; private TestBroker(List<ReplicationMsg> list) { super(new DummyReplicationDomain(0), null, new DomainFakeCfg(null, 0, new TreeSet<String>()), null); this.list = list; } @Override public void publishRecovery(ReplicationMsg msg) { list.add(msg); } } private MatchingRule getRule() { String oid = "1.3.6.1.4.1.26027.1.4.4"; return new SchemaBuilder(CoreSchema.getInstance()) .buildMatchingRule(oid).names("historicalCsnOrderingMatch") .syntaxOID("1.3.6.1.4.1.1466.115.121.1.40") .implementation(new HistoricalCsnOrderingMatchingRuleImpl()) .addToSchema() .toSchema().getMatchingRule(oid); } /** * Check the basic comparator on the HistoricalCsnOrderingMatchingRule. */ @Test public void basicRuleTest() throws Exception { // Creates a rule MatchingRule rule = getRule(); CSN del1 = new CSN(1, 0, 1); CSN del2 = new CSN(1, 1, 1); ByteString v1 = ByteString.valueOfUtf8("a:" + del1); ByteString v2 = ByteString.valueOfUtf8("a:" + del2); Assertion assert1 = rule.getAssertion(v2); assertEquals(assert1.matches(rule.normalizeAttributeValue(v1)), ConditionResult.TRUE); assertEquals(assert1.matches(rule.normalizeAttributeValue(v2)), ConditionResult.FALSE); Assertion assert2 = rule.getLessOrEqualAssertion(v2); assertEquals(assert2.matches(rule.normalizeAttributeValue(v1)), ConditionResult.TRUE); assertEquals(assert2.matches(rule.normalizeAttributeValue(v2)), ConditionResult.TRUE); Assertion assert3 = rule.getGreaterOrEqualAssertion(v2); assertEquals(assert3.matches(rule.normalizeAttributeValue(v1)), ConditionResult.FALSE); assertEquals(assert3.matches(rule.normalizeAttributeValue(v2)), ConditionResult.TRUE); } /** * Test that we can retrieve the entries that were missed by * a replication server and can re-build operations from the historical * informations. */ @Test public void buildAndPublishMissingChangesOneEntryTest() throws Exception { final int serverId = 123; final DN baseDN = DN.valueOf(TEST_ROOT_DN_STRING); TestCaseUtils.initializeTestBackend(true); ReplicationServer rs = createReplicationServer(); // Create Replication Server and Domain LDAPReplicationDomain rd1 = createReplicationDomain(serverId); try { long startTime = TimeThread.getTime(); final DN dn1 = DN.valueOf("cn=test1," + baseDN); final AttributeType histType = DirectoryServer.getAttributeTypeOrNull(EntryHistorical.HISTORICAL_ATTRIBUTE_NAME); logger.error(LocalizableMessage.raw("Starting replication test : changesCmpTest")); // Add the first test entry. TestCaseUtils.addEntry( "dn: cn=test1," + baseDN, "displayname: Test1", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "cn: test1", "sn: test" ); // Perform a first modification to update the historical attribute int resultCode = TestCaseUtils.applyModifications(false, "dn: cn=test1," + baseDN, "changetype: modify", "add: description", "description: foo"); assertEquals(resultCode, 0); // Read the entry back to get its historical and included CSN Entry entry = DirectoryServer.getEntry(dn1); List<Attribute> attrs1 = entry.getAttribute(histType); Assertions.assertThat(attrs1).isNotEmpty(); String histValue = attrs1.get(0).iterator().next().toString(); logger.error(LocalizableMessage.raw("First historical value:" + histValue)); // Perform a 2nd modification to update the hist attribute with // a second value resultCode = TestCaseUtils.applyModifications(false, "dn: cn=test1," + baseDN, "changetype: modify", "add: description", "description: bar"); assertEquals(resultCode, 0); Entry entry2 = DirectoryServer.getEntry(dn1); List<Attribute> attrs2 = entry2.getAttribute(histType); Assertions.assertThat(attrs2).isNotEmpty(); for (ByteString av : attrs2.get(0)) { logger.error(LocalizableMessage.raw("Second historical value: %s", av)); } LinkedList<ReplicationMsg> opList = new LinkedList<>(); TestBroker session = new TestBroker(opList); CSN csn = new CSN(startTime, 0, serverId); boolean result = rd1.buildAndPublishMissingChanges(csn, session); assertTrue(result, "buildAndPublishMissingChanges has failed"); assertEquals(opList.size(), 3, "buildAndPublishMissingChanges should return 3 operations"); assertEquals(opList.getFirst().getClass(), AddMsg.class); // Build a CSN from the first modification String hv[] = histValue.split(":"); logger.error(LocalizableMessage.raw(hv[1])); CSN fromCSN = new CSN(hv[1]); opList = new LinkedList<>(); session = new TestBroker(opList); result = rd1.buildAndPublishMissingChanges(fromCSN, session); assertTrue(result, "buildAndPublishMissingChanges has failed"); assertEquals(opList.size(), 1, "buildAndPublishMissingChanges should return 1 operation"); assertEquals(opList.getFirst().getClass(), ModifyMsg.class); } finally { MultimasterReplication.deleteDomain(baseDN); remove(rs); } } /** * Test that we can retrieve the entries that were missed by * a replication server and can re-build operations from the historical * informations. */ @Test public void buildAndPublishMissingChangesSeveralEntriesTest() throws Exception { final DN baseDN = DN.valueOf(TEST_ROOT_DN_STRING); TestCaseUtils.initializeTestBackend(true); ReplicationServer rs = createReplicationServer(); // Create Replication Server and Domain LDAPReplicationDomain rd1 = createReplicationDomain(serverId); long startTime = TimeThread.getTime(); try { logger.error(LocalizableMessage.raw("Starting replication test : changesCmpTest")); // Add 3 entries. DN dnTest1 = DN.valueOf("cn=test1," + baseDN); DN dnTest2 = DN.valueOf("cn=test2," + baseDN); DN dnTest3 = DN.valueOf("cn=test3," + baseDN); TestCaseUtils.addEntry( "dn: " + dnTest3, "displayname: Test1", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "cn: test1", "sn: test" ); TestCaseUtils.addEntry( "dn: " + dnTest1, "displayname: Test1", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "cn: test1", "sn: test" ); TestCaseUtils.deleteEntry(dnTest3); TestCaseUtils.addEntry( "dn: " + dnTest2, "displayname: Test1", "objectClass: top", "objectClass: person", "objectClass: organizationalPerson", "objectClass: inetOrgPerson", "cn: test1", "sn: test" ); // Perform modifications on the 2 entries int resultCode = TestCaseUtils.applyModifications(false, "dn: cn=test2," + baseDN, "changetype: modify", "add: description", "description: foo"); resultCode = TestCaseUtils.applyModifications(false, "dn: cn=test1," + baseDN, "changetype: modify", "add: description", "description: foo"); assertEquals(resultCode, 0); LinkedList<ReplicationMsg> opList = new LinkedList<>(); TestBroker session = new TestBroker(opList); // Call the buildAndPublishMissingChanges and check that this method // correctly generates the 4 operations in the correct order. CSN csn = new CSN(startTime, 0, serverId); boolean result = rd1.buildAndPublishMissingChanges(csn, session); assertTrue(result, "buildAndPublishMissingChanges has failed"); assertEquals(opList.size(), 5, "buildAndPublishMissingChanges should return 5 operations"); ReplicationMsg msg = opList.removeFirst(); assertEquals(msg.getClass(), AddMsg.class); assertEquals(((LDAPUpdateMsg) msg).getDN(), dnTest1); msg = opList.removeFirst(); assertEquals(msg.getClass(), DeleteMsg.class); assertEquals(((LDAPUpdateMsg) msg).getDN(), dnTest3); msg = opList.removeFirst(); assertEquals(msg.getClass(), AddMsg.class); assertEquals(((LDAPUpdateMsg) msg).getDN(), dnTest2); msg = opList.removeFirst(); assertEquals(msg.getClass(), ModifyMsg.class); assertEquals(((LDAPUpdateMsg) msg).getDN(), dnTest2); msg = opList.removeFirst(); assertEquals(msg.getClass(), ModifyMsg.class); assertEquals(((LDAPUpdateMsg) msg).getDN(), dnTest1); } finally { MultimasterReplication.deleteDomain(baseDN); rs.remove(); } } private ReplicationServer createReplicationServer() throws Exception { int rsPort = TestCaseUtils.findFreePort(); replServers.add("localhost:" + rsPort); ReplicationServer replicationServer = new ReplicationServer( new ReplServerFakeConfiguration(rsPort, "HistoricalCsnOrdering", 0, 1, 0, 100, replServers, 1, 1000, 5000)); clearChangelogDB(replicationServer); return replicationServer; } private LDAPReplicationDomain createReplicationDomain(int dsId) throws DirectoryException, ConfigException { final DN baseDN = DN.valueOf(TEST_ROOT_DN_STRING); final DomainFakeCfg domainConf = new DomainFakeCfg( baseDN, dsId, replServers, AssuredType.NOT_ASSURED, 2, 1, 0, null); LDAPReplicationDomain replicationDomain = MultimasterReplication.createNewDomain(domainConf); replicationDomain.start(); return replicationDomain; } }