| | |
| | | import java.util.LinkedHashSet; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.UUID; |
| | | |
| | | |
| | | import org.testng.annotations.AfterClass; |
| | | import org.testng.annotations.BeforeClass; |
| | | import org.testng.annotations.Test; |
| | | import static org.testng.Assert.*; |
| | | |
| | | import static org.opends.server.synchronization.OperationContext.*; |
| | | |
| | | import org.opends.server.SchemaFixture; |
| | | import org.opends.server.core.AddOperation; |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.core.ModifyOperation; |
| | | import org.opends.server.protocols.asn1.ASN1OctetString; |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.types.Attribute; |
| | | import org.opends.server.types.AttributeType; |
| | |
| | | import org.opends.server.types.ObjectClass; |
| | | |
| | | /* |
| | | * Test the conflict resolution for modify operations |
| | | * This is still a work in progress. |
| | | * currently implemented tests |
| | | * - check that an replace with a smaller csn is ignored |
| | | * should test : |
| | | * - conflict with multi-valued attributes |
| | | * - conflict with single-valued attributes |
| | | * - conflict with options |
| | | * - conflict with binary attributes |
| | | * - Replace, add, delete attribute, delete attribute value |
| | | * - conflict on the objectclass attribute |
| | | * Test the conflict resolution for modify operations As a consequence, |
| | | * this will also test the Historical.java Class This is still a work in |
| | | * progress. currently implemented tests - check that an replace with a |
| | | * smaller csn is ignored should test : - conflict with multi-valued |
| | | * attributes - conflict with single-valued attributes - conflict with |
| | | * options - conflict with binary attributes - Replace, add, delete |
| | | * attribute, delete attribute value - conflict on the objectclass |
| | | * attribute |
| | | */ |
| | | |
| | | |
| | | public class ModifyConflictTest extends SynchronizationTestCase |
| | | public class ModifyConflictTest |
| | | extends SynchronizationTestCase |
| | | { |
| | | |
| | | /** |
| | | * Test that conflict between a modify-replace and modify-add |
| | | * for multi-valued attributes are handled correctly. |
| | | * Test that conflict between a modify-replace and modify-add for |
| | | * multi-valued attributes@DataProvider(name = "ackMsg") are handled |
| | | * correctly. |
| | | */ |
| | | @Test() |
| | | public void replaceAndAdd() |
| | | throws Exception |
| | | public void replaceAndAdd() throws Exception |
| | | { |
| | | /* |
| | | * Objectclass and DN do not have any impact on the modifty conflict |
| | | * resolution for the description attribute. |
| | | * Always use the same values for all these tests. |
| | | */ |
| | | DN dn = DN.decode("dc=com"); |
| | | Map<ObjectClass, String> objectClasses = new HashMap<ObjectClass, String>(); |
| | | ObjectClass org = DirectoryServer.getObjectClass("organization"); |
| | | objectClasses.put(org, "organization"); |
| | | |
| | | /* |
| | | * start with a new entry with an empty description |
| | | */ |
| | | Entry entry = new Entry(dn, objectClasses, null, null); |
| | | |
| | | // Construct a new random UUID. and add it into the entry |
| | | UUID uuid = UUID.randomUUID(); |
| | | |
| | | // Create the att values list of uuid |
| | | LinkedHashSet<AttributeValue> valuesUuid = new LinkedHashSet<AttributeValue>( |
| | | 1); |
| | | valuesUuid.add(new AttributeValue(Historical.entryuuidAttrType, |
| | | new ASN1OctetString(uuid.toString()))); |
| | | ArrayList<Attribute> uuidList = new ArrayList<Attribute>(1); |
| | | Attribute uuidAttr = new Attribute(Historical.entryuuidAttrType, |
| | | "entryUUID", valuesUuid); |
| | | uuidList.add(uuidAttr); |
| | | |
| | | /* |
| | | * Add the uuid in the entry |
| | | */ |
| | | Map<AttributeType, List<Attribute>> operationalAttributes = entry |
| | | .getOperationalAttributes(); |
| | | operationalAttributes.put(Historical.entryuuidAttrType, uuidList); |
| | | |
| | | // Create the att values list of historicalAttr |
| | | String stringVal = |
| | | "ds-sync-hist:00000108b3a6cbb800000001:repl:00000108b3a6cbb800000002"; |
| | | |
| | | AttributeValue val = new AttributeValue(Historical.historicalAttrType, |
| | | stringVal); |
| | | LinkedHashSet<AttributeValue> valuesHist = |
| | | new LinkedHashSet<AttributeValue>(1); |
| | | valuesHist.add(val); |
| | | ArrayList<Attribute> histList = new ArrayList<Attribute>(1); |
| | | Attribute histAttr = new Attribute(Historical.historicalAttrType, |
| | | "ds-sync-hist", valuesHist); |
| | | histList.add(histAttr); |
| | | |
| | | //Add the historical att in the entry |
| | | operationalAttributes.put(Historical.historicalAttrType,histList) ; |
| | | |
| | | // load historical from the entry |
| | | Historical hist = Historical.load(entry); |
| | | |
| | | /* |
| | | * simulate a modify-replace done at time t10 |
| | | */ |
| | | testModify(entry, hist, "description", ModificationType.REPLACE, |
| | | "init value", 10, true); |
| | | "init value", 10, true); |
| | | |
| | | /* |
| | | * Now simulate an add at an earlier date that the previous replace |
| | | * conflict resolution should remove it. |
| | | */ |
| | | testModify(entry, hist, "description", ModificationType.ADD, |
| | | "older value", 1, false); |
| | | "older value", 1, false); |
| | | |
| | | /* |
| | | * Now simulate an add at an earlier date that the previous replace |
| | | * conflict resolution should remove it. |
| | | * (a second time to make sure...) |
| | | * conflict resolution should remove it. (a second time to make |
| | | * sure...) |
| | | */ |
| | | testModify(entry, hist, "description", ModificationType.ADD, |
| | | "older value", 2, false); |
| | | "older value", 2, false);; |
| | | |
| | | /* |
| | | * Now simulate an add at a later date that the previous replace. |
| | | * conflict resolution should keep it |
| | | */ |
| | | testModify(entry, hist, "description", ModificationType.ADD, |
| | | "new value", 11, true); |
| | | testModify(entry, hist, "description", ModificationType.ADD, "new value", |
| | | 11, true); |
| | | |
| | | } |
| | | |
| | | /* |
| | | * helper function. |
| | | */ |
| | | private void testHistoricalAndFake( |
| | | Historical hist, Entry entry) |
| | | { |
| | | |
| | | // Get the historical uuid associated to the entry |
| | | // (the one that needs to be tested) |
| | | String uuid = hist.getEntryUuid(entry); |
| | | |
| | | // Get the Entry uuid in String format |
| | | List<Attribute> uuidAttrs = entry |
| | | .getOperationalAttribute(Historical.entryuuidAttrType); |
| | | uuidAttrs.get(0).getValues().iterator().next().toString(); |
| | | |
| | | if (uuidAttrs != null) |
| | | { |
| | | if (uuidAttrs.size() > 0) |
| | | { |
| | | Attribute att = uuidAttrs.get(0); |
| | | String retrievedUuid = (att.getValues().iterator().next()).toString(); |
| | | assertTrue(retrievedUuid.equals(uuid)); |
| | | } |
| | | } |
| | | |
| | | try |
| | | { |
| | | Historical dup = hist.duplicate(); |
| | | // TODO Check values |
| | | } |
| | | catch (RuntimeException e) |
| | | { |
| | | assertTrue(false) ; |
| | | } |
| | | |
| | | |
| | | // Test FakeOperation |
| | | try |
| | | { |
| | | Iterable<FakeOperation> fks = Historical.generateFakeOperations(entry); |
| | | if (fks.iterator().hasNext()) |
| | | { |
| | | FakeOperation fk = fks.iterator().next(); |
| | | assertTrue(new FakeOperationComparator().compare(fk, fk) == 0); |
| | | assertTrue(new FakeOperationComparator().compare(null , fk) < 0); |
| | | SynchronizationMessage generatedMsg = fk.generateMessage() ; |
| | | if (generatedMsg instanceof UpdateMessage) |
| | | { |
| | | UpdateMessage new_name = (UpdateMessage) generatedMsg; |
| | | assertEquals(new_name.getUniqueId(),uuid); |
| | | |
| | | } |
| | | |
| | | } |
| | | |
| | | } |
| | | catch (RuntimeException e) |
| | | { |
| | | assertTrue(false) ; |
| | | } |
| | | } |
| | | |
| | | /* |
| | | * helper function. |
| | | */ |
| | | private void testHistorical( |
| | | Historical hist, AddOperation addOp) |
| | | { |
| | | |
| | | // Get the historical uuid associated to the entry |
| | | // (the one that needs to be tested) |
| | | String uuid = hist.getEntryUuid(addOp); |
| | | |
| | | // Get the op uuid in String format |
| | | List<Attribute> uuidAttrs = addOp.getOperationalAttributes().get( |
| | | Historical.entryuuidAttrType); |
| | | uuidAttrs.get(0).getValues().iterator().next().toString(); |
| | | |
| | | if (uuidAttrs != null) |
| | | { |
| | | if (uuidAttrs.size() > 0) |
| | | { |
| | | Attribute att = uuidAttrs.get(0); |
| | | String retrievedUuid = (att.getValues().iterator().next()).toString(); |
| | | assertTrue(retrievedUuid.equals(uuid)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Test that conflict between a modify-delete-attribute and modify-add |
| | | * for multi-valued attributes are handled correctly. |
| | | */ |
| | | @Test() |
| | | public void deleteAndAdd() |
| | | throws Exception |
| | | public void deleteAndAdd() throws Exception |
| | | { |
| | | /* |
| | | * Objectclass and DN do not have any impact on the modifty conflict |
| | | * resolution for the description attribute. |
| | | * Always use the same values for all these tests. |
| | | * resolution for the description attribute. Always use the same values |
| | | * for all these tests. |
| | | */ |
| | | DN dn = DN.decode("dc=com"); |
| | | Map<ObjectClass, String> objectClasses = new HashMap<ObjectClass, String>(); |
| | |
| | | * start with a new entry with an empty description |
| | | */ |
| | | Entry entry = new Entry(dn, objectClasses, null, null); |
| | | |
| | | // Construct a new random UUID. and add it into the entry |
| | | UUID uuid = UUID.randomUUID(); |
| | | |
| | | // Create the att values list |
| | | LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>( |
| | | 1); |
| | | values.add(new AttributeValue(Historical.entryuuidAttrType, |
| | | new ASN1OctetString(uuid.toString()))); |
| | | ArrayList<Attribute> uuidList = new ArrayList<Attribute>(1); |
| | | Attribute uuidAttr = new Attribute(Historical.entryuuidAttrType, |
| | | "entryUUID", values); |
| | | uuidList.add(uuidAttr); |
| | | |
| | | /* |
| | | * Add the uuid in the entry |
| | | */ |
| | | Map<AttributeType, List<Attribute>> operationalAttributes = entry |
| | | .getOperationalAttributes(); |
| | | |
| | | operationalAttributes.put(Historical.entryuuidAttrType, uuidList); |
| | | |
| | | // Create the att values list of historicalAttr |
| | | String stringVal = |
| | | "ds-sync-hist:00000108b3a6cbb800000001:del:00000108b3a6cbb800000002"; |
| | | |
| | | AttributeValue val = new AttributeValue(Historical.historicalAttrType, |
| | | stringVal); |
| | | LinkedHashSet<AttributeValue> valuesHist = |
| | | new LinkedHashSet<AttributeValue>(1); |
| | | valuesHist.add(val); |
| | | ArrayList<Attribute> histList = new ArrayList<Attribute>(1); |
| | | Attribute histAttr = new Attribute(Historical.historicalAttrType, |
| | | "ds-sync-hist", valuesHist); |
| | | |
| | | //Add the historical att in the entry |
| | | entry.putAttribute(Historical.historicalAttrType,histList) ; |
| | | |
| | | // load historical from the entry |
| | | |
| | | Historical hist = Historical.load(entry); |
| | | |
| | | /* |
| | | * simulate a delete of the whole description attribute done at time t10 |
| | | * simulate a delete of the whole description attribute done at time |
| | | * t10 |
| | | */ |
| | | testModify(entry, hist, "description", ModificationType.DELETE, |
| | | null, 10, true); |
| | | testModify(entry, hist, "description", ModificationType.DELETE, null, 10, |
| | | true); |
| | | |
| | | /* |
| | | * Now simulate an add at an earlier date that the previous delete. |
| | | * The conflict resolution should detect that this add must be ignored. |
| | | */ |
| | | testModify(entry, hist, "description", ModificationType.ADD, |
| | | "older value", 1, false); |
| | | |
| | | /* |
| | | * Now simulate an add at an earlier date that the previous delete. |
| | | * The conflict resolution should detect that this add must be ignored. |
| | | * (a second time to make sure that historical information is kept...) |
| | | * Now simulate an add at an earlier date that the previous delete. The |
| | | * conflict resolution should detect that this add must be ignored. |
| | | */ |
| | | testModify(entry, hist, "description", ModificationType.ADD, |
| | | "older value", 2, false); |
| | | "older value", 1, false); |
| | | |
| | | /* |
| | | * Now simulate an add at an earlier date that the previous delete. The |
| | | * conflict resolution should detect that this add must be ignored. (a |
| | | * second time to make sure that historical information is kept...) |
| | | */ |
| | | testModify(entry, hist, "description", ModificationType.ADD, |
| | | "older value", 2, false); |
| | | |
| | | /* |
| | | * Now simulate an add at a later date that the previous delete. |
| | | * conflict resolution should keep it |
| | | */ |
| | | testModify(entry, hist, "description", ModificationType.ADD, |
| | | "new value", 11, true); |
| | | testModify(entry, hist, "description", ModificationType.ADD, "new value", |
| | | 11, true); |
| | | |
| | | } |
| | | |
| | | /** |
| | | * Test that conflict between a modify-add and modify-add |
| | | * for multi-valued attributes are handled correctly. |
| | | */ |
| | | @Test() |
| | | public void addAndAdd() |
| | | throws Exception |
| | | { |
| | | /* |
| | | * Objectclass and DN do not have any impact on the modifty conflict |
| | | * resolution for the description attribute. |
| | | * Always use the same values for all these tests. |
| | | */ |
| | | DN dn = DN.decode("dc=com"); |
| | | Map<ObjectClass, String> objectClasses = new HashMap<ObjectClass, String>(); |
| | | ObjectClass org = DirectoryServer.getObjectClass("organization"); |
| | | objectClasses.put(org, "organization"); |
| | | * Test that conflict between a modify-add and modify-add for |
| | | * multi-valued attributes are handled correctly. |
| | | */ |
| | | @Test() |
| | | public void addAndAdd() throws Exception |
| | | { |
| | | /* |
| | | * Objectclass and DN do not have any impact on the modifty conflict |
| | | * resolution for the description attribute. Always use the same values |
| | | * for all these tests. |
| | | */ |
| | | DN dn = DN.decode("dc=com"); |
| | | Map<ObjectClass, String> objectClasses = new HashMap<ObjectClass, String>(); |
| | | ObjectClass org = DirectoryServer.getObjectClass("organization"); |
| | | objectClasses.put(org, "organization"); |
| | | |
| | | /* |
| | | * start with a new entry with an empty description |
| | | */ |
| | | Entry entry = new Entry(dn, objectClasses, null, null); |
| | | Historical hist = Historical.load(entry); |
| | | /* |
| | | * start with a new entry with an empty description |
| | | */ |
| | | Entry entry = new Entry(dn, objectClasses, null, null); |
| | | |
| | | /* |
| | | * simulate a add of the description attribute done at time t10 |
| | | */ |
| | | testModify(entry, hist, "description", ModificationType.ADD, |
| | | "init value", 10, true); |
| | | // Construct a new random UUID. and add it into the entry |
| | | UUID uuid = UUID.randomUUID(); |
| | | |
| | | /* |
| | | * Now simulate an add at an earlier date that the previous add. |
| | | * The conflict resolution should detect that this add must be kept. |
| | | */ |
| | | testModify(entry, hist, "description", ModificationType.ADD, |
| | | "older value", 1, true); |
| | | // Create the att values list |
| | | LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>( |
| | | 1); |
| | | values.add(new AttributeValue(Historical.entryuuidAttrType, |
| | | new ASN1OctetString(uuid.toString()))); |
| | | ArrayList<Attribute> uuidList = new ArrayList<Attribute>(1); |
| | | Attribute uuidAttr = new Attribute(Historical.entryuuidAttrType, |
| | | "entryUUID", values); |
| | | uuidList.add(uuidAttr); |
| | | |
| | | /* |
| | | * Now simulate an add at an earlier date that the previous add. |
| | | * The conflict resolution should detect that this add must be kept. |
| | | * (a second time to make sure that historical information is kept...) |
| | | */ |
| | | testModify(entry, hist, "description", ModificationType.ADD, |
| | | "older value", 2, false); |
| | | /* |
| | | * Add the uuid in the entry |
| | | */ |
| | | Map<AttributeType, List<Attribute>> operationalAttributes = entry |
| | | .getOperationalAttributes(); |
| | | |
| | | /* |
| | | * Now simulate an add at a later date that the previous add. |
| | | * conflict resolution should keep it |
| | | */ |
| | | testModify(entry, hist, "description", ModificationType.ADD, |
| | | "new value", 11, true); |
| | | } |
| | | operationalAttributes.put(Historical.entryuuidAttrType, uuidList); |
| | | // Create the att values list of historicalAttr |
| | | String stringVal = |
| | | "ds-sync-hist:00000108b3a6cbb800000001:add:00000108b3a6cbb800000002"; |
| | | |
| | | AttributeValue val = new AttributeValue(Historical.historicalAttrType, |
| | | stringVal); |
| | | LinkedHashSet<AttributeValue> valuesHist = |
| | | new LinkedHashSet<AttributeValue>(1); |
| | | valuesHist.add(val); |
| | | ArrayList<Attribute> histList = new ArrayList<Attribute>(1); |
| | | Attribute histAttr = new Attribute(Historical.historicalAttrType, |
| | | "ds-sync-hist", valuesHist); |
| | | histList.add(histAttr); |
| | | |
| | | //Add the historycal att in the entry |
| | | entry.putAttribute(Historical.historicalAttrType,histList) ; |
| | | |
| | | // load historical from the entry |
| | | |
| | | Historical hist = Historical.load(entry); |
| | | |
| | | /* |
| | | * simulate a add of the description attribute done at time t10 |
| | | */ |
| | | testModify(entry, hist, "description", ModificationType.ADD, |
| | | "init value", 10, true); |
| | | /* |
| | | * Now simulate an add at an earlier date that the previous add. The |
| | | * conflict resolution should detect that this add must be kept. |
| | | */ |
| | | testModify(entry, hist, "description", ModificationType.ADD, |
| | | "older value", 1, true); |
| | | |
| | | /* |
| | | * Now simulate an add at an earlier date that the previous add. The |
| | | * conflict resolution should detect that this add must be kept. (a |
| | | * second time to make sure that historical information is kept...) |
| | | */ |
| | | testModify(entry, hist, "description", ModificationType.ADD, |
| | | "older value", 2, false); |
| | | |
| | | /* |
| | | * Now simulate an add at a later date that the previous add. conflict |
| | | * resolution should keep it |
| | | */ |
| | | testModify(entry, hist, "description", ModificationType.ADD, "new value", |
| | | 11, true); |
| | | } |
| | | |
| | | /* |
| | | * helper function. |
| | | */ |
| | | private static void testModify(Entry entry, |
| | | private void testModify(Entry entry, |
| | | Historical hist, String attrName, |
| | | ModificationType modType, String value, |
| | | int date, boolean keepChangeResult) |
| | |
| | | List<Modification> mods = new ArrayList<Modification>(); |
| | | Modification mod = new Modification(modType, attr); |
| | | mods.add(mod); |
| | | |
| | | |
| | | ModifyOperation modOp = new ModifyOperation(connection, 1, 1, null, |
| | | entry.getDN(), mods); |
| | | entry.getDN(), mods); |
| | | ModifyContext ctx = new ModifyContext(t, "uniqueId"); |
| | | modOp.setAttachment(SYNCHROCONTEXT, ctx); |
| | | |
| | | hist.replayOperation(modOp, entry); |
| | | |
| | | if (modType.intValue() == ModificationType.ADD.intValue()) |
| | | { |
| | | AddOperation addOp = new AddOperation(connection, 1, 1, null, entry |
| | | .getDN(), entry.getObjectClasses(), entry.getUserAttributes(), |
| | | entry.getOperationalAttributes()); |
| | | testHistorical(hist, addOp); |
| | | } |
| | | else |
| | | { |
| | | testHistoricalAndFake(hist, entry); |
| | | } |
| | | /* |
| | | * The last older change should have been detected as conflicting |
| | | * and should be removed by the conflict resolution code. |
| | | * The last older change should have been detected as conflicting and |
| | | * should be removed by the conflict resolution code. |
| | | */ |
| | | if (keepChangeResult) |
| | | { |
| | |
| | | assertEquals(0, mods.size()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Set up the environment for performing the tests in this suite. |
| | | * |
| | | * @throws Exception |
| | | * If the environment could not be set up. |
| | | */ |
| | | @BeforeClass |
| | | public void setUp() throws Exception { |
| | | // This test suite depends on having the schema available. |
| | | SchemaFixture.FACTORY.setUp(); |
| | | } |
| | | |
| | | /** |
| | | * Tears down the environment for performing the tests in this suite. |
| | | * |
| | | * @throws Exception |
| | | * If the environment could not be finalized. |
| | | */ |
| | | @AfterClass |
| | | public void tearDown() throws Exception { |
| | | SchemaFixture.FACTORY.tearDown(); |
| | | } |
| | | } |