This set of changes allow to have the schema synchronization working by
configuring synchronization for suffix cn=schema (issue 613) .
You may want to read the schema Synchronization documents before reviewing this
https://opends.dev.java.net/public/docs/dev-docs/SchemaSyncFeatureRequirements.html and
https://opends.dev.java.net/public/docs/dev-docs/SchemaSyncDesign.html
It includes :
- Change the PersistentServerState to use attribute ds-sync-state in the base entry
instead of a specific entry.
Add new unit test for the PersistentServerState class.
Change attribute ds-sync-state to be an operational attribute.
- Change the schema backend to allow storage of the ds-synch-state attribute in the
schema ldif File.
This change is arguable because I have chosen to make this as simple as possible
and therefore only allowed the storage of this attribute in the schema File.
While this has the advantage of being very simple it has
the drawback of adding some code in the schema backend that is only related to synchronization.
The other choice would be to add a generic service in the schema backend for storing
any type of attribute.
Please tell me if you think that this would be better.
- Disable the conflict resolution for cn=schema so that we don't polute the ldif entries
with the historical information
- Add unit test for schema synchronization
The work for schema synchronization is not complete with this.
A second round of modification is necessary for synchronizing the schema changes
done by manually editing the files or by dynamically loading a file.
2 files added
13 files modified
| | |
| | | SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 |
| | | SINGLE-VALUE X-ORIGIN 'OpenDS Directory Server' ) |
| | | attributeTypes: ( 1.3.6.1.4.1.26027.1.1.227 NAME 'ds-sync-state' |
| | | SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDS Directory Server' ) |
| | | SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 USAGE directoryOperation |
| | | X-ORIGIN 'OpenDS Directory Server' ) |
| | | attributeTypes: ( 1.3.6.1.4.1.26027.1.1.228 |
| | | NAME 'ds-cfg-backup-directory' EQUALITY caseExactMatch |
| | | SUBSTR caseExactSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 |
| | |
| | | // The attribute type that will be used to include the defined name forms. |
| | | private AttributeType nameFormsType; |
| | | |
| | | // The attribute type that will be used to save the synchronization state. |
| | | private AttributeType synchronizationStateType; |
| | | |
| | | // The value containing DN of the user we'll say created the configuration. |
| | | private AttributeValue creatorsName; |
| | | |
| | |
| | | matchingRuleUsesType = |
| | | DirectoryServer.getAttributeType(ATTR_MATCHING_RULE_USE_LC, true); |
| | | nameFormsType = DirectoryServer.getAttributeType(ATTR_NAME_FORMS_LC, true); |
| | | synchronizationStateType = |
| | | DirectoryServer.getAttributeType(ATTR_SYNCHRONIZATION_STATE_LC, true); |
| | | |
| | | |
| | | // Initialize the lastmod attributes. |
| | |
| | | valueSet)); |
| | | operationalAttrs.put(modifyTimestampType, attrList); |
| | | |
| | | // Add the synchronization State attribute. |
| | | valueSet = DirectoryServer.getSchema().getSynchronizationState(); |
| | | attr = new Attribute(synchronizationStateType, |
| | | ATTR_SYNCHRONIZATION_STATE_LC, valueSet); |
| | | attrList = new ArrayList<Attribute>(1); |
| | | attrList.add(attr); |
| | | if (synchronizationStateType.isOperational() && (! showAllAttributes)) |
| | | { |
| | | operationalAttrs.put(synchronizationStateType, attrList); |
| | | } |
| | | else |
| | | { |
| | | userAttrs.put(synchronizationStateType, attrList); |
| | | } |
| | | |
| | | // Add all the user-defined attributes. |
| | | for (Attribute a : userDefinedAttributes) |
| | |
| | | |
| | | |
| | | default: |
| | | int msgID = MSGID_SCHEMA_INVALID_MODIFICATION_TYPE; |
| | | String message = getMessage(msgID, m.getModificationType()); |
| | | throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message, |
| | | msgID); |
| | | if (!modifyOperation.isSynchronizationOperation()) |
| | | { |
| | | int msgID = MSGID_SCHEMA_INVALID_MODIFICATION_TYPE; |
| | | String message = getMessage(msgID, m.getModificationType()); |
| | | throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, |
| | | message, msgID); |
| | | } |
| | | else |
| | | { |
| | | if (at.equals(synchronizationStateType)) |
| | | newSchema.setSynchronizationState(a.getValues()); |
| | | modifiedSchemaFiles.add(FILE_USER_SCHEMA_ELEMENTS); |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | schemaEntry.putAttribute(matchingRuleUsesType, attrList); |
| | | } |
| | | |
| | | if (schemaFile.equals(FILE_USER_SCHEMA_ELEMENTS)) |
| | | { |
| | | values = schema.getSynchronizationState(); |
| | | if (values != null) |
| | | { |
| | | ArrayList<Attribute> attrList = new ArrayList<Attribute>(1); |
| | | attrList.add(new Attribute(matchingRuleUsesType, |
| | | matchingRuleUsesType.getPrimaryName(), |
| | | values)); |
| | | schemaEntry.putAttribute(matchingRuleUsesType, attrList); |
| | | } |
| | | } |
| | | |
| | | // Create a temporary file to which we can write the schema entry. |
| | | File tempFile = File.createTempFile(schemaFile, "temp"); |
| | |
| | | */ |
| | | public static final String ATTR_MATCHING_RULE_USE_LC = "matchingruleuse"; |
| | | |
| | | |
| | | /** |
| | | * The name of the attribute that holds the sycnhronization state, |
| | | * formatted in lowercase. |
| | | */ |
| | | public static final String ATTR_SYNCHRONIZATION_STATE_LC = "ds-sync-state"; |
| | | |
| | | /** |
| | | * The default maximum request size that should be used if none is specified |
| | |
| | | // in the response to the client. |
| | | private StringBuilder errorMessage; |
| | | |
| | | // Indicates whether this operation nneds to be synchronized to |
| | | // other copies of the data. |
| | | private boolean dontSynchronizeFlag; |
| | | |
| | | |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Specifies whether this operation must be synchronized to other copies |
| | | * of the data. |
| | | * |
| | | * @param dontSynchronize Specifies whether this operation must be |
| | | * synchronized to other copies |
| | | * of the data. |
| | | */ |
| | | public final void setDontSynchronize(boolean dontSynchronize) |
| | | { |
| | | assert debugEnter(CLASS_NAME, "setDontSynchronize", |
| | | String.valueOf(dontSynchronize)); |
| | | |
| | | this.dontSynchronizeFlag = dontSynchronize; |
| | | } |
| | | |
| | | /** |
| | | * Retrieves the entry for the user that should be considered the |
| | |
| | | * operation should be appended. |
| | | */ |
| | | public abstract void toString(StringBuilder buffer); |
| | | |
| | | |
| | | /** |
| | | * Indicates whether this operation needs to be synchronized to |
| | | * other copies of the data. |
| | | * |
| | | * @return <CODE>true</CODE> if this operation don't need to be |
| | | * synchronized, or |
| | | * <CODE>false</CODE> if it needs to be synchronized. |
| | | */ |
| | | public boolean dontSynchronize() |
| | | { |
| | | assert debugEnter(CLASS_NAME, "dontSynchronize"); |
| | | |
| | | return dontSynchronizeFlag; |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | List<Attribute> mruList = entry.getAttribute(mruAttrType); |
| | | |
| | | AttributeType synchronizationStateType = |
| | | schema.getAttributeType(ATTR_SYNCHRONIZATION_STATE_LC); |
| | | if (synchronizationStateType == null) |
| | | { |
| | | synchronizationStateType = |
| | | DirectoryServer.getDefaultAttributeType(ATTR_SYNCHRONIZATION_STATE_LC, |
| | | new MatchingRuleUseSyntax()); |
| | | } |
| | | |
| | | List<Attribute> synchronizationState = |
| | | entry.getAttribute(synchronizationStateType); |
| | | if (synchronizationState != null && !(synchronizationState.isEmpty())) |
| | | schema.setSynchronizationState(synchronizationState.get(0).getValues()); |
| | | |
| | | // Parse the attribute type definitions if there are any. |
| | | if (attrList != null) |
| | |
| | | import org.opends.server.core.AddOperation; |
| | | import org.opends.server.synchronization.changelog.Changelog; |
| | | import org.opends.server.synchronization.common.LogMessages; |
| | | import org.opends.server.synchronization.common.ServerState; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.core.DeleteOperation; |
| | | import org.opends.server.types.DirectoryException; |
| | |
| | | private static Map<DN, SynchronizationDomain> domains = |
| | | new HashMap<DN, SynchronizationDomain>() ; |
| | | |
| | | /** |
| | | * Get the ServerState associated to the SynchronizationDomain |
| | | * with a given DN. |
| | | * |
| | | * @param baseDn The DN of the Synchronization Domain for which the |
| | | * ServerState must be returned. |
| | | * @return the ServerState associated to the SynchronizationDomain |
| | | * with the DN in parameter. |
| | | */ |
| | | public static ServerState getServerState(DN baseDn) |
| | | { |
| | | SynchronizationDomain domain = findDomain(baseDn); |
| | | return domain.getServerState(); |
| | | } |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | |
| | | public SynchronizationProviderResult handleConflictResolution( |
| | | ModifyOperation modifyOperation) |
| | | { |
| | | SynchronizationDomain domain = findDomain(modifyOperation.getEntryDN()); |
| | | SynchronizationDomain domain = |
| | | findDomain(modifyOperation.getEntryDN(), modifyOperation); |
| | | if (domain == null) |
| | | return new SynchronizationProviderResult(true); |
| | | |
| | |
| | | public SynchronizationProviderResult handleConflictResolution( |
| | | AddOperation addOperation) throws DirectoryException |
| | | { |
| | | SynchronizationDomain domain = findDomain(addOperation.getEntryDN()); |
| | | SynchronizationDomain domain = |
| | | findDomain(addOperation.getEntryDN(), addOperation); |
| | | if (domain == null) |
| | | return new SynchronizationProviderResult(true); |
| | | |
| | |
| | | public SynchronizationProviderResult handleConflictResolution( |
| | | DeleteOperation deleteOperation) throws DirectoryException |
| | | { |
| | | SynchronizationDomain domain = findDomain(deleteOperation.getEntryDN()); |
| | | SynchronizationDomain domain = |
| | | findDomain(deleteOperation.getEntryDN(), deleteOperation); |
| | | if (domain == null) |
| | | return new SynchronizationProviderResult(true); |
| | | |
| | |
| | | public SynchronizationProviderResult handleConflictResolution( |
| | | ModifyDNOperation modifyDNOperation) throws DirectoryException |
| | | { |
| | | SynchronizationDomain domain = findDomain(modifyDNOperation.getEntryDN()); |
| | | SynchronizationDomain domain = |
| | | findDomain(modifyDNOperation.getEntryDN(), modifyDNOperation); |
| | | if (domain == null) |
| | | return new SynchronizationProviderResult(true); |
| | | |
| | |
| | | public SynchronizationProviderResult |
| | | doPreOperation(ModifyOperation modifyOperation) |
| | | { |
| | | SynchronizationDomain domain = findDomain(modifyOperation.getEntryDN()); |
| | | if (domain == null) |
| | | DN operationDN = modifyOperation.getEntryDN(); |
| | | SynchronizationDomain domain = findDomain(operationDN, modifyOperation); |
| | | |
| | | if ((domain == null) || (!domain.solveConflict())) |
| | | return new SynchronizationProviderResult(true); |
| | | |
| | | Historical historicalInformation = (Historical) |
| | |
| | | @Override |
| | | public SynchronizationProviderResult doPreOperation(AddOperation addOperation) |
| | | { |
| | | SynchronizationDomain domain = findDomain(addOperation.getEntryDN()); |
| | | SynchronizationDomain domain = |
| | | findDomain(addOperation.getEntryDN(), addOperation); |
| | | if (domain == null) |
| | | return new SynchronizationProviderResult(true); |
| | | |
| | |
| | | * @param dn The DN for which the domain must be returned. |
| | | * @return The Synchronization domain for this DN. |
| | | */ |
| | | private static SynchronizationDomain findDomain(DN dn) |
| | | private static SynchronizationDomain findDomain(DN dn, Operation op) |
| | | { |
| | | /* |
| | | * Don't run the special synchronization code on Operation that are |
| | | * specifically marked as don't synchronize. |
| | | */ |
| | | if (op.dontSynchronize()) |
| | | return null; |
| | | |
| | | SynchronizationDomain domain = null; |
| | | DN temp = dn; |
| | | do |
| | |
| | | } |
| | | } while (domain == null); |
| | | |
| | | /* |
| | | * Don't apply synchronization to the special entry where the ServerState |
| | | * is stored. |
| | | */ |
| | | if ((domain!= null) && (domain.getServerStateDN().equals(dn))) |
| | | return null; |
| | | |
| | | return domain; |
| | | } |
| | | |
| | |
| | | */ |
| | | private void genericPostOperation(Operation operation, DN dn) |
| | | { |
| | | SynchronizationDomain domain = findDomain(dn); |
| | | SynchronizationDomain domain = findDomain(dn, operation); |
| | | if (domain == null) |
| | | return; |
| | | |
| | |
| | | import java.util.LinkedList; |
| | | import java.util.List; |
| | | |
| | | 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.types.Control; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.DereferencePolicy; |
| | | import org.opends.server.types.DirectoryException; |
| | | import org.opends.server.types.ErrorLogCategory; |
| | | import org.opends.server.types.ErrorLogSeverity; |
| | | import org.opends.server.types.ModificationType; |
| | |
| | | private boolean savedStatus = true; |
| | | private InternalClientConnection conn = |
| | | InternalClientConnection.getRootConnection(); |
| | | private ASN1OctetString serverStateAsn1Dn; |
| | | private DN serverStateDn; |
| | | private ASN1OctetString asn1BaseDn; |
| | | |
| | | /** |
| | | * The attribute name used to store the state in the backend. |
| | |
| | | public PersistentServerState(DN baseDn) |
| | | { |
| | | this.baseDn = baseDn; |
| | | serverStateAsn1Dn = new ASN1OctetString( |
| | | "dc=ffffffff-ffffffff-ffffffff-ffffffff," |
| | | + baseDn.toString()); |
| | | try |
| | | { |
| | | serverStateDn = DN.decode(serverStateAsn1Dn); |
| | | } catch (DirectoryException e) |
| | | { |
| | | // never happens |
| | | } |
| | | asn1BaseDn = new ASN1OctetString(baseDn.toString()); |
| | | loadState(); |
| | | } |
| | | |
| | | /** |
| | |
| | | ResultCode resultCode = updateStateEntry(); |
| | | if (resultCode != ResultCode.SUCCESS) |
| | | { |
| | | if (resultCode == ResultCode.NO_SUCH_OBJECT) |
| | | { |
| | | createStateEntry(); |
| | | } |
| | | else |
| | | { |
| | | savedStatus = false; |
| | | |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Load the ServerState from the backing entry in database to memory. |
| | | */ |
| | | public void loadState() |
| | | private void loadState() |
| | | { |
| | | /* |
| | | * Read the serverState from the database, |
| | |
| | | * Search the database entry that is used to periodically |
| | | * save the ServerState |
| | | */ |
| | | InternalSearchOperation search = conn.processSearch(serverStateAsn1Dn, |
| | | LinkedHashSet<String> attributes = new LinkedHashSet<String>(1); |
| | | attributes.add(SYNCHRONIZATION_STATE); |
| | | InternalSearchOperation search = conn.processSearch(asn1BaseDn, |
| | | SearchScope.BASE_OBJECT, |
| | | DereferencePolicy.DEREF_ALWAYS, 0, 0, false, |
| | | filter,new LinkedHashSet<String>(0)); |
| | | filter,attributes); |
| | | if (((search.getResultCode() != ResultCode.SUCCESS)) && |
| | | ((search.getResultCode() != ResultCode.NO_SUCH_OBJECT))) |
| | | { |
| | |
| | | * and an ordering index for historical attribute |
| | | */ |
| | | } |
| | | |
| | | if ((resultEntry == null) || |
| | | ((search.getResultCode() != ResultCode.SUCCESS))) |
| | | { |
| | | createStateEntry(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Create the Entry that will be used to store the ServerState information. |
| | | * It will be updated when the server stops and periodically. |
| | | */ |
| | | private void createStateEntry() |
| | | { |
| | | ArrayList<LDAPAttribute> attrs = new ArrayList<LDAPAttribute>(); |
| | | |
| | | ArrayList<ASN1OctetString> values = new ArrayList<ASN1OctetString>(); |
| | | ASN1OctetString value = new ASN1OctetString("extensibleObject"); |
| | | values.add(value); |
| | | LDAPAttribute attr = new LDAPAttribute("objectClass", values); |
| | | value = new ASN1OctetString("domain"); |
| | | values.add(value); |
| | | attr = new LDAPAttribute("objectClass", values); |
| | | attrs.add(attr); |
| | | |
| | | values = new ArrayList<ASN1OctetString>(); |
| | | value = new ASN1OctetString("ffffffff-ffffffff-ffffffff-ffffffff"); |
| | | values.add(value); |
| | | attr = new LDAPAttribute("dc", values); |
| | | attrs.add(attr); |
| | | |
| | | AddOperation add = conn.processAdd(serverStateAsn1Dn, attrs); |
| | | ResultCode resultCode = add.getResultCode(); |
| | | if ((resultCode != ResultCode.SUCCESS) && |
| | | (resultCode != ResultCode.NO_SUCH_OBJECT)) |
| | | { |
| | | int msgID = MSGID_ERROR_UPDATING_RUV; |
| | | String message = getMessage(msgID, |
| | | add.getResultCode().getResultCodeName(), |
| | | add.toString(), add.getErrorMessage(), |
| | | baseDn.toString()); |
| | | logError(ErrorLogCategory.SYNCHRONIZATION, |
| | | ErrorLogSeverity.SEVERE_ERROR, |
| | | message, msgID); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | private ResultCode updateStateEntry() |
| | | { |
| | | /* |
| | | * Generate a modify operation on the Server State Entry : |
| | | * cn=ffffffff-ffffffff-ffffffff-ffffffff, baseDn |
| | | * Generate a modify operation on the Server State baseD Entry. |
| | | */ |
| | | ArrayList<ASN1OctetString> values = this.toASN1ArrayList(); |
| | | |
| | |
| | | ModifyOperation op = |
| | | new ModifyOperation(conn, InternalClientConnection.nextOperationID(), |
| | | InternalClientConnection.nextMessageID(), |
| | | new ArrayList<Control>(0), serverStateAsn1Dn, |
| | | new ArrayList<Control>(0), asn1BaseDn, |
| | | mods); |
| | | op.setInternalOperation(true); |
| | | op.setSynchronizationOperation(true); |
| | | op.setDontSynchronize(true); |
| | | |
| | | op.run(); |
| | | |
| | |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * Get the Dn where the ServerState is stored. |
| | | * @return Returns the serverStateDn. |
| | | */ |
| | | public DN getServerStateDn() |
| | | { |
| | | return serverStateDn; |
| | | } |
| | | |
| | | |
| | | } |
| | |
| | | |
| | | private short serverId; |
| | | |
| | | private BooleanConfigAttribute receiveStatusStub; |
| | | private int listenerThreadNumber = 10; |
| | | private boolean receiveStatus = true; |
| | | |
| | |
| | | private InternalClientConnection conn = |
| | | InternalClientConnection.getRootConnection(); |
| | | |
| | | static String CHANGELOG_SERVER_ATTR = "ds-cfg-changelog-server"; |
| | | static String BASE_DN_ATTR = "ds-cfg-synchronization-dn"; |
| | | static String SERVER_ID_ATTR = "ds-cfg-directory-server-id"; |
| | | static String RECEIVE_STATUS = "ds-cfg-receive-status"; |
| | | static String MAX_RECEIVE_QUEUE = "ds-cfg-max-receive-queue"; |
| | | static String MAX_RECEIVE_DELAY = "ds-cfg-max-receive-delay"; |
| | | static String MAX_SEND_QUEUE = "ds-cfg-max-send-queue"; |
| | | static String MAX_SEND_DELAY = "ds-cfg-max-send-delay"; |
| | | static String WINDOW_SIZE = "ds-cfg-window-size"; |
| | | static String HEARTBEAT_INTERVAL = "ds-cfg-heartbeat-interval"; |
| | | private boolean solveConflictFlag = true; |
| | | |
| | | static final String CHANGELOG_SERVER_ATTR = "ds-cfg-changelog-server"; |
| | | static final String BASE_DN_ATTR = "ds-cfg-synchronization-dn"; |
| | | static final String SERVER_ID_ATTR = "ds-cfg-directory-server-id"; |
| | | static final String RECEIVE_STATUS = "ds-cfg-receive-status"; |
| | | static final String MAX_RECEIVE_QUEUE = "ds-cfg-max-receive-queue"; |
| | | static final String MAX_RECEIVE_DELAY = "ds-cfg-max-receive-delay"; |
| | | static final String MAX_SEND_QUEUE = "ds-cfg-max-send-queue"; |
| | | static final String MAX_SEND_DELAY = "ds-cfg-max-send-delay"; |
| | | static final String WINDOW_SIZE = "ds-cfg-window-size"; |
| | | static final String HEARTBEAT_INTERVAL = "ds-cfg-heartbeat-interval"; |
| | | |
| | | private static final StringConfigAttribute changelogStub = |
| | | new StringConfigAttribute(CHANGELOG_SERVER_ATTR, |
| | |
| | | new DNConfigAttribute(BASE_DN_ATTR, "synchronization base DN", |
| | | true, false, false); |
| | | |
| | | private static final BooleanConfigAttribute receiveStatusStub = |
| | | new BooleanConfigAttribute(RECEIVE_STATUS, "receive status", false); |
| | | |
| | | |
| | | /** |
| | | * The set of time units that will be used for expressing the heartbeat |
| | | * interval. |
| | |
| | | baseDN = baseDn.activeValue(); |
| | | configAttributes.add(baseDn); |
| | | |
| | | /* |
| | | * Modify conflicts are solved for all suffixes but the cn=schema suffix |
| | | * because we don't want to store extra information in the schema |
| | | * ldif files. |
| | | * This has no negative impact because the changes on schema should |
| | | * not produce conflicts. |
| | | */ |
| | | try |
| | | { |
| | | if (baseDN.compareTo(DN.decode("cn=schema")) == 0) |
| | | { |
| | | solveConflictFlag = false; |
| | | } |
| | | else |
| | | { |
| | | solveConflictFlag = true; |
| | | } |
| | | } catch (DirectoryException e1) |
| | | { |
| | | // never happens because "cn=schema" is a valid DN |
| | | } |
| | | |
| | | state = new PersistentServerState(baseDN); |
| | | state.loadState(); |
| | | |
| | | /* |
| | | * Read the Receive Status. |
| | | */ |
| | | receiveStatusStub = new BooleanConfigAttribute(RECEIVE_STATUS, |
| | | "receive status", false); |
| | | BooleanConfigAttribute receiveStatusAttr = (BooleanConfigAttribute) |
| | | configEntry.getConfigAttribute(receiveStatusStub); |
| | | if (receiveStatusAttr != null) |
| | |
| | | // so this is not a synchronization operation. |
| | | ChangeNumber changeNumber = generateChangeNumber(modifyOperation); |
| | | String modifiedEntryUUID = Historical.getEntryUuid(modifiedEntry); |
| | | if (modifiedEntryUUID == null) |
| | | modifiedEntryUUID = modifyOperation.getEntryDN().toString(); |
| | | ctx = new ModifyContext(changeNumber, modifiedEntryUUID); |
| | | modifyOperation.setAttachment(SYNCHROCONTEXT, ctx); |
| | | } |
| | |
| | | { |
| | | String modifiedEntryUUID = ctx.getEntryUid(); |
| | | String currentEntryUUID = Historical.getEntryUuid(modifiedEntry); |
| | | if (!currentEntryUUID.equals(modifiedEntryUUID)) |
| | | if ((currentEntryUUID != null) && |
| | | (!currentEntryUUID.equals(modifiedEntryUUID))) |
| | | { |
| | | /* |
| | | * The current modified entry is not the same entry as the one on |
| | |
| | | } |
| | | |
| | | /** |
| | | * Get the DN where the ServerState is stored. |
| | | * @return The DN where the ServerState is stored. |
| | | */ |
| | | public DN getServerStateDN() |
| | | { |
| | | return state.getServerStateDn(); |
| | | } |
| | | |
| | | /** |
| | | * Get the name of the changelog server to which this domain is currently |
| | | * connected. |
| | | * |
| | |
| | | { |
| | | return broker.getNumLostConnections(); |
| | | } |
| | | |
| | | /** |
| | | * Check if the domain solve conflicts. |
| | | * |
| | | * @return a boolean indicating if the domain should sove conflicts. |
| | | */ |
| | | public boolean solveConflict() |
| | | { |
| | | return solveConflictFlag; |
| | | } |
| | | } |
| | |
| | | // file. |
| | | private long youngestModificationTime; |
| | | |
| | | // The Synchronization State. |
| | | private LinkedHashSet<AttributeValue> synchronizationState = null; |
| | | |
| | | |
| | | |
| | | /** |
| | |
| | | dupSchema.objectClassSet.addAll(objectClassSet); |
| | | dupSchema.oldestModificationTime = oldestModificationTime; |
| | | dupSchema.youngestModificationTime = youngestModificationTime; |
| | | if (synchronizationState != null) |
| | | { |
| | | dupSchema.synchronizationState = |
| | | new LinkedHashSet<AttributeValue>(synchronizationState); |
| | | } |
| | | |
| | | return dupSchema; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Retrieves the Synchronization state for this schema. |
| | | * |
| | | * @return The Synchronization state for this schema. |
| | | */ |
| | | public LinkedHashSet<AttributeValue> getSynchronizationState() |
| | | { |
| | | return synchronizationState; |
| | | } |
| | | |
| | | /** |
| | | * Sets the Synchronization state for this schema. |
| | | * |
| | | * @param values Synchronization state for this schema. |
| | | */ |
| | | public void setSynchronizationState( |
| | | LinkedHashSet<AttributeValue> values) |
| | | { |
| | | synchronizationState = values; |
| | | } |
| | | } |
| | | |
| | |
| | | DN configEntryDN = c.getConfigurableComponentEntryDN(); |
| | | ConfigEntry configEntry = DirectoryServer.getConfigEntry(configEntryDN); |
| | | |
| | | ArrayList<String> unacceptableReasons = new ArrayList<String>(); |
| | | assertTrue(c.hasAcceptableConfiguration(configEntry, unacceptableReasons)); |
| | | if (configEntry != null) |
| | | { |
| | | ArrayList<String> unacceptableReasons = new ArrayList<String>(); |
| | | assertTrue(c.hasAcceptableConfiguration(configEntry, |
| | | unacceptableReasons)); |
| | | } |
| | | } |
| | | } |
| | | |
| New file |
| | |
| | | /* |
| | | * 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 |
| | | * |
| | | * |
| | | * Portions Copyright 2007 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.synchronization; |
| | | |
| | | import static org.opends.server.loggers.Error.logError; |
| | | import static org.testng.Assert.assertTrue; |
| | | import static org.testng.Assert.fail; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.LinkedHashSet; |
| | | import java.util.List; |
| | | |
| | | import org.opends.server.TestCaseUtils; |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.core.ModifyOperation; |
| | | import org.opends.server.core.Operation; |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.protocols.ldap.LDAPModification; |
| | | import org.opends.server.synchronization.common.ChangeNumber; |
| | | import org.opends.server.synchronization.plugin.ChangelogBroker; |
| | | import org.opends.server.synchronization.protocol.ModifyMsg; |
| | | import org.opends.server.synchronization.protocol.SynchronizationMessage; |
| | | import org.opends.server.types.Attribute; |
| | | import org.opends.server.types.AttributeType; |
| | | import org.opends.server.types.AttributeValue; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.ErrorLogCategory; |
| | | import org.opends.server.types.ErrorLogSeverity; |
| | | import org.opends.server.types.Modification; |
| | | import org.opends.server.types.ModificationType; |
| | | import org.opends.server.types.ResultCode; |
| | | import org.testng.annotations.BeforeClass; |
| | | import org.testng.annotations.Test; |
| | | |
| | | /** |
| | | * Test for the schema synchronization. |
| | | */ |
| | | public class SchemaSynchronizationTest extends SynchronizationTestCase |
| | | { |
| | | |
| | | ArrayList<Modification> rcvdMods = null; |
| | | |
| | | /** |
| | | * Set up the environment for performing the tests in this Class. |
| | | * synchronization |
| | | * |
| | | * @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. |
| | | TestCaseUtils.startServer(); |
| | | |
| | | // Disable schema check |
| | | schemaCheck = DirectoryServer.checkSchema(); |
| | | DirectoryServer.setCheckSchema(false); |
| | | |
| | | // Create an internal connection |
| | | connection = InternalClientConnection.getRootConnection(); |
| | | |
| | | // top level synchro provider |
| | | String synchroStringDN = "cn=Synchronization Providers,cn=config"; |
| | | |
| | | // Multimaster Synchro plugin |
| | | synchroPluginStringDN = "cn=Multimaster Synchronization, " |
| | | + synchroStringDN; |
| | | String synchroPluginLdif = "dn: " |
| | | + synchroPluginStringDN |
| | | + "\n" |
| | | + "objectClass: top\n" |
| | | + "objectClass: ds-cfg-synchronization-provider\n" |
| | | + "ds-cfg-synchronization-provider-enabled: true\n" |
| | | + "ds-cfg-synchronization-provider-class: org.opends.server.synchronization.MultimasterSynchronization\n"; |
| | | synchroPluginEntry = TestCaseUtils.entryFromLdifString(synchroPluginLdif); |
| | | |
| | | // Change log |
| | | String changeLogStringDN = "cn=Changelog Server, " + synchroPluginStringDN; |
| | | String changeLogLdif = "dn: " + changeLogStringDN + "\n" |
| | | + "objectClass: top\n" |
| | | + "objectClass: ds-cfg-synchronization-changelog-server-config\n" |
| | | + "cn: Changelog Server\n" + "ds-cfg-changelog-port: 8989\n" |
| | | + "ds-cfg-changelog-server-id: 1\n"; |
| | | changeLogEntry = TestCaseUtils.entryFromLdifString(changeLogLdif); |
| | | |
| | | // suffix synchronized |
| | | String synchroServerLdif = "dn: cn=example, " + synchroPluginStringDN + "\n" |
| | | + "objectClass: top\n" |
| | | + "objectClass: ds-cfg-synchronization-provider-config\n" |
| | | + "cn: example\n" |
| | | + "ds-cfg-synchronization-dn: cn=schema\n" |
| | | + "ds-cfg-changelog-server: localhost:8989\n" |
| | | + "ds-cfg-directory-server-id: 1\n"; |
| | | synchroServerEntry = TestCaseUtils.entryFromLdifString(synchroServerLdif); |
| | | |
| | | configureSynchronization(); |
| | | } |
| | | |
| | | /** |
| | | * Checks that changes done to the schema are pushed to the changelog |
| | | * clients. |
| | | */ |
| | | @Test() |
| | | public void pushSchemaChange() throws Exception |
| | | { |
| | | logError(ErrorLogCategory.SYNCHRONIZATION, |
| | | ErrorLogSeverity.NOTICE, |
| | | "Starting synchronization test : pushSchemaChange ", 1); |
| | | |
| | | final DN baseDn = DN.decode("cn=schema"); |
| | | |
| | | ChangelogBroker broker = |
| | | openChangelogSession(baseDn, (short) 2, 100, 8989, 1000, true); |
| | | |
| | | try |
| | | { |
| | | // Modify the schema |
| | | AttributeType attrType = |
| | | DirectoryServer.getAttributeType("attributetypes", true); |
| | | LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(); |
| | | values.add(new AttributeValue(attrType, "( 2.5.44.77.33 NAME 'dummy' )")); |
| | | Attribute attr = new Attribute(attrType, "attributetypes", values); |
| | | List<Modification> mods = new ArrayList<Modification>(); |
| | | Modification mod = new Modification(ModificationType.ADD, attr); |
| | | mods.add(mod); |
| | | ModifyOperation modOp = new ModifyOperation(connection, |
| | | InternalClientConnection.nextOperationID(), InternalClientConnection |
| | | .nextMessageID(), null, baseDn, mods); |
| | | modOp.setInternalOperation(true); |
| | | modOp.run(); |
| | | |
| | | ResultCode code = modOp.getResultCode(); |
| | | assertTrue(code.equals(ResultCode.SUCCESS), |
| | | "The original operation failed"); |
| | | |
| | | // See if the client has received the msg |
| | | SynchronizationMessage msg = broker.receive(); |
| | | |
| | | assertTrue(msg instanceof ModifyMsg, |
| | | "The received synchronization message is not a MODIFY msg"); |
| | | ModifyMsg modMsg = (ModifyMsg) msg; |
| | | |
| | | Operation receivedOp = modMsg.createOperation(connection); |
| | | assertTrue(DN.decode(modMsg.getDn()).compareTo(baseDn) == 0, |
| | | "The received message is not for cn=schema"); |
| | | |
| | | assertTrue(receivedOp instanceof ModifyOperation, |
| | | "The received synchronization message is not a MODIFY msg"); |
| | | ModifyOperation receivedModifyOperation = (ModifyOperation) receivedOp; |
| | | |
| | | List<LDAPModification> rcvdRawMods = |
| | | receivedModifyOperation.getRawModifications(); |
| | | |
| | | this.rcvdMods = new ArrayList<Modification>(); |
| | | for (LDAPModification m : rcvdRawMods) |
| | | { |
| | | this.rcvdMods.add(m.toModification()); |
| | | } |
| | | |
| | | assertTrue(this.rcvdMods.contains(mod), |
| | | "The received mod does not contain the original change"); |
| | | |
| | | /* |
| | | * Now cleanup the schema for the next test |
| | | */ |
| | | mod = new Modification(ModificationType.DELETE, attr); |
| | | mods.clear(); |
| | | mods.add(mod); |
| | | modOp = new ModifyOperation(connection, |
| | | InternalClientConnection.nextOperationID(), InternalClientConnection |
| | | .nextMessageID(), null, baseDn, mods); |
| | | modOp.setInternalOperation(true); |
| | | modOp.run(); |
| | | |
| | | code = modOp.getResultCode(); |
| | | assertTrue(code.equals(ResultCode.SUCCESS), |
| | | "The original operation failed"); |
| | | } |
| | | finally |
| | | { |
| | | broker.stop(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Checks that changes to the schema pushed to the changelog |
| | | * are received and correctly replayed by synchronization plugin. |
| | | */ |
| | | @Test(dependsOnMethods = { "pushSchemaChange" }) |
| | | public void replaySchemaChange() throws Exception |
| | | { |
| | | logError(ErrorLogCategory.SYNCHRONIZATION, |
| | | ErrorLogSeverity.NOTICE, |
| | | "Starting synchronization test : pushSchemaChange ", 1); |
| | | |
| | | final DN baseDn = DN.decode("cn=schema"); |
| | | |
| | | ChangelogBroker broker = |
| | | openChangelogSession(baseDn, (short) 2, 100, 8989, 1000, true); |
| | | |
| | | ModifyMsg modMsg = new ModifyMsg(new ChangeNumber((long) 10, 1, (short) 2), |
| | | baseDn, rcvdMods, "cn=schema"); |
| | | broker.publish(modMsg); |
| | | |
| | | boolean found = checkEntryHasAttribute(baseDn, "attributetypes", |
| | | "( 2.5.44.77.33 NAME 'dummy' )", |
| | | 10000, true); |
| | | |
| | | if (found == false) |
| | | fail("The modification has not been correctly replayed."); |
| | | } |
| | | } |
| | |
| | | import org.testng.annotations.Test; |
| | | |
| | | /** |
| | | * Test the contructors, encoders and decoders of the synchronization AckMsg, |
| | | * ModifyMsg, ModifyDnMsg, AddMsg and Delete Msg |
| | | * Stress test for the synchronization code using the ChangelogBroker API. |
| | | */ |
| | | public class StressTest extends SynchronizationTestCase |
| | | { |
| | |
| | | |
| | | final DN baseDn = DN.decode("ou=People,dc=example,dc=com"); |
| | | final int TOTAL_MESSAGES = 1000; |
| | | cleanEntries(); |
| | | |
| | | ChangelogBroker broker = |
| | | openChangelogSession(baseDn, (short) 18, 100, 8989, 5000, true); |
| | |
| | | int port, int timeout, boolean emptyOldChanges) |
| | | throws Exception, SocketException |
| | | { |
| | | PersistentServerState state = new PersistentServerState(baseDn); |
| | | ServerState state; |
| | | if (emptyOldChanges) |
| | | state.loadState(); |
| | | state = new PersistentServerState(baseDn); |
| | | else |
| | | state = new ServerState(); |
| | | |
| | | ChangelogBroker broker = new ChangelogBroker( |
| | | state, baseDn, serverId, 0, 0, 0, 0, window_size, 0); |
| | | ArrayList<String> servers = new ArrayList<String>(1); |
| | |
| | | boolean emptyOldChanges) |
| | | throws Exception, SocketException |
| | | { |
| | | PersistentServerState state = new PersistentServerState(baseDn); |
| | | ServerState state; |
| | | if (emptyOldChanges) |
| | | state.loadState(); |
| | | state = new PersistentServerState(baseDn); |
| | | else |
| | | state = new ServerState(); |
| | | |
| | | ChangelogBroker broker = new ChangelogBroker( |
| | | state, baseDn, serverId, maxRcvQueue, 0, |
| | | maxSendQueue, 0, window_size, 0); |
| | |
| | | entry.getUserAttributes(), entry.getOperationalAttributes()); |
| | | addOp.setInternalOperation(true); |
| | | addOp.run(); |
| | | entryList.add(entry.getDN()); |
| | | } |
| | | |
| | | baseUUID = getEntryUUID(DN.decode("ou=People,dc=example,dc=com")); |
| | |
| | | { |
| | | return new Object[][] { { false }, {true} }; |
| | | } |
| | | |
| | | /** |
| | | * Tests done using directly the ChangelogBroker interface. |
| | | */ |
| | |
| | | ChangeNumberGenerator gen = new ChangeNumberGenerator((short) 27, 0); |
| | | |
| | | /* |
| | | * loop receiving update until there is nothing left |
| | | * to make sure that message from previous tests have been consumed. |
| | | */ |
| | | try |
| | | { |
| | | while (true) |
| | | { |
| | | broker.receive(); |
| | | } |
| | | } |
| | | catch (Exception e) |
| | | {} |
| | | /* |
| | | * Test that operations done on this server are sent to the |
| | | * changelog server and forwarded to our changelog broker session. |
| | | */ |
| New file |
| | |
| | | /* |
| | | * 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 |
| | | * |
| | | * |
| | | * Portions Copyright 2006 Sun Microsystems, Inc. |
| | | */ |
| | | package org.opends.server.synchronization.plugin; |
| | | |
| | | import static org.testng.Assert.assertEquals; |
| | | |
| | | import org.opends.server.TestCaseUtils; |
| | | import org.opends.server.core.AddOperation; |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.synchronization.SynchronizationTestCase; |
| | | import org.opends.server.synchronization.common.ChangeNumber; |
| | | import org.opends.server.synchronization.common.ChangeNumberGenerator; |
| | | import org.opends.server.types.DN; |
| | | import org.opends.server.types.Entry; |
| | | import org.testng.annotations.BeforeClass; |
| | | import org.testng.annotations.DataProvider; |
| | | import org.testng.annotations.Test; |
| | | |
| | | /** |
| | | * Test the PersistentServerState class. |
| | | */ |
| | | public class PersistentStateTest extends SynchronizationTestCase |
| | | { |
| | | /** |
| | | * 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 |
| | | { |
| | | /* |
| | | * start the server and create the dc=exmaple,dc=xom entry if it does not |
| | | * exist yet. |
| | | */ |
| | | TestCaseUtils.startServer(); |
| | | String topEntry = "dn: dc=example,dc=com\n" + "objectClass: top\n" |
| | | + "objectClass: domain\n"; |
| | | |
| | | connection = InternalClientConnection.getRootConnection(); |
| | | Entry entry = TestCaseUtils.entryFromLdifString(topEntry); |
| | | AddOperation addOp = new AddOperation(connection, |
| | | InternalClientConnection.nextOperationID(), InternalClientConnection |
| | | .nextMessageID(), null, entry.getDN(), entry.getObjectClasses(), |
| | | entry.getUserAttributes(), entry.getOperationalAttributes()); |
| | | addOp.setInternalOperation(true); |
| | | addOp.run(); |
| | | } |
| | | |
| | | /** |
| | | * The suffix for which we want to test the PersistentServerState class. |
| | | */ |
| | | @DataProvider(name = "suffix") |
| | | public Object[][] suffixData() { |
| | | return new Object[][] { |
| | | {"dc=example,dc=com"}, |
| | | {"cn=schema"} |
| | | }; |
| | | } |
| | | |
| | | /** |
| | | * Test that the PersistentServerState class is able to store and |
| | | * retrieve ServerState to persistent storage. |
| | | */ |
| | | @Test(dataProvider = "suffix") |
| | | public void persistenServerStateTest(String dn) |
| | | throws Exception |
| | | { |
| | | /* |
| | | * Create a new PersitentServerState, |
| | | * update it with 2 new ChangeNumbers with 2 different server Ids |
| | | * save it |
| | | * |
| | | * Then creates a new PersistentServerState and check that the |
| | | * 2 ChangeNumbers have been saved in this new PersistentServerState. |
| | | */ |
| | | DN baseDn = DN.decode(dn); |
| | | PersistentServerState state = new PersistentServerState(baseDn); |
| | | ChangeNumberGenerator gen1 = new ChangeNumberGenerator((short) 1, state); |
| | | ChangeNumberGenerator gen2 = new ChangeNumberGenerator((short) 2, state); |
| | | |
| | | ChangeNumber cn1 = gen1.NewChangeNumber(); |
| | | ChangeNumber cn2 = gen2.NewChangeNumber(); |
| | | |
| | | state.update(cn1); |
| | | state.update(cn2); |
| | | |
| | | state.save(); |
| | | |
| | | PersistentServerState stateSaved = new PersistentServerState(baseDn); |
| | | ChangeNumber cn1Saved = stateSaved.getMaxChangeNumber((short) 1); |
| | | ChangeNumber cn2Saved = stateSaved.getMaxChangeNumber((short) 2); |
| | | |
| | | assertEquals(cn1Saved, cn1, |
| | | "cn1 has not been saved or loaded correctly for " + dn); |
| | | assertEquals(cn2Saved, cn2, |
| | | "cn2 has not been saved or loaded correctly for " + dn); |
| | | |
| | | } |
| | | } |