opends/resource/config/config.ldif
@@ -1834,6 +1834,16 @@ ds-cfg-plugin-type: postOperationModifyDn ds-cfg-java-class: org.opends.server.plugins.ChangeNumberControlPlugin dn: cn=Fractional Replication LDIF Import,cn=Plugins,cn=config objectClass: top objectClass: ds-cfg-plugin objectClass: ds-cfg-fractional-ldif-import-plugin cn: Fractional Replication LDIF Import ds-cfg-java-class: org.opends.server.replication.plugin.FractionalLDIFImportPlugin ds-cfg-enabled: true ds-cfg-plugin-type: ldifImport ds-cfg-invoke-for-internal-operations: true dn: cn=Root DNs,cn=config objectClass: top objectClass: ds-cfg-root-dn opends/resource/schema/02-config.ldif
@@ -2397,6 +2397,24 @@ NAME 'ds-cfg-index-extensible-matching-rule' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDS Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.26027.1.1.586 NAME 'ds-cfg-fractional-include' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDS Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.26027.1.1.587 NAME 'ds-cfg-fractional-exclude' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDS Directory Server' ) attributeTypes: ( 1.3.6.1.4.1.26027.1.1.588 NAME 'ds-sync-fractional-include' 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.589 NAME 'ds-sync-fractional-exclude' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 USAGE directoryOperation X-ORIGIN 'OpenDS Directory Server' ) objectClasses: ( 1.3.6.1.4.1.26027.1.2.1 NAME 'ds-cfg-access-control-handler' SUP top @@ -2955,7 +2973,9 @@ ds-cfg-assured-sd-level $ ds-cfg-assured-timeout $ ds-cfg-group-id $ ds-cfg-referrals-url) ds-cfg-referrals-url $ ds-cfg-fractional-exclude $ ds-cfg-fractional-include) X-ORIGIN 'OpenDS Directory Server' ) objectClasses: ( 1.3.6.1.4.1.26027.1.2.58 NAME 'ds-cfg-length-based-password-validator' @@ -3958,7 +3978,7 @@ NAME 'ds-cfg-qos-policy' SUP top STRUCTURAL MUST ( cn $ MUST ( cn $ ds-cfg-java-class) X-ORIGIN 'OpenDS Directory Server' ) objectClasses: ( 1.3.6.1.4.1.26027.1.2.185 @@ -4054,4 +4074,9 @@ SUP ds-cfg-matching-rule STRUCTURAL X-ORIGIN 'OpenDS Directory Server' ) objectClasses: ( 1.3.6.1.4.1.26027.1.2.232 NAME 'ds-cfg-fractional-ldif-import-plugin' SUP ds-cfg-plugin STRUCTURAL X-ORIGIN 'OpenDS Directory Server' ) opends/src/admin/defn/org/opends/server/admin/std/FractionalLDIFImportPluginConfiguration.xml
New file @@ -0,0 +1,50 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- ! 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 ! ! ! Copyright 2009 Sun Microsystems, Inc. ! --> <adm:managed-object name="fractional-ldif-import-plugin" plural-name="fractional-ldif-import-plugins" package="org.opends.server.admin.std" extends="plugin" xmlns:adm="http://www.opends.org/admin" xmlns:ldap="http://www.opends.org/admin-ldap"> <adm:synopsis> The <adm:user-friendly-name /> is used internally by the replication plugin to support fractional replication. </adm:synopsis> <adm:description> It is used to check fractional configuration consistency with local domain one as well as to filter attributes when performing an online import from a remote backend to a local backend. </adm:description> <adm:profile name="ldap"> <ldap:object-class> <ldap:name>ds-cfg-fractional-ldif-import-plugin</ldap:name> <ldap:superior>ds-cfg-plugin</ldap:superior> </ldap:object-class> </adm:profile> </adm:managed-object> opends/src/admin/defn/org/opends/server/admin/std/ReplicationDomainConfiguration.xml
@@ -345,4 +345,99 @@ </ldap:attribute> </adm:profile> </adm:property> <adm:property name="fractional-include" multi-valued="true" mandatory="false" advanced="true"> <adm:synopsis> Allows to include some attributes to replicate to this server. </adm:synopsis> <adm:description> If fractional-include configuration attribute is used, only attributes specified in this attribute will be added/modified/deleted when an operation performed from another directory server is being replayed in the local server. Note that the usage of this configuration attribute is mutually exclusive with the usage of the fractional-exclude attribute. </adm:description> <adm:default-behavior> <adm:undefined/> </adm:default-behavior> <adm:syntax> <adm:string> <adm:pattern> <!-- This java regex is mostly derived from keystring BNF definition that can be found in RFC 2252, section "4.1. Common Encoding Aspects". This can be read as: (oid|\*):oid(,oid)*+ --> <adm:regex>^((([a-zA-Z]([a-zA-Z]|[0-9]|-|;)*+)|(0|([1-9]([0-9])*+))(\\.(0|([1-9]([0-9])*+)))*+)|\\*):(([a-zA-Z]([a-zA-Z]|[0-9]|-|;)*+)|(0|([1-9]([0-9])*+))(\\.(0|([1-9]([0-9])*+)))*+)(,(([a-zA-Z]([a-zA-Z]|[0-9]|-|;)*+)|(0|([1-9]([0-9])*+))(\\.(0|([1-9]([0-9])*+)))*+))*+$</adm:regex> <adm:usage>Syntax: className:attributeName[,attributeName] or *:attributeName[,attributeName] Note that any class (className) or attribute (attributeName) definition can be replaced with its OID definition. Examples: - inetOrgPerson:uid,employeeNumber : 'uid' and 'employeeNumber' attributes of any entry of type 'inetOrgPerson' class. This can also be 2.16.840.1.113730.3.2.2:0.9.2342.19200300.100.1.1,2.16.840.1.113730.3.1.3 or a mix. - *:description : the 'description' attribute of any entry that has this attribute. This can also be *:2.5.4.13 </adm:usage> <adm:synopsis> Defines attribute(s) of one particular class or of all possible classes, to include in the replication. </adm:synopsis> </adm:pattern> </adm:string> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-fractional-include</ldap:name> </ldap:attribute> </adm:profile> </adm:property> <adm:property name="fractional-exclude" multi-valued="true" mandatory="false" advanced="true"> <adm:synopsis> Allows to exclude some attributes to replicate to this server. </adm:synopsis> <adm:description> If fractional-exclude configuration attribute is used, attributes specified in this attribute will be ignored (not added/modified/deleted) when an operation performed from another directory server is being replayed in the local server. Note that the usage of this configuration attribute is mutually exclusive with the usage of the fractional-include attribute. </adm:description> <adm:default-behavior> <adm:undefined/> </adm:default-behavior> <adm:syntax> <adm:string> <adm:pattern> <!-- This java regex is mostly derived from keystring BNF definition that can be found in RFC 2252, section "4.1. Common Encoding Aspects". This can be read as: (oid|\*):oid(,oid)*+ --> <adm:regex>^((([a-zA-Z]([a-zA-Z]|[0-9]|-|;)*+)|(0|([1-9]([0-9])*+))(\\.(0|([1-9]([0-9])*+)))*+)|\\*):(([a-zA-Z]([a-zA-Z]|[0-9]|-|;)*+)|(0|([1-9]([0-9])*+))(\\.(0|([1-9]([0-9])*+)))*+)(,(([a-zA-Z]([a-zA-Z]|[0-9]|-|;)*+)|(0|([1-9]([0-9])*+))(\\.(0|([1-9]([0-9])*+)))*+))*+$</adm:regex> <adm:usage>Syntax: className:attributeName[,attributeName] or *:attributeName[,attributeName]. Note that any class (className) or attribute (attributeName) definition can be replaced with its OID definition. Examples: inetOrgPerson:photo,jpegPhoto : 'photo' and 'jpegPhoto' attributes of any entry of type 'inetOrgPerson' class. This can also be 2.16.840.1.113730.3.2.2:0.9.2342.19200300.100.1.7,0.9.2342.19200300.100.1.60 or a mix. *:jpegPhoto : the 'jpegPhoto' attribute of any entry that has this attribute. This can also be *:0.9.2342.19200300.100.1.60 </adm:usage> <adm:synopsis> Defines attribute(s) of one particular class or of all possible classes, to exclude from the replication. </adm:synopsis> </adm:pattern> </adm:string> </adm:syntax> <adm:profile name="ldap"> <ldap:attribute> <ldap:name>ds-cfg-fractional-exclude</ldap:name> </ldap:attribute> </adm:profile> </adm:property> </adm:managed-object> opends/src/messages/messages/replication.properties
@@ -372,4 +372,41 @@ SEVERE_ERR_INVALID_COOKIE_FULL_RESYNC_REQUIRED_158=The provided cookie is \ valid in the current context due to %s. Full resync is required SEVERE_ERR_BYTE_COUNT_159=The Server Handler byte count is not correct (Fixed) NOTICE_ERR_FRACTIONAL_CONFIG_UNKNOWN_OBJECT_CLASS_160=Wrong fractional \ replication configuration: could not find object class definition for %s in \ schema NOTICE_ERR_FRACTIONAL_CONFIG_UNKNOWN_ATTRIBUTE_TYPE_161=Wrong fractional \ replication configuration : could not find attribute type definition for %s \ in schema NOTICE_ERR_FRACTIONAL_CONFIG_NOT_OPTIONAL_ATTRIBUTE_162=Wrong fractional \ replication configuration : attribute %s is not optional in class %s NOTICE_ERR_FRACTIONAL_CONFIG_WRONG_FORMAT_163=Wrong fractional replication \ configuration : wrong format : %s (need at least [<className>|*],attributeName) NOTICE_ERR_FRACTIONAL_CONFIG_BOTH_MODES_164=Wrong fractional replication \ configuration : cannot use both exclusive and inclusive modes NOTICE_ERR_FRACTIONAL_CONFIG_PROHIBITED_ATTRIBUTE_165=Wrong fractional \ replication configuration : prohibited attribute %s usage NOTICE_ERR_FRACTIONAL_166=Fractional replication : exception for domain : %s : \ %s NOTICE_FRACTIONAL_BAD_DATA_SET_NEED_RESYNC_167=Warning : domain %s fractional \ replication configuration is inconsistent with backend data set : need \ resynchronization or fractional configuration to be changed MILD_ERR_PLUGIN_FRACTIONAL_LDIF_IMPORT_INVALID_PLUGIN_TYPE_168=The fractional \ replication ldif import plugin is configured with invalid plugin type %s. \ Only the ldifImport plugin type is allowed NOTICE_ERR_FULL_UPDATE_IMPORT_FRACTIONAL_BAD_REMOTE_169=The online full \ update for importing suffix %s data from remote directory server %s has been \ stopped due to fractional configuration inconsistency between destination \ and source server : imported data set has not the same fractional configuration NOTICE_ERR_FULL_UPDATE_IMPORT_FRACTIONAL_REMOTE_IS_FRACTIONAL_170=The online \ full update for importing suffix %s data from remote directory server %s has \ been stopped due to fractional configuration inconsistency between \ destination and source server : imported data set has some fractional \ configuration but not destination server NOTICE_ERR_FRACTIONAL_FORBIDDEN_OPERATION_171=The following operation has \ been forbidden in suffix %s due to inconsistency with the fractional \ replication configuration : %s NOTICE_ERR_FRACTIONAL_FORBIDDEN_FULL_UPDATE_FRACTIONAL_172=The export of \ domain %s from server %s to all other servers of the topology is forbidden as \ the source server has some fractional configuration : only fractional servers \ in a replicated topology does not makes sense opends/src/server/org/opends/server/replication/common/ChangeNumber.java
@@ -41,7 +41,7 @@ // A String representation of the ChangeNumber suitable for network // transmission. private String formatedString = null;; private String formatedString = null; /** * Create a new ChangeNumber from a String. opends/src/server/org/opends/server/replication/plugin/FractionalLDIFImportPlugin.java
New file @@ -0,0 +1,355 @@ /* * 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 * * * Copyright 2009 Sun Microsystems, Inc. */ package org.opends.server.replication.plugin; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.opends.messages.Message; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.std.server.FractionalLDIFImportPluginCfg; import org.opends.server.admin.std.server.PluginCfg; import org.opends.server.api.plugin.*; import org.opends.server.config.ConfigException; import org.opends.server.core.DirectoryServer; import org.opends.server.replication.plugin.LDAPReplicationDomain. AttributeValueStringIterator; import org.opends.server.types.*; import static org.opends.messages.ReplicationMessages.*; /** * This class implements a Directory Server plugin that is used in fractional * replication when an online full update occurs. * The following tasks are done: * - check that the fractional configuration (if any) stored in the root entry * of the domain is compliant with the fractional configuration of the domain * (if not make online update stop) * - perform filtering according to fractional configuration of the domain * - flush the fractional configuration of the domain in the root entry * (if no one already present) */ public final class FractionalLDIFImportPlugin extends DirectoryServerPlugin<FractionalLDIFImportPluginCfg> implements ConfigurationChangeListener<FractionalLDIFImportPluginCfg> { /** * Creates a new instance of this Directory Server plugin. Every plugin must * implement a default constructor (it is the only one that will be used to * create plugins defined in the configuration), and every plugin constructor * must call {@code super()} as its first element. */ public FractionalLDIFImportPlugin() { super(); } /** * {@inheritDoc} */ @Override() public final void initializePlugin(Set<PluginType> pluginTypes, FractionalLDIFImportPluginCfg configuration) throws ConfigException { // Make sure that the plugin has been enabled for the appropriate types. for (PluginType t : pluginTypes) { switch (t) { case LDIF_IMPORT: // This is acceptable. break; default: Message message = ERR_PLUGIN_FRACTIONAL_LDIF_IMPORT_INVALID_PLUGIN_TYPE.get( t.toString()); throw new ConfigException(message); } } } /** * {@inheritDoc} */ @Override() public final void finalizePlugin() { } /** * {@inheritDoc} */ @Override() public final PluginResult.ImportLDIF doLDIFImport( LDIFImportConfig importConfig, Entry entry) { /** * See class comment for what we achieve here... */ // Retrieve the replicated domain this entry belongs to DN entryDn = entry.getDN(); LDAPReplicationDomain domain = MultimasterReplication.findDomain(entryDn, null); if (domain == null) { // Not part of a replicated domain : nothing to do return PluginResult.ImportLDIF.continueEntryProcessing(); } // Is the entry to treat the root entry of the domain ? If yes, analyze the // fractional configuration in it and compare with local domain fractional // configuration. Stop the import if some inconsistency is detcted DN domainBaseDn = domain.getBaseDN(); if (domainBaseDn.equals(entryDn)) { /* * This is the root entry, try to read a fractional configuration from it */ Iterator<String> exclIt = null; AttributeType fractionalExcludeType = DirectoryServer.getAttributeType( LDAPReplicationDomain.REPLICATION_FRACTIONAL_EXCLUDE); List<Attribute> exclAttrs = entry.getAttribute(fractionalExcludeType); Attribute exclAttr = null; if (exclAttrs != null) { exclAttr = exclAttrs.get(0); if (exclAttr != null) { exclIt = new AttributeValueStringIterator(exclAttr.iterator()); } } Iterator<String> inclIt = null; AttributeType fractionalIncludeType = DirectoryServer.getAttributeType( LDAPReplicationDomain.REPLICATION_FRACTIONAL_INCLUDE); List<Attribute> inclAttrs = entry.getAttribute(fractionalIncludeType); Attribute inclAttr = null; if (inclAttrs != null) { inclAttr = inclAttrs.get(0); if (inclAttr != null) { inclIt = new AttributeValueStringIterator(inclAttr.iterator()); } } // Compare backend and local fractional configuration boolean sameConfig = domain.isFractionalConfigConsistent(exclIt, inclIt); if (domain.isFractional()) { // Local domain is fractional if (sameConfig) { // Both local and remote fractional configuration are equivalent : // follow import, no need to go with filtering as remote backend // should be ok return PluginResult.ImportLDIF.continueEntryProcessing(); } else { // Local domain is fractional, remote domain has not same config boolean remoteDomainHasSomeConfig = false; if ((exclAttr != null && (exclAttr.size() > 0)) || (inclAttr != null && (inclAttr.size() > 0))) { remoteDomainHasSomeConfig = true; } if (remoteDomainHasSomeConfig) { // Local domain is fractional, remote domain has some config which // is different : stop import (error will be logged when import is // stopped) domain.setImportErrorMessageId( LDAPReplicationDomain.IMPORT_ERROR_MESSAGE_BAD_REMOTE); domain.setFollowImport(false); return PluginResult.ImportLDIF.continueEntryProcessing(); } else { // Local domain is fractional but remote domain has no config : // flush local config into root entry and follow import with // filtering flushFractionalConfigIntoEntry(domain, entry); } } } else { // Local domain is not fractional if (sameConfig) { // None of the local or remote domain has fractional config : nothing // more to do : let import finish return PluginResult.ImportLDIF.continueEntryProcessing(); } else { // Local domain is not fractional but remote one is : stop import : // local domain should be configured with the same config as remote // one domain.setImportErrorMessageId( LDAPReplicationDomain.IMPORT_ERROR_MESSAGE_REMOTE_IS_FRACTIONAL); domain.setFollowImport(false); return PluginResult.ImportLDIF.continueEntryProcessing(); } } } // If we get here, local domain fractional configuration is enabled. // Now filter for potential attributes to be removed. domain.fractionalRemoveAttributesFromEntry(entry.getDN().getRDN(), entry.getObjectClasses(), entry.getUserAttributes(), true); return PluginResult.ImportLDIF.continueEntryProcessing(); } /** * Write the fractional configuration in the passed domain into the passed * entry. * WARNING: assumption is that no fractional attributes at all is already * present in the passed entry. Also assumption is that domain fractional * configuration is on. * @param domain Domain containing the fractional configuration to use * @param entry The entry to modify */ private static void flushFractionalConfigIntoEntry( LDAPReplicationDomain domain, Entry entry) { if (domain.isFractional()) // Paranoia check { // Get the fractional configuration of the domain boolean fractionalExclusive = domain.isFractionalExclusive(); Map<String, List<String>> fractionalSpecificClassesAttributes = domain.getFractionalSpecificClassesAttributes(); List<String> fractionalAllClassesAttributes = domain.getFractionalAllClassesAttributes(); // Create attribute builder for the rigth fractional mode String fractAttribute = null; if (fractionalExclusive) { fractAttribute = LDAPReplicationDomain.REPLICATION_FRACTIONAL_EXCLUDE; } else { fractAttribute = LDAPReplicationDomain.REPLICATION_FRACTIONAL_INCLUDE; } AttributeBuilder attrBuilder = new AttributeBuilder(fractAttribute); boolean somethingToFlush = false; // Add attribute values for all classes int size = fractionalAllClassesAttributes.size(); if (size > 0) { String fracValue = "*:"; int i = 1; for (String attrName : fractionalAllClassesAttributes) { fracValue += attrName; if (i < size) { fracValue += ","; } i++; } somethingToFlush = true; attrBuilder.add(fracValue); } // Add attribute values for specific classes size = fractionalSpecificClassesAttributes.size(); if (size > 0) { for (String className : fractionalSpecificClassesAttributes.keySet()) { int valuesSize = fractionalSpecificClassesAttributes.get(className).size(); if (valuesSize > 0) { String fracValue = className + ":"; int i = 1; for (String attrName : fractionalSpecificClassesAttributes.get( className)) { fracValue += attrName; if (i < valuesSize) { fracValue += ","; } i++; } somethingToFlush = true; attrBuilder.add(fracValue); } } } // Now flush attribute values into entry if (somethingToFlush) { List<AttributeValue> duplicateValues = new ArrayList<AttributeValue>(); entry.addAttribute(attrBuilder.toAttribute(), duplicateValues); } } } /** * {@inheritDoc} */ @Override() public boolean isConfigurationAcceptable(PluginCfg configuration, List<Message> unacceptableReasons) { return true; } /** * {@inheritDoc} */ public boolean isConfigurationChangeAcceptable( FractionalLDIFImportPluginCfg configuration, List<Message> unacceptableReasons) { return true; } /** * {@inheritDoc} */ public ConfigChangeResult applyConfigurationChange( FractionalLDIFImportPluginCfg configuration) { return new ConfigChangeResult(ResultCode.SUCCESS, false); } } opends/src/server/org/opends/server/replication/plugin/LDAPReplicationDomain.java
@@ -51,13 +51,18 @@ import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.SortedSet; import java.util.StringTokenizer; import java.util.TreeMap; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeoutException; @@ -75,6 +80,7 @@ import org.opends.server.api.DirectoryThread; import org.opends.server.api.SynchronizationProvider; import org.opends.server.backends.jeb.BackendImpl; import org.opends.server.backends.task.Task; import org.opends.server.config.ConfigException; import org.opends.server.controls.SubtreeDeleteControl; import org.opends.server.core.AddOperation; @@ -99,6 +105,7 @@ import org.opends.server.replication.common.ChangeNumberGenerator; import org.opends.server.replication.common.ServerState; import org.opends.server.replication.common.ServerStatus; import org.opends.server.replication.common.StatusMachineEvent; import org.opends.server.replication.protocol.AddContext; import org.opends.server.replication.protocol.AddMsg; import org.opends.server.replication.protocol.DeleteContext; @@ -108,12 +115,14 @@ import org.opends.server.replication.protocol.ModifyMsg; import org.opends.server.replication.protocol.OperationContext; import org.opends.server.replication.protocol.ProtocolSession; import org.opends.server.replication.protocol.RoutableMsg; import org.opends.server.replication.protocol.UpdateMsg; import org.opends.server.tasks.TaskUtils; import org.opends.server.types.AbstractOperation; import org.opends.server.types.Attribute; import org.opends.server.types.AttributeBuilder; import org.opends.server.types.AttributeType; import org.opends.server.types.AttributeValue; import org.opends.server.types.AttributeValues; import org.opends.server.types.ByteString; import org.opends.server.types.ConfigChangeResult; @@ -128,10 +137,12 @@ import org.opends.server.types.LDIFImportConfig; import org.opends.server.types.Modification; import org.opends.server.types.ModificationType; import org.opends.server.types.ObjectClass; import org.opends.server.types.Operation; import org.opends.server.types.RDN; import org.opends.server.types.RawModification; import org.opends.server.types.ResultCode; import org.opends.server.types.Schema; import org.opends.server.types.SearchFilter; import org.opends.server.types.SearchResultEntry; import org.opends.server.types.SearchResultReference; @@ -227,7 +238,7 @@ // This list is used to temporary store operations that needs // to be replayed at session establishment time. private final TreeMap<ChangeNumber, FakeOperation> replayOperations = new TreeMap<ChangeNumber, FakeOperation>();; new TreeMap<ChangeNumber, FakeOperation>(); /** * The isolation policy that this domain is going to use. @@ -252,6 +263,142 @@ private ServerStateFlush flushThread; /** * The attribute name used to store the generation id in the backend. */ protected static final String REPLICATION_GENERATION_ID = "ds-sync-generation-id"; /** * The attribute name used to store the fractional include configuration in * the backend. */ public static final String REPLICATION_FRACTIONAL_INCLUDE = "ds-sync-fractional-include"; /** * The attribute name used to store the fractional exclude configuration in * the backend. */ public static final String REPLICATION_FRACTIONAL_EXCLUDE = "ds-sync-fractional-exclude"; /** * Fractional replication variables. */ // Return type of the parseFractionalConfig method private static final int NOT_FRACTIONAL = 0; private static final int EXCLUSIVE_FRACTIONAL = 1; private static final int INCLUSIVE_FRACTIONAL = 2; /** * Tells if fractional replication is enabled or not (some fractional * constraints have been put in place). If this is true then * fractionalExclusive explains the configuration mode and either * fractionalSpecificClassesAttributes or fractionalAllClassesAttributes or * both should be filled with something. */ private boolean fractional = false; /** * - If true, tells that the configured fractional replication is exclusive: * Every attributes contained in fractionalSpecificClassesAttributes and * fractionalAllClassesAttributes should be ignored when replaying operation * in local backend. * - If false, tells that the configured fractional replication is inclusive: * Only attributes contained in fractionalSpecificClassesAttributes and * fractionalAllClassesAttributes should be taken into account in local * backend. */ private boolean fractionalExclusive = true; /** * Used in fractional replication: holds attributes of a specific object * class. * - key = object class (name or OID of the class) * - value = the attributes of that class that should be taken into account * (inclusive or exclusive fractional replication) (name or OID of the * attribute) * When an operation coming from the network is to be locally replayed, if the * concerned entry has an objectClass attribute equals to 'key': * - inclusive mode: only the attributes in 'value' will be added/deleted/ * modified * - exclusive mode: the attributes in 'value' will not be added/deleted/ * modified */ private Map<String, List<String>> fractionalSpecificClassesAttributes = new HashMap<String, List<String>>(); /** * Used in fractional replication: holds attributes of any object class. When * an operation coming from the network is to be locally replayed: * - inclusive mode: only attributes of the matching entry not present in * fractionalAllClassesAttributes will be added/deleted/modified * - exclusive mode: attributes of the matching entry present in * fractionalAllClassesAttributes will not be added/deleted/modified * The attributes may be in human readable form of OID form. */ private List<String> fractionalAllClassesAttributes = new ArrayList<String>(); /** * The list of attributes that cannot be used in fractional replication * configuration. */ private static final String[] FRACTIONAL_PROHIBITED_ATTRIBUTES = new String[] { "objectClass", "2.5.4.0" // objectClass OID }; /** * When true, this flag is used to force the domain status to be put in bad * data set just after the connection to the replication server. * This must be used when fractional replication is enabled with a * configuration different from the previous one (or at the very first * fractional usage time) : after connection, a ChangeStatusMsg is sent * requesting the bad data set status. Then none of the update messages * received from the replication server are taken into account until the * backend is synchronized with brand new data set compliant with the new * fractional configuration (i.e with compliant fractional configuration in * domain root entry). */ private boolean force_bad_data_set = false; /** * This flag is used by the fractional replication ldif import plugin to * stop the (online) import process if a fractional configuration * inconsistency is detected by it. */ private boolean followImport = true; /** * This is the message id to be used when an import is stopped with error by * the fractional replication ldif import plugin. */ private int importErrorMessageId = -1; /** * Message type for ERR_FULL_UPDATE_IMPORT_FRACTIONAL_BAD_REMOTE. */ public static final int IMPORT_ERROR_MESSAGE_BAD_REMOTE = 1; /** * Message type for ERR_FULL_UPDATE_IMPORT_FRACTIONAL_REMOTE_IS_FRACTIONAL. */ public static final int IMPORT_ERROR_MESSAGE_REMOTE_IS_FRACTIONAL = 2; /* * Definitions for the return codes of the * fractionalFilterOperation(PreOperationModifyOperation * modifyOperation, boolean performFiltering) method */ // The operation contains attributes subject to fractional filtering according // to the fractional configuration private static final int FRACTIONAL_HAS_FRACTIONAL_FILTERED_ATTRIBUTES = 1; // The operation contains no attributes subject to fractional filtering // according to the fractional configuration private static final int FRACTIONAL_HAS_NO_FRACTIONAL_FILTERED_ATTRIBUTES = 2; // The operation should become a no-op private static final int FRACTIONAL_BECOME_NO_OP = 3; /** * The thread that periodically saves the ServerState of this * LDAPReplicationDomain in the database. */ @@ -326,6 +473,9 @@ // Get assured configuration readAssuredConfig(configuration, false); // Get fractional configuration readFractionalConfig(configuration, false); setGroupId((byte)configuration.getGroupId()); setURLs(configuration.getReferralsUrl()); @@ -477,6 +627,1352 @@ } /** * Returns true if fractional replication is configured in this domain. * @return True if fractional replication is configured in this domain. */ public boolean isFractional() { return fractional; } /** * Returns true if the fractional replication configuration is exclusive mode * in this domain, false if inclusive mode. * @return True if the fractional replication configuration is exclusive mode * in this domain, false if inclusive mode. */ public boolean isFractionalExclusive() { return fractionalExclusive; } /** * Returns the fractional configuration for specific classes. * @return The fractional configuration for specific classes. */ public Map<String, List<String>> getFractionalSpecificClassesAttributes() { return fractionalSpecificClassesAttributes; } /** * Returns the fractional configuration for all classes. * @return The fractional configuration for all classes. */ public List<String> getFractionalAllClassesAttributes() { return fractionalAllClassesAttributes; } /** * Sets the error message id to be used when online import is stopped with * error by the fractional replication ldif import plugin. * @param importErrorMessageId The message to use. */ public void setImportErrorMessageId(int importErrorMessageId) { this.importErrorMessageId = importErrorMessageId; } /** * Sets the boolean telling if the online import currently in progress should * continue. * @param followImport The boolean telling if the online import currently in * progress should continue. */ public void setFollowImport(boolean followImport) { this.followImport = followImport; } /** * Gets and stores the fractional replication configuration parameters. * @param configuration The configuration object * @param allowReconnection Tells if one must reconnect if significant changes * occurred */ private void readFractionalConfig(ReplicationDomainCfg configuration, boolean allowReconnection) { boolean needReconnection = false; // Prepare fractional configuration variables to parse Iterator<String> exclIt = null; SortedSet<String> fractionalExclude = configuration.getFractionalExclude(); if (fractionalExclude != null) { exclIt = fractionalExclude.iterator(); } Iterator<String> inclIt = null; SortedSet<String> fractionalInclude = configuration.getFractionalInclude(); if (fractionalInclude != null) { inclIt = fractionalInclude.iterator(); } // Get potentially new fractional configuration Map<String, List<String>> newFractionalSpecificClassesAttributes = new HashMap<String, List<String>>(); List<String> newFractionalAllClassesAttributes = new ArrayList<String>(); int newFractionalMode = NOT_FRACTIONAL; try { newFractionalMode = parseFractionalConfig(exclIt, inclIt, newFractionalSpecificClassesAttributes, newFractionalAllClassesAttributes); } catch(ConfigException e) { // Should not happen as normally already called without problem in // isConfigurationChangeAcceptable or isConfigurationAcceptable // if we come up to this method Message message = NOTE_ERR_FRACTIONAL.get(baseDn.toString(), e.getLocalizedMessage()); logError(message); return; } /** * Is there any change in fractional configuration ? */ // Compute current configuration int fractionalMode = fractionalConfigToInt(); try { needReconnection = !isFractionalConfigEquivalent(fractionalMode, fractionalSpecificClassesAttributes, fractionalAllClassesAttributes, newFractionalMode, newFractionalSpecificClassesAttributes, newFractionalAllClassesAttributes); } catch (ConfigException e) { // Should not happen Message message = NOTE_ERR_FRACTIONAL.get(baseDn.toString(), e.getLocalizedMessage()); logError(message); return; } // Disable service if configuration changed if (needReconnection && allowReconnection) disableService(); // Set new configuration fractional = (newFractionalMode != NOT_FRACTIONAL); if (fractional) { // Set new fractional configuration values if (newFractionalMode == EXCLUSIVE_FRACTIONAL) fractionalExclusive = true; else fractionalExclusive = false; fractionalSpecificClassesAttributes = newFractionalSpecificClassesAttributes; fractionalAllClassesAttributes = newFractionalAllClassesAttributes; } else { // Reset default values fractionalExclusive = true; fractionalSpecificClassesAttributes = new HashMap<String, List<String>>(); fractionalAllClassesAttributes = new ArrayList<String>(); } // Reconnect if required if (needReconnection && allowReconnection) enableService(); } /** * Return true if the fractional configuration stored in the domain root * entry of the backend is equivalent to the fractional configuration stored * in the local variables. */ private boolean isBackendFractionalConfigConsistent() { /* * Read config stored in domain root entry */ if (debugEnabled()) TRACER.debugInfo( "Attempt to read the potential fractional config in domain root " + "entry " + baseDn.toString()); ByteString asn1BaseDn = ByteString.valueOf(baseDn.toString()); boolean found = false; LDAPFilter filter; try { filter = LDAPFilter.decode("objectclass=*"); } catch (LDAPException e) { // Can not happen return false; } /* * Search the domain root entry that is used to save the generation id */ InternalSearchOperation search = null; LinkedHashSet<String> attributes = new LinkedHashSet<String>(1); attributes.add(REPLICATION_GENERATION_ID); attributes.add(REPLICATION_FRACTIONAL_EXCLUDE); attributes.add(REPLICATION_FRACTIONAL_INCLUDE); search = conn.processSearch(asn1BaseDn, SearchScope.BASE_OBJECT, DereferencePolicy.DEREF_ALWAYS, 0, 0, false, filter, attributes); if (((search.getResultCode() != ResultCode.SUCCESS)) && ((search.getResultCode() != ResultCode.NO_SUCH_OBJECT))) { Message message = ERR_SEARCHING_GENERATION_ID.get( search.getResultCode().getResultCodeName() + " " + search.getErrorMessage(), baseDn.toString()); logError(message); return false; } SearchResultEntry resultEntry = null; if (search.getResultCode() == ResultCode.SUCCESS) { LinkedList<SearchResultEntry> result = search.getSearchEntries(); resultEntry = result.getFirst(); if (resultEntry != null) { AttributeType synchronizationGenIDType = DirectoryServer.getAttributeType(REPLICATION_GENERATION_ID); List<Attribute> attrs = resultEntry.getAttribute(synchronizationGenIDType); if (attrs != null) { Attribute attr = attrs.get(0); if (attr.size() > 1) { Message message = ERR_LOADING_GENERATION_ID.get( baseDn.toString(), "#Values=" + attr.size() + " Must be exactly 1 in entry " + resultEntry.toLDIFString()); logError(message); } else if (attr.size() == 1) { found = true; } } } } if (!found) { // The backend is probably empty: if there is some fractional // configuration in memory, we do not let the domain being connected, // otherwise, it's ok if (fractional) { return false; } else { return true; } } /* * Now extract fractional configuration if any */ Iterator<String> exclIt = null; AttributeType fractionalExcludeType = DirectoryServer.getAttributeType(REPLICATION_FRACTIONAL_EXCLUDE); List<Attribute> exclAttrs = resultEntry.getAttribute(fractionalExcludeType); if (exclAttrs != null) { Attribute exclAttr = exclAttrs.get(0); if (exclAttr != null) { exclIt = new AttributeValueStringIterator(exclAttr.iterator()); } } Iterator<String> inclIt = null; AttributeType fractionalIncludeType = DirectoryServer.getAttributeType(REPLICATION_FRACTIONAL_INCLUDE); List<Attribute> inclAttrs = resultEntry.getAttribute(fractionalIncludeType); if (inclAttrs != null) { Attribute inclAttr = inclAttrs.get(0); if (inclAttr != null) { inclIt = new AttributeValueStringIterator(inclAttr.iterator()); } } // Compare backend and local fractional configuration return isFractionalConfigConsistent(exclIt, inclIt); } /** * Return true if the fractional configuration passed as fractional * configuration attribute values is equivalent to the fractional * configuration stored in the local variables. * @param exclIt Fractional exclude mode configuration attribute values to * analyze. * @param inclIt Fractional include mode configuration attribute values to * analyze. * @return True if the fractional configuration passed as fractional * configuration attribute values is equivalent to the fractional * configuration stored in the local variables. */ public boolean isFractionalConfigConsistent(Iterator<String> exclIt, Iterator<String> inclIt) { /* * Parse fractional configuration stored in passed fractional configuration * attributes values */ Map<String, List<String>> storedFractionalSpecificClassesAttributes = new HashMap<String, List<String>>(); List<String> storedFractionalAllClassesAttributes = new ArrayList<String>(); int storedFractionalMode = NOT_FRACTIONAL; try { storedFractionalMode = parseFractionalConfig(exclIt, inclIt, storedFractionalSpecificClassesAttributes, storedFractionalAllClassesAttributes); } catch (ConfigException e) { // Should not happen as configuration in domain root entry is flushed // from valid configuration in local variables Message message = NOTE_ERR_FRACTIONAL.get(baseDn.toString(), e.getLocalizedMessage()); logError(message); return false; } /* * Compare configuration stored in passed fractional configuration * attributes with local variable one */ // Compute current configuration from local variables int fractionalMode = fractionalConfigToInt(); try { return isFractionalConfigEquivalent(fractionalMode, fractionalSpecificClassesAttributes, fractionalAllClassesAttributes, storedFractionalMode, storedFractionalSpecificClassesAttributes, storedFractionalAllClassesAttributes); } catch (ConfigException e) { // Should not happen as configuration in domain root entry is flushed // from valid configuration in local variables so both should have already // been checked Message message = NOTE_ERR_FRACTIONAL.get(baseDn.toString(), e.getLocalizedMessage()); logError(message); return false; } } /** * Get an integer representation of the domain fractional configuration. * @return An integer representation of the domain fractional configuration. */ public int fractionalConfigToInt() { int fractionalMode = -1; if (fractional) { if (fractionalExclusive) fractionalMode = EXCLUSIVE_FRACTIONAL; else fractionalMode = INCLUSIVE_FRACTIONAL; } else { fractionalMode = NOT_FRACTIONAL; } return fractionalMode; } /** * Utility class to have get a sting iterator from an AtributeValue iterator. * Assuming the attribute values are strings. */ public static class AttributeValueStringIterator implements Iterator<String> { private Iterator<AttributeValue> attrValIt = null; /** * Creates a new AttributeValueStringIterator object. * @param attrValIt The underlying attribute iterator to use, assuming * internal values are strings. */ public AttributeValueStringIterator(Iterator<AttributeValue> attrValIt) { this.attrValIt = attrValIt; } /** * {@inheritDoc} */ @Override public boolean hasNext() { return attrValIt.hasNext(); } /** * {@inheritDoc} */ @Override public String next() { return attrValIt.next().getValue().toString(); } /** * {@inheritDoc} */ @Override // Should not be needed anyway public void remove() { attrValIt.remove(); } } /** * Compare 2 fractional replication configurations and returns true if they * are equivalent. * @param mode1 Fractional mode 1 * @param specificClassesAttributes1 Specific classes attributes 1 * @param allClassesAttributes1 All classes attributes 1 * @param mode2 Fractional mode 1 * @param specificClassesAttributes2 Specific classes attributes 2 * @param allClassesAttributes2 Fractional mode 2 * @return True if both configurations are equivalent. * @throws ConfigException If some classes or attributes could not be * retrieved from the schema. */ private static boolean isFractionalConfigEquivalent(int mode1, Map<String, List<String>> specificClassesAttributes1, List<String> allClassesAttributes1, int mode2, Map<String, List<String>> specificClassesAttributes2, List<String> allClassesAttributes2) throws ConfigException { // Compare modes if (mode1 != mode2) return false; // Compare all classes attributes if (!isAttributeListEquivalent(allClassesAttributes1, allClassesAttributes2)) return false; // Compare specific classes attributes if (specificClassesAttributes1.size() != specificClassesAttributes2.size()) return false; // Check consistency of specific classes attributes /* * For each class in specificClassesAttributes1, check that the attribute * list is equivalent to specificClassesAttributes2 attribute list */ Schema schema = DirectoryServer.getSchema(); for (String className1 : specificClassesAttributes1.keySet()) { // Get class from specificClassesAttributes1 ObjectClass objectClass1 = schema.getObjectClass(className1); if (objectClass1 == null) { throw new ConfigException( NOTE_ERR_FRACTIONAL_CONFIG_UNKNOWN_OBJECT_CLASS.get(className1)); } // Look for matching one in specificClassesAttributes2 boolean foundClass = false; for (String className2 : specificClassesAttributes2.keySet()) { ObjectClass objectClass2 = schema.getObjectClass(className2); if (objectClass2 == null) { throw new ConfigException( NOTE_ERR_FRACTIONAL_CONFIG_UNKNOWN_OBJECT_CLASS.get(className2)); } if (objectClass1.equals(objectClass2)) { foundClass = true; // Now compare the 2 attribute lists List<String> attributes1 = specificClassesAttributes1.get(className1); List<String> attributes2 = specificClassesAttributes2.get(className2); if (!isAttributeListEquivalent(attributes1, attributes2)) return false; break; } } // Found matching class ? if (!foundClass) return false; } return true; } /** * Compare 2 attribute lists and returns true if they are equivalent. * @param attributes1 First attribute list to compare. * @param attributes2 Second attribute list to compare. * @return True if both attribute lists are equivalent. * @throws ConfigException If some attributes could not be retrieved from the * schema. */ private static boolean isAttributeListEquivalent( List<String> attributes1, List<String> attributes2) throws ConfigException { // Compare all classes attributes if (attributes1.size() != attributes2.size()) return false; // Check consistency of all classes attributes Schema schema = DirectoryServer.getSchema(); /* * For each attribute in attributes1, check there is the matching * one in attributes2. */ for (String attributName1 : attributes1) { // Get attribute from attributes1 AttributeType attributeType1 = schema.getAttributeType(attributName1); if (attributeType1 == null) { throw new ConfigException( NOTE_ERR_FRACTIONAL_CONFIG_UNKNOWN_ATTRIBUTE_TYPE.get(attributName1)); } // Look for matching one in attributes2 boolean foundAttribute = false; for (String attributName2 : attributes2) { AttributeType attributeType2 = schema.getAttributeType(attributName2); if (attributeType2 == null) { throw new ConfigException( NOTE_ERR_FRACTIONAL_CONFIG_UNKNOWN_ATTRIBUTE_TYPE.get( attributName2)); } if (attributeType1.equals(attributeType2)) { foundAttribute = true; break; } } // Found matching attribute ? if (!foundAttribute) return false; } return true; } /** * Parses a fractional replication configuration, filling the empty passed * variables and returning the used fractional mode. The 2 passed variables to * fill should be initialized (not null) and empty. * @param exclIt The list of fractional exclude configuration values (may be * null) * @param inclIt The list of fractional include configuration values (may be * null) * @param fractionalSpecificClassesAttributes An empty map to be filled with * what is read from the fractional configuration properties. * @param fractionalAllClassesAttributes An empty list to be filled with what * is read from the fractional configuration properties. * @return the fractional mode deduced from the passed configuration: * not fractional, exclusive fractional or inclusive fractional modes */ private static int parseFractionalConfig ( Iterator<String> exclIt, Iterator<String> inclIt, Map<String, List<String>> fractionalSpecificClassesAttributes, List<String> fractionalAllClassesAttributes) throws ConfigException { int fractional_mode = NOT_FRACTIONAL; // Determine if fractional-exclude or fractional-include property is used // : only one of them is allowed Iterator<String> fracConfIt = null; // Deduce the wished fractional mode if ((exclIt != null) && exclIt.hasNext()) { if ((inclIt != null) && inclIt.hasNext()) { throw new ConfigException(NOTE_ERR_FRACTIONAL_CONFIG_BOTH_MODES.get()); } else { fractional_mode = EXCLUSIVE_FRACTIONAL; fracConfIt = exclIt; } } else { if ((inclIt != null) && inclIt.hasNext()) { fractional_mode = INCLUSIVE_FRACTIONAL; fracConfIt = inclIt; } else { return NOT_FRACTIONAL; } } while (fracConfIt.hasNext()) { // Parse a value with the form class:attr1,attr2... // or *:attr1,attr2... String fractCfgStr = fracConfIt.next(); StringTokenizer st = new StringTokenizer(fractCfgStr, ":"); int nTokens = st.countTokens(); if (nTokens < 2) { throw new ConfigException(NOTE_ERR_FRACTIONAL_CONFIG_WRONG_FORMAT. get(fractCfgStr)); } // Get the class name String classNameLower = st.nextToken().toLowerCase(); boolean allClasses = classNameLower.equals("*"); // Get the attributes String attributes = st.nextToken(); st = new StringTokenizer(attributes, ","); while (st.hasMoreTokens()) { String attrNameLower = st.nextToken().toLowerCase(); // Store attribute in the appropriate variable if (allClasses) { // Avoid duplicate attributes if (!fractionalAllClassesAttributes.contains(attrNameLower)) { fractionalAllClassesAttributes.add(attrNameLower); } } else { List<String> attrList = fractionalSpecificClassesAttributes.get(classNameLower); if (attrList != null) { // Avoid duplicate attributes if (!attrList.contains(attrNameLower)) { attrList.add(attrNameLower); } } else { attrList = new ArrayList<String>(); attrList.add(attrNameLower); fractionalSpecificClassesAttributes.put(classNameLower, attrList); } } } } return fractional_mode; } /** * Check that the passed fractional configuration is acceptable * regarding configuration syntax, schema constraints... * Throws an exception if the configuration is not acceptable. * @param configuration The configuration to analyze. * @throws org.opends.server.config.ConfigException if the configuration is * not acceptable. */ private static void isFractionalConfigAcceptable( ReplicationDomainCfg configuration) throws ConfigException { /* * Parse fractional configuration */ // Prepare fractional configuration variables to parse Iterator<String> exclIt = null; SortedSet<String> fractionalExclude = configuration.getFractionalExclude(); if (fractionalExclude != null) { exclIt = fractionalExclude.iterator(); } Iterator<String> inclIt = null; SortedSet<String> fractionalInclude = configuration.getFractionalInclude(); if (fractionalInclude != null) { inclIt = fractionalInclude.iterator(); } // Prepare variables to be filled with config Map<String, List<String>> newFractionalSpecificClassesAttributes = new HashMap<String, List<String>>(); List<String> newFractionalAllClassesAttributes = new ArrayList<String>(); int fractionalMode = parseFractionalConfig(exclIt, inclIt, newFractionalSpecificClassesAttributes, newFractionalAllClassesAttributes); switch (fractionalMode) { case NOT_FRACTIONAL: // Nothing to check return; case EXCLUSIVE_FRACTIONAL: case INCLUSIVE_FRACTIONAL: // Ok, checking done out of the switch statement break; default: // Should not happen return; } /* * Check attributes consistency : we only allow to filter MAY (optional) * attributes of a class : to be compliant with the schema, no MUST * (mandatory) attribute can be filtered by fractional replication. */ // Check consistency of specific classes attributes Schema schema = DirectoryServer.getSchema(); for (String className : newFractionalSpecificClassesAttributes.keySet()) { // Does the class exist ? ObjectClass fractionalClass = schema.getObjectClass( className.toLowerCase()); if (fractionalClass == null) { throw new ConfigException( NOTE_ERR_FRACTIONAL_CONFIG_UNKNOWN_OBJECT_CLASS.get(className)); } boolean isExtensibleObjectClass = className. equalsIgnoreCase("extensibleObject"); List<String> attributes = newFractionalSpecificClassesAttributes.get(className); for (String attrName : attributes) { // Not a prohibited attribute ? if (isFractionalProhibitedAttr(attrName)) { throw new ConfigException( NOTE_ERR_FRACTIONAL_CONFIG_PROHIBITED_ATTRIBUTE.get(attrName)); } // Does the attribute exist ? AttributeType attributeType = schema.getAttributeType(attrName); if (attributeType != null) { // No more checking for the extensibleObject class if (!isExtensibleObjectClass) { if (fractionalMode == EXCLUSIVE_FRACTIONAL) { // Exclusive mode : the attribute must be optional if (!fractionalClass.isOptional(attributeType)) { throw new ConfigException( NOTE_ERR_FRACTIONAL_CONFIG_NOT_OPTIONAL_ATTRIBUTE. get(attrName, className)); } } } } else { throw new ConfigException( NOTE_ERR_FRACTIONAL_CONFIG_UNKNOWN_ATTRIBUTE_TYPE.get(attrName)); } } } // Check consistency of all classes attributes for (String attrName : newFractionalAllClassesAttributes) { // Not a prohibited attribute ? if (isFractionalProhibitedAttr(attrName)) { throw new ConfigException( NOTE_ERR_FRACTIONAL_CONFIG_PROHIBITED_ATTRIBUTE.get(attrName)); } // Does the attribute exist ? if (schema.getAttributeType(attrName) == null) { throw new ConfigException( NOTE_ERR_FRACTIONAL_CONFIG_UNKNOWN_ATTRIBUTE_TYPE.get(attrName)); } } } /** * Test if the passed attribute is not allowed to be used in configuration of * fractional replication. * @param attr Attribute to test. * @return true if the attribute is prohibited. */ private static boolean isFractionalProhibitedAttr(String attr) { for (String forbiddenAttr : FRACTIONAL_PROHIBITED_ATTRIBUTES) { if (forbiddenAttr.equalsIgnoreCase(attr)) { return true; } } return false; } /** * If fractional replication is enabled, this analyzes the operation and * suppresses the forbidden attributes in it so that they are not added in * the local backend. * * @param addOperation The operation to modify based on fractional * replication configuration * @param performFiltering Tells if the effective attribute filtering should * be performed or if the call is just to analyze if there are some * attributes filtered by fractional configuration * @return true if the operation contains some attributes subject to filtering * by the fractional configuration */ public boolean fractionalFilterOperation( PreOperationAddOperation addOperation, boolean performFiltering) { return fractionalRemoveAttributesFromEntry( addOperation.getEntryDN().getRDN(), addOperation.getObjectClasses(), addOperation.getUserAttributes(), performFiltering); } /** * If fractional replication is enabled, this analyzes the operation and * suppresses the forbidden attributes in it so that they are not added in * the local backend. * * @param modifyDNOperation The operation to modify based on fractional * replication configuration * @param performFiltering Tells if the effective modifications should * be performed or if the call is just to analyze if there are some * inconsistency with fractional configuration * @return true if the operation is inconsistent with fractional configuration */ public boolean fractionalFilterOperation( PreOperationModifyDNOperation modifyDNOperation, boolean performFiltering) { boolean inconsistentOperation = false; // Quick exit if not called for analyze and if (performFiltering) { if (modifyDNOperation.deleteOldRDN()) { // The core will remove any occurence of attribute that was part of the // old RDN, nothing more to do. return true; // Will not be used as analyze was not requested } } /* * Create a list of filtered attributes for this entry */ Entry concernedEntry = modifyDNOperation.getOriginalEntry(); List<String> fractionalConcernedAttributes = createFractionalConcernedAttrList( concernedEntry.getObjectClasses().keySet()); if ( fractionalExclusive && (fractionalConcernedAttributes.size() == 0) ) // No attributes to filter return false; /** * Analyze the old and new rdn to see if they are some attributes to be * removed: if the oldnRDN contains some forbidden attributes (for instance * it is possible if the entry was created with an add operation and the * RDN used contains a forbidden attribute: in this case the attribute value * has been kept to be consistent with the dn of the entry.) that are no * more part of the new RDN, we must remove any attribute of this type by * putting a modification to delete the attribute. */ RDN rdn = modifyDNOperation.getEntryDN().getRDN(); RDN newRdn = modifyDNOperation.getNewRDN(); // Go through each attribute of the old RDN for (int i=0 ; i<rdn.getNumValues() ; i++) { AttributeType attributeType = rdn.getAttributeType(i); boolean found = false; // Is it present in the fractional attributes established list ? for (String attrTypeStr : fractionalConcernedAttributes) { AttributeType attributeTypeFromList = DirectoryServer.getAttributeType(attrTypeStr); if (attributeTypeFromList.equals(attributeType)) { found = true; break; } } boolean attributeToBeFiltered = ( (fractionalExclusive && found) || (!fractionalExclusive && !found) ); if (attributeToBeFiltered && !newRdn.hasAttributeType(attributeType) && !modifyDNOperation.deleteOldRDN()) { // A forbidden attribute is in the old RDN and no more in the new RDN, // and it has not been requested to remove attributes from old RDN: // remove ourself the attribute from the entry to stay consistent with // fractional configuration Modification modification = new Modification(ModificationType.DELETE, Attributes.empty(attributeType)); modifyDNOperation.addModification(modification); inconsistentOperation = true; } } return inconsistentOperation; } /** * Remove attributes from an entry, according to the current fractional * configuration. The entry is represented by the 2 passed parameters. * The attributes to be removed are removed using the remove method on the * passed iterator for the attributes in the entry. * @param entryRdn The rdn of the entry to add * @param classes The object classes representing the entry to modify * @param attributesMap The map of attributes/values to be potentially removed * from the entry. * @param performFiltering Tells if the effective attribute filtering should * be performed or if the call is just an analyze to see if there are some * attributes filtered by fractional configuration * @return true if the operation contains some attributes subject to filtering * by the fractional configuration */ public boolean fractionalRemoveAttributesFromEntry(RDN entryRdn, Map<ObjectClass,String> classes, Map<AttributeType, List<Attribute>> attributesMap, boolean performFiltering) { boolean hasSomeAttributesToFilter = false; /* * Prepare a list of attributes to be included/excluded according to the * fractional replication configuration */ List<String> fractionalConcernedAttributes = createFractionalConcernedAttrList(classes.keySet()); if ( fractionalExclusive && (fractionalConcernedAttributes.size() == 0) ) return false; // No attributes to filter // Prepare list of object classes of the added entry Set<ObjectClass> entryClasses = classes.keySet(); /* * Go through the user attributes and remove those that match filtered one * - exclude mode : remove only attributes that are in * fractionalConcernedAttributes * - include mode : remove any attribute that is not in * fractionalConcernedAttributes */ Iterator<AttributeType> attributeTypes = attributesMap.keySet().iterator(); List<List<Attribute>> newRdnAttrLists = new ArrayList<List<Attribute>>(); List<AttributeType> rdnAttrTypes = new ArrayList<AttributeType>(); while (attributeTypes.hasNext()) { AttributeType attributeType = attributeTypes.next(); // Only optional attributes may be removed boolean isMandatoryAttribute = false; for (ObjectClass objectClass : entryClasses) { if (objectClass.isRequired(attributeType)) { isMandatoryAttribute = true; break; } } if (isMandatoryAttribute) { continue; } String attributeName = attributeType.getPrimaryName(); String attributeOid = attributeType.getOID(); // Do not remove an attribute if it is a prohibited one if (((attributeName != null) && isFractionalProhibitedAttr(attributeName)) || isFractionalProhibitedAttr(attributeOid)) { continue; } // Is the current attribute part of the established list ? boolean foundAttribute = fractionalConcernedAttributes.contains(attributeName.toLowerCase()); if (!foundAttribute) { foundAttribute = fractionalConcernedAttributes.contains(attributeOid); } // Now remove the attribute if: // - exclusive mode and attribute is in configuration // - inclusive mode and attribute is not in configuration if ((foundAttribute && fractionalExclusive) || (!foundAttribute && !fractionalExclusive)) { if (performFiltering) { // Do not remove an attribute/value that is part of the RDN of the // entry as it is forbidden if (entryRdn.hasAttributeType(attributeType)) { // We must remove all values of the attributes map for this // attribute type but the one that has the value which is in the RDN // of the entry. In fact the (underlying )attribute list does not // suppot remove so we have to create a new list, keeping only the // attribute value which is the same as in the RDN AttributeValue rdnAttributeValue = entryRdn.getAttributeValue(attributeType); List<Attribute> attrList = attributesMap.get(attributeType); Iterator<Attribute> attrIt = attrList.iterator(); AttributeValue sameAttrValue = null; // Locate the attribute value identical to the one in the RDN while(attrIt.hasNext()) { Attribute attr = attrIt.next(); if (attr.contains(rdnAttributeValue)) { Iterator<AttributeValue> attrValues = attr.iterator(); while(attrValues.hasNext()) { AttributeValue attrValue = attrValues.next(); if (rdnAttributeValue.equals(attrValue)) { // Keep the value we want sameAttrValue = attrValue; } else { hasSomeAttributesToFilter = true; } } } else { hasSomeAttributesToFilter = true; } } // Recreate the attribute list with only the RDN attribute value if (sameAttrValue != null) // Paranoia check: should never be the case as we should always // find the attribute/value pair matching the pair in the RDN { // Construct and store new atribute list List<Attribute> newRdnAttrList = new ArrayList<Attribute>(); AttributeBuilder attrBuilder = new AttributeBuilder(attributeType); attrBuilder.add(sameAttrValue); newRdnAttrList.add(attrBuilder.toAttribute()); newRdnAttrLists.add(newRdnAttrList); // Store matching attribute type // The mapping will be done using object from rdnAttrTypes as key // and object from newRdnAttrLists (at same index) as value in // the user attribute map to be modified rdnAttrTypes.add(attributeType); } } else { // Found an attribute to remove, remove it from the list. attributeTypes.remove(); hasSomeAttributesToFilter = true; } } else { // The call was just to check : at least one attribute to filter // found, return immediatly the answer; return true; } } } // Now overwrite the attribute values for the attribute types present in the // RDN, if there are some filtered attributes in the RDN int index = 0; for (index = 0 ; index < rdnAttrTypes.size() ; index++) { attributesMap.put(rdnAttrTypes.get(index), newRdnAttrLists.get(index)); } return hasSomeAttributesToFilter; } /** * Prepares a list of attributes of interest for the fractional feature. * @param entryObjectClasses The object classes of an entry on which an * operation is going to be performed. * @return The list of attributes of the entry to be excluded/included * when the operation will be performed. */ private List<String> createFractionalConcernedAttrList( Set<ObjectClass> entryObjectClasses) { /* * Is the concerned entry of a type concerned by fractional replication * configuration ? If yes, add the matching attribute names to a list of * attributes to take into account for filtering * (inclusive or exclusive mode) */ List<String> fractionalConcernedAttributes = new ArrayList<String>(); // Get object classes the entry matches Set<String> fractionalClasses = fractionalSpecificClassesAttributes.keySet(); for (ObjectClass entryObjectClass : entryObjectClasses) { for(String fractionalClass : fractionalClasses) { if (entryObjectClass.hasNameOrOID(fractionalClass.toLowerCase())) { List<String> attrList = fractionalSpecificClassesAttributes.get(fractionalClass); for(String attr : attrList) { // Avoid duplicate attributes (from 2 inheriting classes for // instance) if (!fractionalConcernedAttributes.contains(attr)) { fractionalConcernedAttributes.add(attr); } } } } } /* * Add to the list any attribute which is class independent */ for (String attr : fractionalAllClassesAttributes) { if (!fractionalConcernedAttributes.contains(attr)) { fractionalConcernedAttributes.add(attr); } } return fractionalConcernedAttributes; } /** * If fractional replication is enabled, this analyzes the operation and * suppresses the forbidden attributes in it so that they are not added/ * deleted/modified in the local backend. * * @param modifyOperation The operation to modify based on fractional * replication configuration * @param performFiltering Tells if the effective attribute filtering should * be performed or if the call is just to analyze if there are some * attributes filtered by fractional configuration * @return FRACTIONAL_HAS_FRACTIONAL_FILTERED_ATTRIBUTES, * FRACTIONAL_HAS_NO_FRACTIONAL_FILTERED_ATTRIBUTES or FRACTIONAL_BECOME_NO_OP */ public int fractionalFilterOperation(PreOperationModifyOperation modifyOperation, boolean performFiltering) { int result = FRACTIONAL_HAS_NO_FRACTIONAL_FILTERED_ATTRIBUTES; /* * Prepare a list of attributes to be included/excluded according to the * fractional replication configuration */ Entry modifiedEntry = modifyOperation.getCurrentEntry(); List<String> fractionalConcernedAttributes = createFractionalConcernedAttrList( modifiedEntry.getObjectClasses().keySet()); if ( fractionalExclusive && (fractionalConcernedAttributes.size() == 0) ) // No attributes to filter return FRACTIONAL_HAS_NO_FRACTIONAL_FILTERED_ATTRIBUTES; // Prepare list of object classes of the modified entry DN entryToModifyDn = modifyOperation.getEntryDN(); Entry entryToModify = null; try { entryToModify = DirectoryServer.getEntry(entryToModifyDn); } catch(DirectoryException e) { Message message = NOTE_ERR_FRACTIONAL.get(baseDn.toString(), e.getLocalizedMessage()); logError(message); return FRACTIONAL_HAS_NO_FRACTIONAL_FILTERED_ATTRIBUTES; } Set<ObjectClass> entryClasses = entryToModify.getObjectClasses().keySet(); /* * Now go through the attribute modifications and filter the mods according * to the fractional configuration (using the just established concerned * attributes list): * - delete attributes: remove them if regarding a filtered attribute * - add attributes: remove them if regarding a filtered attribute * - modify attributes: remove them if regarding a filtered attribute */ List<Modification> mods = modifyOperation.getModifications(); Iterator<Modification> modsIt = mods.iterator(); while (modsIt.hasNext()) { Modification mod = modsIt.next(); Attribute attr = mod.getAttribute(); AttributeType attrType = attr.getAttributeType(); // Fractional replication ignores operational attributes if (!attrType.isOperational()) { // Only optional attributes may be removed boolean isMandatoryAttribute = false; for (ObjectClass objectClass : entryClasses) { if (objectClass.isRequired(attrType)) { isMandatoryAttribute = true; break; } } if (isMandatoryAttribute) { continue; } String attributeName = attrType.getPrimaryName(); String attributeOid = attrType.getOID(); // Do not remove an attribute if it is a prohibited one if (((attributeName != null) && isFractionalProhibitedAttr(attributeName)) || isFractionalProhibitedAttr(attributeOid)) { continue; } // Is the current attribute part of the established list ? boolean foundAttribute = fractionalConcernedAttributes.contains(attributeName.toLowerCase()); if (!foundAttribute) { foundAttribute = fractionalConcernedAttributes.contains(attributeOid); } // Now remove the modification if: // - exclusive mode and the concerned attribute is in configuration // - inclusive mode and the concerned attribute is not in configuration if ( (foundAttribute && fractionalExclusive) || (!foundAttribute && !fractionalExclusive) ) { if (performFiltering) { // Found a modification to remove, remove it from the list. modsIt.remove(); result = FRACTIONAL_HAS_FRACTIONAL_FILTERED_ATTRIBUTES; if (mods.size() == 0) { // This operation must become a no-op as no more modification in // it return FRACTIONAL_BECOME_NO_OP; } } else { // The call was just to check : at least one attribute to filter // found, return immediatly the answer; return FRACTIONAL_HAS_FRACTIONAL_FILTERED_ATTRIBUTES; } } } } return result; } /** * This is overwritten to allow stopping the (online) import process by the * fractional ldif import plugin when it detects that the (imported) remote * data set is not consistent with the local fractional configuration. * {@inheritDoc} */ @Override protected byte[] receiveEntryBytes() { if (followImport) { // Ok, next entry is allowed to be received return super.receiveEntryBytes(); } else { // Fractional ldif import plugin detected inconsistency between local // and remote server fractional configuration and is stopping the import // process: // This is an error termination during the import // The error is stored and the import is ended // by returning null Message msg = null; switch (importErrorMessageId) { case IMPORT_ERROR_MESSAGE_BAD_REMOTE: msg = NOTE_ERR_FULL_UPDATE_IMPORT_FRACTIONAL_BAD_REMOTE.get( baseDn.toString(), Short.toString(ieContext.getImportSource())); break; case IMPORT_ERROR_MESSAGE_REMOTE_IS_FRACTIONAL: msg = NOTE_ERR_FULL_UPDATE_IMPORT_FRACTIONAL_REMOTE_IS_FRACTIONAL.get( baseDn.toString(), Short.toString(ieContext.getImportSource())); break; } ieContext.setException( new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg)); return null; } } /** * This is overwritten to allow stopping the (online) export process if the * local domain is fractional and the destination is all other servers: * This make no sense to have only fractional servers in a replicated * topology. This prevents from administrator manipulation error that would * lead to whole topology data corruption. * {@inheritDoc} */ @Override protected void initializeRemote(short target, short requestorID, Task initTask) throws DirectoryException { if ((target == RoutableMsg.ALL_SERVERS) && fractional) { Message msg = NOTE_ERR_FRACTIONAL_FORBIDDEN_FULL_UPDATE_FRACTIONAL.get( baseDn.toString(), Short.toString(getServerId())); throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg); } else { super.initializeRemote(target, requestorID, initTask); } } /** * Returns the base DN of this ReplicationDomain. * * @return The base DN of this ReplicationDomain @@ -580,6 +2076,38 @@ ResultCode.UNWILLING_TO_PERFORM, msg); } if (fractional) { if (addOperation.isSynchronizationOperation()) { /* * Filter attributes here for fractional replication. If fractional * replication is enabled, we analyze the operation to suppress the * forbidden attributes in it so that they are not added in the local * backend. This must be called before any other plugin is called, to * keep coherency across plugin calls. */ fractionalFilterOperation(addOperation, true); } else { /* * Direct access from an LDAP client : if some attributes are to be * removed according to the fractional configuration, simply forbid * the operation */ if (fractionalFilterOperation(addOperation, false)) { StringBuilder sb = new StringBuilder(); addOperation.toString(sb); Message msg = NOTE_ERR_FRACTIONAL_FORBIDDEN_OPERATION.get( baseDn.toString(), sb.toString()); return new SynchronizationProviderResult.StopProcessing( ResultCode.UNWILLING_TO_PERFORM, msg); } } } if (addOperation.isSynchronizationOperation()) { AddContext ctx = (AddContext) addOperation.getAttachment(SYNCHROCONTEXT); @@ -686,6 +2214,36 @@ ResultCode.UNWILLING_TO_PERFORM, msg); } if (fractional) { if (modifyDNOperation.isSynchronizationOperation()) { /* * Filter operation here for fractional replication. If fractional * replication is enabled, we analyze the operation and modify it if * necessary to stay consistent with what is defined in fractional * configuration. */ fractionalFilterOperation(modifyDNOperation, true); } else { /* * Direct access from an LDAP client : something is inconsistent with * the fractional configuration, forbid the operation. */ if (fractionalFilterOperation(modifyDNOperation, false)) { StringBuilder sb = new StringBuilder(); modifyDNOperation.toString(sb); Message msg = NOTE_ERR_FRACTIONAL_FORBIDDEN_OPERATION.get( baseDn.toString(), sb.toString()); return new SynchronizationProviderResult.StopProcessing( ResultCode.UNWILLING_TO_PERFORM, msg); } } } ModifyDnContext ctx = (ModifyDnContext) modifyDNOperation.getAttachment(SYNCHROCONTEXT); if (ctx != null) @@ -775,6 +2333,51 @@ ResultCode.UNWILLING_TO_PERFORM, msg); } if (fractional) { if (modifyOperation.isSynchronizationOperation()) { /* * Filter attributes here for fractional replication. If fractional * replication is enabled, we analyze the operation and modify it so * that no forbidden attribute is added/modified/deleted in the local * backend. This must be called before any other plugin is called, to * keep coherency across plugin calls. */ if (fractionalFilterOperation(modifyOperation, true) == FRACTIONAL_BECOME_NO_OP) { // Every modifications filtered in this operation: the operation // becomes a no-op return new SynchronizationProviderResult.StopProcessing( ResultCode.SUCCESS, null); } } else { /* * Direct access from an LDAP client : if some attributes are to be * removed according to the fractional configuration, simply forbid * the operation */ switch(fractionalFilterOperation(modifyOperation, false)) { case FRACTIONAL_HAS_NO_FRACTIONAL_FILTERED_ATTRIBUTES: // Ok, let the operation happen break; case FRACTIONAL_HAS_FRACTIONAL_FILTERED_ATTRIBUTES: // Some attributes not compliant with fractional configuration : // forbid the operation StringBuilder sb = new StringBuilder(); modifyOperation.toString(sb); Message msg = NOTE_ERR_FRACTIONAL_FORBIDDEN_OPERATION.get( baseDn.toString(), sb.toString()); return new SynchronizationProviderResult.StopProcessing( ResultCode.UNWILLING_TO_PERFORM, msg); } } } ModifyContext ctx = (ModifyContext) modifyOperation.getAttachment(SYNCHROCONTEXT); @@ -2045,12 +3648,6 @@ } /** * The attribute name used to store the state in the backend. */ protected static final String REPLICATION_GENERATION_ID = "ds-sync-generation-id"; /** * Stores the value of the generationId. * @param generationId The value of the generationId. * @return a ResultCode indicating if the method was successful. @@ -2136,7 +3733,7 @@ /* * Search the database entry that is used to periodically * save the ServerState * save the generation id */ InternalSearchOperation search = null; LinkedHashSet<String> attributes = new LinkedHashSet<String>(1); @@ -2534,6 +4131,11 @@ includeBranches.add(this.baseDn); importConfig.setIncludeBranches(includeBranches); importConfig.setAppendToExistingData(false); // Allow fractional replication ldif import plugin to be called importConfig.setInvokeImportPlugins(true); // Reset the follow import flag and message before starting the import importErrorMessageId = -1; followImport = true; // TODO How to deal with rejected entries during the import importConfig.writeRejectedEntries( @@ -2736,6 +4338,17 @@ unacceptableReasons.add(message); return false; } // Check fractional configuration try { isFractionalConfigAcceptable(configuration); } catch (ConfigException e) { unacceptableReasons.add(e.getMessageObject()); return false; } return true; } @@ -2756,6 +4369,9 @@ // Read assured configuration and reconnect if needed readAssuredConfig(configuration, true); // Read fractional configuration and reconnect if needed readFractionalConfig(configuration, true); return new ConfigChangeResult(ResultCode.SUCCESS, false); } @@ -2765,14 +4381,25 @@ public boolean isConfigurationChangeAcceptable( ReplicationDomainCfg configuration, List<Message> unacceptableReasons) { // Check that a import/export is not in progress if (this.importInProgress() || this.exportInProgress()) { unacceptableReasons.add( NOTE_ERR_CANNOT_CHANGE_CONFIG_DURING_TOTAL_UPDATE.get()); return false; } else return true; // Check fractional configuration try { isFractionalConfigAcceptable(configuration); } catch (ConfigException e) { unacceptableReasons.add(e.getMessageObject()); return false; } return true; } /** @@ -2827,8 +4454,25 @@ long generationID, ProtocolSession session) { // Check domain fractional configuration consistency with local // configuration variables force_bad_data_set = !isBackendFractionalConfigConsistent(); super.sessionInitiated( initStatus, replicationServerState,generationID, session); // Now for bad data set status if needed if (force_bad_data_set) { // Go into bad data set status setNewStatus(StatusMachineEvent.TO_BAD_GEN_ID_STATUS_EVENT); broker.signalStatusChange(status); Message message = NOTE_FRACTIONAL_BAD_DATA_SET_NEED_RESYNC.get( baseDn.toString()); logError(message); return; // Do not send changes to the replication server } try { /* @@ -3036,6 +4680,13 @@ @Override public boolean processUpdate(UpdateMsg updateMsg) { // Ignore message if fractional configuration is inconcsistent and // we have been passed into bad data set status if (force_bad_data_set) { return false; } if (updateMsg instanceof LDAPUpdateMsg) { LDAPUpdateMsg msg = (LDAPUpdateMsg) updateMsg; opends/src/server/org/opends/server/replication/protocol/MonitorMsg.java
@@ -78,7 +78,7 @@ new HashMap<Short, ServerData>(); } SubTopoMonitorData data = new SubTopoMonitorData();; SubTopoMonitorData data = new SubTopoMonitorData(); /** * Creates a new EntryMessage. opends/src/server/org/opends/server/replication/service/ReplicationDomain.java
@@ -151,7 +151,7 @@ /** * Current status for this replicated domain. */ private ServerStatus status = ServerStatus.NOT_CONNECTED_STATUS; protected ServerStatus status = ServerStatus.NOT_CONNECTED_STATUS; /** * The tracer object for the debug logger. @@ -176,7 +176,7 @@ * The ReplicationBroker that is used by this ReplicationDomain to * connect to the ReplicationService. */ private ReplicationBroker broker = null; protected ReplicationBroker broker = null; /** * This Map is used to store all outgoing assured messages in order @@ -191,7 +191,7 @@ * The context related to an import or export being processed * Null when none is being processed. */ private IEContext ieContext = null; protected IEContext ieContext = null; /** * The Thread waiting for incoming update messages for this domain and pushing @@ -788,6 +788,7 @@ mb.append(de.getMessageObject()); TRACER.debugInfo(Message.toString(mb.toMessage())); broker.publish(errorMsg); logError(de.getMessageObject()); } } else if (msg instanceof ErrorMsg) @@ -1076,11 +1077,11 @@ * This class contain the context related to an import or export * launched on the domain. */ private class IEContext protected class IEContext { // The task that initiated the operation. // Theprivate task that initiated the operation. Task initializeTask; // The target in the case of an export // The destination in the case of an export short exportTarget = RoutableMsg.UNKNOWN_SERVER; // The source in the case of an import short importSource = RoutableMsg.UNKNOWN_SERVER; @@ -1111,9 +1112,9 @@ /** * Initializes the import/export counters with the provider value. * @param total * @param left * @throws DirectoryException * @param total Total number of entries to be processed. * @param left Remaining number of entries to be processed. * @throws DirectoryException if an error occurred. */ public void setCounters(long total, long left) throws DirectoryException @@ -1139,7 +1140,7 @@ /** * Update the counters of the task for each entry processed during * an import or export. * @throws DirectoryException * @throws DirectoryException if an error occurred. */ public void updateCounters() throws DirectoryException @@ -1166,7 +1167,7 @@ * @param entriesDone The number of entries that were processed * since the last time this method was called. * * @throws DirectoryException * @throws DirectoryException if an error occurred. */ public void updateCounters(int entriesDone) throws DirectoryException @@ -1195,6 +1196,42 @@ return new String("[ Entry count=" + this.entryCount + ", Entry left count=" + this.entryLeftCount + "]"); } /** * Gets the server id of the exporting server. * @return the server id of the exporting server. */ public short getExportTarget() { return exportTarget; } /** * Gets the server id of the importing server. * @return the server id of the importing server. */ public short getImportSource() { return importSource; } /** * Get the exception that occurred during the import/export. * @return the exception that occurred during the import/export. */ public DirectoryException getException() { return exception; } /** * Set the exception that occurred during the import/export. * @param exception the exception that occurred during the import/export. */ public void setException(DirectoryException exception) { this.exception = exception; } } /** * Verifies that the given string represents a valid source @@ -1304,8 +1341,8 @@ * * @exception DirectoryException When an error occurs. */ void initializeRemote(short target, short requestorID, Task initTask) throws DirectoryException protected void initializeRemote(short target, short requestorID, Task initTask) throws DirectoryException { Message msg = NOTE_FULL_UPDATE_ENGAGED_FOR_REMOTE_START.get( Short.toString(serverID), @@ -1417,14 +1454,14 @@ if (ieContext != null) { ieContext.exception = new DirectoryException(ResultCode.OTHER, errorMsg.getDetails()); ieContext.setException(new DirectoryException(ResultCode.OTHER, errorMsg.getDetails())); if (ieContext.initializeTask instanceof InitializeTask) { // Update the task that initiated the import ((InitializeTask)ieContext.initializeTask). updateTaskCompletionState(ieContext.exception); updateTaskCompletionState(ieContext.getException()); releaseIEContext(); } @@ -1437,7 +1474,7 @@ * * @return The bytes. Null when the Done or Err message has been received */ byte[] receiveEntryBytes() protected byte[] receiveEntryBytes() { ReplicationMsg msg; while (true) @@ -1477,9 +1514,8 @@ // The error is stored and the import is ended // by returning null ErrorMsg errorMsg = (ErrorMsg)msg; ieContext.exception = new DirectoryException( ResultCode.OTHER, errorMsg.getDetails()); ieContext.setException(new DirectoryException(ResultCode.OTHER, errorMsg.getDetails())); return null; } else @@ -1490,9 +1526,9 @@ catch(Exception e) { // TODO: i18n ieContext.exception = new DirectoryException(ResultCode.OTHER, Message.raw("received an unexpected message type" + e.getLocalizedMessage())); ieContext.setException(new DirectoryException(ResultCode.OTHER, Message.raw("received an unexpected message type" + e.getLocalizedMessage()))); } } } @@ -1547,15 +1583,15 @@ { // If an error was raised - like receiving an ErrorMsg // we just let down the export. if (ieContext.exception != null) if (ieContext.getException() != null) { IOException ioe = new IOException(ieContext.exception.getMessage()); IOException ioe = new IOException(ieContext.getException().getMessage()); ieContext = null; throw ioe; } EntryMsg entryMessage = new EntryMsg( serverID, ieContext.exportTarget, lDIFEntry, pos, length); serverID,ieContext.getExportTarget(), lDIFEntry, pos, length); broker.publish(entryMessage); try @@ -1702,8 +1738,8 @@ } finally { if ((ieContext != null) && (ieContext.exception != null)) de = ieContext.exception; if ((ieContext != null) && (ieContext.getException() != null)) de = ieContext.getException(); // Update the task that initiated the import if ((ieContext != null ) && (ieContext.initializeTask != null)) @@ -1732,7 +1768,7 @@ * event. * @param event The event that may make the status be changed */ private void setNewStatus(StatusMachineEvent event) protected void setNewStatus(StatusMachineEvent event) { ServerStatus newStatus = StatusMachine.computeNewStatus(status, event); opends/tests/unit-tests-testng/src/server/org/opends/server/replication/ReplicationTestCase.java
@@ -1202,4 +1202,34 @@ throw new Exception("Entry: " + dn + " Could not be found."); return found; } /** * Utility method : removes a domain deleting the passed config entry */ protected void removeDomain(Entry domainCfgEntry) { DeleteOperationBasis op; // Delete entries try { DN dn = domainCfgEntry.getDN(); logError(Message.raw(Category.SYNC, Severity.NOTICE, "cleaning config entry " + dn)); op = new DeleteOperationBasis(connection, InternalClientConnection. nextOperationID(), InternalClientConnection.nextMessageID(), null, dn); op.run(); if ((op.getResultCode() != ResultCode.SUCCESS) && (op.getResultCode() != ResultCode.NO_SUCH_OBJECT)) { fail("Deleting config entry" + dn + " failed: " + op.getResultCode().getResultCodeName()); } } catch (NoSuchElementException e) { // done } } } opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/AssuredReplicationPluginTest.java
@@ -266,36 +266,6 @@ } /** * Removes a domain deleting the passed config entry */ private void removeDomain(Entry domainCfgEntry) { DeleteOperationBasis op; // Delete entries try { DN dn = domainCfgEntry.getDN(); logError(Message.raw(Category.SYNC, Severity.NOTICE, "cleaning config entry " + dn)); op = new DeleteOperationBasis(connection, InternalClientConnection. nextOperationID(), InternalClientConnection.nextMessageID(), null, dn); op.run(); if ((op.getResultCode() != ResultCode.SUCCESS) && (op.getResultCode() != ResultCode.NO_SUCH_OBJECT)) { fail("Deleting config entry" + dn + " failed: " + op.getResultCode().getResultCodeName()); } } catch (NoSuchElementException e) { // done } } /** * The fake replication server used to emulate RS behaviour the way we want * for assured features test. * This fake replication server is able to receive a DS connection only. opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/DomainFakeCfg.java
@@ -26,6 +26,7 @@ */ package org.opends.server.replication.plugin; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; @@ -63,6 +64,9 @@ // Referrals urls to be published to other servers of the topology SortedSet<String> refUrls = new TreeSet<String>(); private SortedSet<String> fractionalExcludes = new TreeSet<String>(); private SortedSet<String> fractionalIncludes = new TreeSet<String>(); /** * Creates a new Domain with the provided information * (assured mode disabled, default group id) @@ -76,6 +80,30 @@ /** * Creates a new Domain with the provided information * (with some fractional configuration provided) */ public DomainFakeCfg(DN baseDn, int serverId, SortedSet<String> replServers, List<String> fractionalExcludes, List<String> fractionalIncludes) { this(baseDn, serverId, replServers); if (fractionalExcludes != null) { for (String str : fractionalExcludes) { this.fractionalExcludes.add(str); } } if (fractionalIncludes != null) { for (String str : fractionalIncludes) { this.fractionalIncludes.add(str); } } } /** * Creates a new Domain with the provided information * (assured mode disabled, group id provided) */ public DomainFakeCfg(DN baseDn, int serverId, SortedSet<String> replServers, @@ -305,4 +333,14 @@ { return refUrls; } public SortedSet<String> getFractionalExclude() { return fractionalExcludes; } public SortedSet<String> getFractionalInclude() { return fractionalIncludes; } } opends/tests/unit-tests-testng/src/server/org/opends/server/replication/plugin/FractionalReplicationTest.java
New file @@ -0,0 +1,2031 @@ /* * 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 * * * Copyright 2009 Sun Microsystems, Inc. */ package org.opends.server.replication.plugin; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import org.opends.messages.Category; import org.opends.messages.Message; import org.opends.messages.Severity; import org.opends.server.TestCaseUtils; import org.opends.server.config.ConfigException; import org.opends.server.core.AddOperationBasis; import org.opends.server.core.DirectoryServer; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.protocols.internal.InternalClientConnection; import org.opends.server.replication.ReplicationTestCase; import org.opends.server.replication.common.ChangeNumberGenerator; import org.opends.server.replication.common.ServerStatus; import org.opends.server.replication.protocol.AddMsg; import org.opends.server.replication.protocol.ModifyDNMsg; import org.opends.server.replication.protocol.ModifyMsg; import org.opends.server.replication.protocol.UpdateMsg; import org.opends.server.replication.server.ReplServerFakeConfiguration; import org.opends.server.replication.server.ReplicationServer; import org.opends.server.replication.service.ReplicationDomain; import org.opends.server.types.Attribute; import org.opends.server.types.AttributeType; import org.opends.server.types.AttributeValue; import org.opends.server.types.Attributes; import org.opends.server.types.DN; import org.opends.server.types.DirectoryException; import org.opends.server.types.Entry; import org.opends.server.types.Modification; import org.opends.server.types.ModificationType; import org.opends.server.types.ObjectClass; import org.opends.server.types.ResultCode; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static org.testng.Assert.*; import static org.opends.server.TestCaseUtils.*; import static org.opends.server.loggers.ErrorLogger.logError; import static org.opends.server.loggers.debug.DebugLogger.debugEnabled; import static org.opends.server.loggers.debug.DebugLogger.getTracer; import static org.opends.messages.ReplicationMessages.*; /** * Various tests around fractional replication */ public class FractionalReplicationTest extends ReplicationTestCase { // The RS private ReplicationServer replicationServer = null; // RS port private int replServerPort = -1; // Represents the real domain to test (replays and filters) private Entry fractionalDomainCfgEntry = null; // The domain used to send updates to the reald domain private FakeReplicationDomain replicationDomain = null; // Ids of servers private static final short DS1_ID = 1; // fractional domain private static final short DS2_ID = 2; // fake domain private static final short RS_ID = 91; // replication server private final String testName = this.getClass().getSimpleName(); // Fractional mode private static final int EXCLUDE_FRAC_MODE = 0; private static final int INCLUDE_FRAC_MODE = 1; private ChangeNumberGenerator gen = null; // The tracer object for the debug logger private static final DebugTracer TRACER = getTracer(); // Number of seconds before generating an error if some conditions not met private static final int TIMEOUT = 10; // Uuid of the manipulated entry private static final String ENTRY_UUID = "11111111-1111-1111-1111-111111111111"; private static final String ENTRY_UUID2 = "22222222-2222-2222-2222-222222222222"; private static final String ENTRY_UUID3 = "33333333-3333-3333-3333-333333333333"; // Dn of the manipulated entry private static String ENTRY_DN = "uid=1," + TEST_ROOT_DN_STRING; // Optional attribute not part of concerned attributes of the fractional // configuration during tests. It should not be impacted by fractional // mechanism private static final String OPTIONAL_ATTR = "description"; // Optional attribute used as synchronization attribute to know when the modify // operation has been processed (used as add new attribute in the modify operation) // It may or may not be part of the filtered attributes, depending on the fractional // test mode : exclusive or inclusive private static final String SYNCHRO_OPTIONAL_ATTR = "seeAlso"; // Second test backend private static final String TEST2_ROOT_DN_STRING = "dc=example,dc=com"; private static final String TEST2_ORG_DN_STRING = "o=test2," + TEST2_ROOT_DN_STRING; private static String ENTRY_DN2 = "uid=1," + TEST2_ORG_DN_STRING; private void debugInfo(String s) { logError(Message.raw(Category.SYNC, Severity.NOTICE, s)); if (debugEnabled()) { TRACER.debugInfo("** TEST **" + s); } } /** * Before starting the tests configure some stuff */ @BeforeClass @Override public void setUp() throws Exception { super.setUp(); // Find a free port for the replicationServer ServerSocket socket = TestCaseUtils.bindFreePort(); replServerPort = socket.getLocalPort(); socket.close(); } /** * Returns a bunch of single values for fractional-exclude configuration * attribute */ @DataProvider(name = "testExcludePrecommitProvider") private Object[][] testExcludePrecommitProvider() { return new Object[][] { { 1, new String[] {"inetOrgPerson", "displayName"}} }; } /** * Returns a bunch of single values for fractional-exclude configuration * attribute */ @DataProvider(name = "testExcludeNightlyProvider") private Object[][] testExcludeNightlyProvider() { return new Object[][] { { 1, new String[] {"INETORGPERSON", "DISPLAYNAME"}}, { 2, new String[] {"inetOrgPerson", "2.16.840.1.113730.3.1.241"}}, { 3, new String[] {"2.16.840.1.113730.3.2.2", "displayName"}}, { 4, new String[] {"2.16.840.1.113730.3.2.2", "2.16.840.1.113730.3.1.241"}}, { 5, new String[] {"inetOrgPerson", "displayName", "carLicense"}}, { 6, new String[] {"organizationalPerson", "title", "postalCode"}}, { 7, new String[] {"2.5.6.7", "title", "postalCode"}}, { 8, new String[] {"2.5.6.7", "TITLE", "2.5.4.17"}}, { 9, new String[] {"2.5.6.7", "2.5.4.12", "2.5.4.17"}}, { 10, new String[] {"*", "roomNumber"}}, { 11, new String[] {"*", "0.9.2342.19200300.100.1.6"}}, { 12, new String[] {"*", "postOfficeBox", "0.9.2342.19200300.100.1.6"}}, { 13, new String[] {"*", "2.5.4.18", "0.9.2342.19200300.100.1.6"}} }; } /** * Calls the testExclude test with a small set of data, for precommit test * purpose */ @Test(dataProvider = "testExcludePrecommitProvider") public void testExcludePrecommit(int testProviderLineId, String... fractionalConf) throws Exception { testExclude(testProviderLineId, fractionalConf); } /** * Calls the testExclude test with a larger set of data, for nightly tests * purpose */ @Test(dataProvider = "testExcludeNightlyProvider", groups = "slow") public void testExcludeNightly(int testProviderLineId, String... fractionalConf) throws Exception { testExclude(testProviderLineId, fractionalConf); } /** * Performs Add and Modify operations including attributes that are excluded * with the passed fractional configuration and checks that these attributes * are not part of the concerned entry. * Note: testProviderLineId just here to know what is the provider problematic * line if the test fail: prevent some display like: * [testng] parameter[0]: [Ljava.lang.String;@151e824 * but have instead: * [testng] parameter[0]: 6 * [testng] parameter[1]: [Ljava.lang.String;@151e824 */ private void testExclude(int testProviderLineId, String... fractionalConf) throws Exception { String testcase = "testExclude" + testProviderLineId; initTest(); try { // create replication server createReplicationServer(testcase); // create fractional domain with the passed fractional configuration createFractionalDomain(true, EXCLUDE_FRAC_MODE, fractionalConf); // create fake domain to send operations createFakeReplicationDomain(true, readGenIdFromSuffixRootEntry(TEST_ROOT_DN_STRING)); // perform add operation sendAddMsg(true, fractionalConf); // check that entry has been created and that it does not contain // forbidden attributes Entry newEntry = null; try { newEntry = getEntry(DN.decode(ENTRY_DN), TIMEOUT, true); } catch(Exception e) { fail("Entry has not been added: " + e.getMessage()); } checkEntryFilteredAfterAdd(newEntry, EXCLUDE_FRAC_MODE, fractionalConf); // perform modify operation (modify forbidden attributes + // modify authorized attribute (not a no op)) sendModifyMsg(true, fractionalConf); // Wait for modify operation being replayed and // check that entry does not contain forbidden attributes Entry entry = null; boolean synchroAttrFound = false; int timeout = TIMEOUT; while(timeout>0) { try { entry = getEntry(DN.decode(ENTRY_DN), TIMEOUT, true); if (entry.hasAttribute(DirectoryServer.getAttributeType(SYNCHRO_OPTIONAL_ATTR.toLowerCase()))) { synchroAttrFound = true; break; } Thread.sleep(1000); timeout--; } catch (Exception e) { fail("Error waiting for modify operation being replayed : " + e.getMessage()); } } assertTrue(synchroAttrFound, "Modify operation not replayed"); checkEntryFilteredAfterModify(entry, EXCLUDE_FRAC_MODE, fractionalConf); } finally { endTest(); } } /** * Returns a bunch of single values for fractional-include configuration * attribute */ @DataProvider(name = "testIncludePrecommitProvider") private Object[][] testIncludePrecommitProvider() { return new Object[][] { { 1, new String[] {"inetOrgPerson", "displayName"}} }; } /** * Returns a bunch of single values for fractional-include configuration * attribute */ @DataProvider(name = "testIncludeNightlyProvider") private Object[][] testIncludeNightlyProvider() { return new Object[][] { { 1, new String[] {"INETORGPERSON", "DISPLAYNAME"}}, { 2, new String[] {"inetOrgPerson", "2.16.840.1.113730.3.1.241"}}, { 3, new String[] {"2.16.840.1.113730.3.2.2", "displayName"}}, { 4, new String[] {"2.16.840.1.113730.3.2.2", "2.16.840.1.113730.3.1.241"}}, { 5, new String[] {"inetOrgPerson", "displayName", "carLicense"}}, { 6, new String[] {"organizationalPerson", "title", "postalCode"}}, { 7, new String[] {"2.5.6.7", "title", "postalCode"}}, { 8, new String[] {"2.5.6.7", "TITLE", "2.5.4.17"}}, { 9, new String[] {"2.5.6.7", "2.5.4.12", "2.5.4.17"}}, { 10, new String[] {"*", "roomNumber"}}, { 11, new String[] {"*", "0.9.2342.19200300.100.1.6"}}, { 12, new String[] {"*", "postOfficeBox", "0.9.2342.19200300.100.1.6"}}, { 13, new String[] {"*", "2.5.4.18", "0.9.2342.19200300.100.1.6"}} }; } /** * Calls the testInclude test with a small set of data, for precommit test * purpose */ @Test(dataProvider = "testIncludePrecommitProvider") public void testIncludePrecommit(int testProviderLineId, String... fractionalConf) throws Exception { testInclude(testProviderLineId, fractionalConf); } /** * Calls the testInclude test with a larger set of data, for nightly tests * purpose */ @Test(dataProvider = "testIncludeNightlyProvider", groups = "slow") public void testIncludeNightly(int testProviderLineId, String... fractionalConf) throws Exception { testInclude(testProviderLineId, fractionalConf); } /** * Performs Add and Modify operations including attributes that are excluded * with the passed fractional configuration and checks that these attributes * are not part of the concerned entry. * Note: testProviderLineId just here to know what is the provider problematic * line if the test fail: prevent some display like: * [testng] parameter[0]: [Ljava.lang.String;@151e824 * but have instead: * [testng] parameter[0]: 6 * [testng] parameter[1]: [Ljava.lang.String;@151e824 */ private void testInclude(int testProviderLineId, String... fractionalConf) throws Exception { String testcase = "testInclude" + testProviderLineId; initTest(); try { // create replication server createReplicationServer(testcase); // create fractional domain with the passed fractional configuration createFractionalDomain(true, INCLUDE_FRAC_MODE, fractionalConf); // create fake domain to send operations createFakeReplicationDomain(true, readGenIdFromSuffixRootEntry(TEST_ROOT_DN_STRING)); // perform add operation sendAddMsg(true, fractionalConf); // check that entry has been created and that it does not contain // forbidden attributes Entry newEntry = null; try { newEntry = getEntry(DN.decode(ENTRY_DN), TIMEOUT, true); } catch(Exception e) { fail("Entry has not been added: " + e.getMessage()); } checkEntryFilteredAfterAdd(newEntry, INCLUDE_FRAC_MODE, fractionalConf); // perform modify operation (modify forbidden attributes + // modify authorized attribute (not a no op)) sendModifyMsg(true, fractionalConf); // Wait for modify operation being replayed and // check that entry does not contain forbidden attributes Entry entry = null; boolean synchroAttrFound = false; int timeout = TIMEOUT; while(timeout>0) { try { entry = getEntry(DN.decode(ENTRY_DN), TIMEOUT, true); if (entry.hasAttribute(DirectoryServer.getAttributeType(SYNCHRO_OPTIONAL_ATTR.toLowerCase()))) { synchroAttrFound = true; break; } Thread.sleep(1000); timeout--; } catch (Exception e) { fail("Error waiting for modify operation being replayed : " + e.getMessage()); } } assertTrue(synchroAttrFound, "Modify operation not replayed"); checkEntryFilteredAfterModify(entry, INCLUDE_FRAC_MODE, fractionalConf); } finally { endTest(); } } /** * Creates connects (to the RS) and starts the fake replication domain * Use the passed generation id. */ private void createFakeReplicationDomain(boolean firstBackend, long generationId) { try{ List<String> replicationServers = new ArrayList<String>(); replicationServers.add("localhost:" + replServerPort); replicationDomain = new FakeReplicationDomain( (firstBackend ? TEST_ROOT_DN_STRING : TEST2_ROOT_DN_STRING), DS2_ID, replicationServers, 100, 1000, generationId); // Test connection assertTrue(replicationDomain.isConnected()); int rdPort = -1; // Check connected server port String serverStr = replicationDomain.getReplicationServer(); int index = serverStr.lastIndexOf(':'); if ((index == -1) || (index >= serverStr.length())) fail("Enable to find port number in: " + serverStr); String rdPortStr = serverStr.substring(index + 1); try { rdPort = (new Integer(rdPortStr)).intValue(); } catch (Exception e) { fail("Enable to get an int from: " + rdPortStr); } assertEquals(rdPort, replServerPort); } catch (Exception e) { fail("createreplicationDomain " + e.getMessage()); } } private void initTest() { replicationDomain = null; fractionalDomainCfgEntry = null; replicationServer = null; // Initialize the test backend try { TestCaseUtils.initializeTestBackend(false); } catch(Exception e) { fail("Could not initialize backend : " + e.getMessage()); } // initialize cn generator gen = new ChangeNumberGenerator(DS2_ID, 0L); } private void endTest() { if (replicationDomain != null) { replicationDomain.disableService(); replicationDomain = null; } if (fractionalDomainCfgEntry != null) { removeDomain(fractionalDomainCfgEntry); fractionalDomainCfgEntry = null; } if (replicationServer != null) { replicationServer.clearDb(); replicationServer.remove(); replicationServer = null; } } /** * Creates a fractional domain with the passed configuration. * Before that, initializes the backend with the root entry and if requested * with the correct fractional configuration in it */ private void createFractionalDomain(boolean initializeDomain, int fractionalMode, String... fractionalConf) { try { String fractModeAttrName = null; String opFractModeAttrName = null; boolean addSynchroAttribute = false; switch (fractionalMode) { case EXCLUDE_FRAC_MODE: fractModeAttrName = "ds-cfg-fractional-exclude"; opFractModeAttrName = "ds-sync-fractional-exclude"; break; case INCLUDE_FRAC_MODE: fractModeAttrName = "ds-cfg-fractional-include"; opFractModeAttrName = "ds-sync-fractional-include"; // For inclusive mode, we use an attribute that is added in the modify // operation to know when the modify operation has been played. The added // attribute can only be part of the include config to be taken into account addSynchroAttribute = true; break; default: fail("Unexpected fractional mode."); } /** * Create a root entry with potentially with fractional configuration before domain creation */ // Create base entry with correct fractional config String topEntryLdif = null; if (initializeDomain) { // Add first backend top entry topEntryLdif = "dn: " + TEST_ROOT_DN_STRING + "\n" + "objectClass: top\n" + "objectClass: organization\n" + "o: " + TEST_BACKEND_ID + "\n" + "entryUUID: " + ENTRY_UUID3 + "\n"; // Add fractional config int i=0; int size = fractionalConf.length; for (String fracCfgValue : fractionalConf) // Add fractional operational attributes { if (i==0) { // First string is the class topEntryLdif += opFractModeAttrName + ": " + fracCfgValue + ":"; } else { // Other strings are attributes String endString = (addSynchroAttribute ? ("," + SYNCHRO_OPTIONAL_ATTR + "\n") : "\n"); topEntryLdif += fracCfgValue + ( (i<size-1) ? "," : endString); } i++; } } else { // Add second backend top entry topEntryLdif = "dn: " + TEST2_ROOT_DN_STRING + "\n" + "objectClass: top\n" + "objectClass: domain\n" + "dc: example\n"; } addEntry(TestCaseUtils.entryFromLdifString(topEntryLdif)); /** * Create the domain with the passed fractional configuration */ // Create a config entry ldif, matching passed settings String configEntryLdif = "dn: cn=" + testName + ", cn=domains, " + SYNCHRO_PLUGIN_DN + "\n" + "objectClass: top\n" + "objectClass: ds-cfg-replication-domain\n" + "cn: " + testName + "\n" + "ds-cfg-base-dn: " + (initializeDomain ? TEST_ROOT_DN_STRING : TEST2_ROOT_DN_STRING) + "\n" + "ds-cfg-replication-server: localhost:" + replServerPort + "\n" + "ds-cfg-server-id: " + DS1_ID + "\n"; int i=0; int size = fractionalConf.length; for (String fracCfgValue : fractionalConf) // Add fractional configuration attributes { if (i==0) { // First string is the class configEntryLdif += fractModeAttrName + ": " + fracCfgValue + ":"; } else { // Other strings are attributes String endString = (addSynchroAttribute ? ("," + SYNCHRO_OPTIONAL_ATTR + "\n") : "\n"); configEntryLdif += fracCfgValue + ( (i<size-1) ? "," : endString); } i++; } fractionalDomainCfgEntry = TestCaseUtils.entryFromLdifString(configEntryLdif); // Add the config entry to create the replicated domain DirectoryServer.getConfigHandler().addEntry(fractionalDomainCfgEntry, null); assertNotNull(DirectoryServer.getConfigEntry(fractionalDomainCfgEntry.getDN()), "Unable to add the domain config entry: " + configEntryLdif); } catch(Exception e) { fail("createFractionalDomain error: " + e.getMessage()); } } /** * Creates a new ReplicationServer. */ private void createReplicationServer(String testCase) { try { SortedSet<String> replServers = new TreeSet<String>(); String dir = testName + RS_ID + testCase + "Db"; ReplServerFakeConfiguration conf = new ReplServerFakeConfiguration(replServerPort, dir, 0, RS_ID, 0, 100, replServers); replicationServer = new ReplicationServer(conf); } catch (Exception e) { fail("createReplicationServer " + e.getMessage()); } } /** * This class is the minimum implementation of a Concrete ReplicationDomain * used to be able to connect to the RS with a known genid. Also to be able * to send updates */ private class FakeReplicationDomain extends ReplicationDomain { // A blocking queue that is used to receive updates from // the Replication Service. BlockingQueue<UpdateMsg> queue = new LinkedBlockingQueue<UpdateMsg>(); // A string that will be exported should exportBackend be called. String exportString = null; // A StringBuilder that will be used to build a new String should the // import be called. StringBuilder importString = null; private int exportedEntryCount; private long generationID = -1; public FakeReplicationDomain( String serviceID, short serverID, Collection<String> replicationServers, int window, long heartbeatInterval, long generationId) throws ConfigException { super(serviceID, serverID); generationID = generationId; startPublishService(replicationServers, window, heartbeatInterval); startListenService(); } public void initExport(String exportString, int exportedEntryCount) { this.exportString = exportString; this.exportedEntryCount = exportedEntryCount; } public void initImport(StringBuilder importString) { this.importString = importString; } @Override public long countEntries() throws DirectoryException { return exportedEntryCount; } @Override protected void exportBackend(OutputStream output) throws DirectoryException { try { output.write(exportString.getBytes()); output.flush(); output.close(); } catch (IOException e) { throw new DirectoryException(ResultCode.OPERATIONS_ERROR, ERR_BACKEND_EXPORT_ENTRY.get("", "")); } } @Override public long getGenerationID() { return generationID; } @Override protected void importBackend(InputStream input) throws DirectoryException { byte[] buffer = new byte[1000]; int ret; do { try { ret = input.read(buffer, 0, 1000); } catch (IOException e) { throw new DirectoryException( ResultCode.OPERATIONS_ERROR, ERR_BACKEND_EXPORT_ENTRY.get("", "")); } importString.append(new String(buffer, 0, ret)); } while (ret >= 0); } @Override public boolean processUpdate(UpdateMsg updateMsg) { if (queue != null) queue.add(updateMsg); return true; } public void setGenerationID(long newGenerationID) { generationID = newGenerationID; } } private static final String REPLICATION_GENERATION_ID = "ds-sync-generation-id"; private long readGenIdFromSuffixRootEntry(String rootDn) { long genId=-1; try { DN baseDn = DN.decode(rootDn); Entry resultEntry = getEntry(baseDn, 1000, true); if (resultEntry==null) { debugInfo("Entry not found <" + rootDn + ">"); } else { debugInfo("Entry found <" + rootDn + ">"); AttributeType synchronizationGenIDType = DirectoryServer.getAttributeType(REPLICATION_GENERATION_ID); List<Attribute> attrs = resultEntry.getAttribute(synchronizationGenIDType); if (attrs != null) { Attribute attr = attrs.get(0); if (attr.size() == 1) { genId = Long.decode(attr.iterator().next().getValue().toString()); } } } } catch(Exception e) { fail("Exception raised in readGenId", e); } return genId; } /** * Send the AddMsg (from the fake replication domain) for the passed entry * containing the attributes defined in the passed fractional configuration */ private void sendAddMsg(boolean firstBackend, String... fractionalConf) { String entryLdif = "dn: " + (firstBackend ? ENTRY_DN : ENTRY_DN2) + "\n" + "objectClass: top\n" + "objectClass: person\n" + "objectClass: organizationalPerson\n"; String classStr = ""; if ( fractionalConf[0].equalsIgnoreCase("inetOrgPerson") || fractionalConf[0].equalsIgnoreCase("2.16.840.1.113730.3.2.2")) { classStr = "objectClass: " + fractionalConf[0] + "\n"; } entryLdif += classStr + "uid: 1\n" + "entryUUID: " + ENTRY_UUID + "\n" + "sn: snValue\n" + "cn: cnValue\n" + OPTIONAL_ATTR + ": " + OPTIONAL_ATTR + "Value\n"; // Add attributes concerned by fractional configuration boolean first = true; for (String fracCfgValue : fractionalConf) { if (!first) { // First string is the class entryLdif += fracCfgValue + ": " + fracCfgValue + "Value\n"; } first = false; } Entry entry = null; try { entry = TestCaseUtils.entryFromLdifString(entryLdif); } catch (Exception e) { fail(e.getMessage()); } // Create an update message to add an entry. AddMsg addMsg = new AddMsg(gen.newChangeNumber(), entry.getDN().toString(), ENTRY_UUID, null, entry.getObjectClassAttribute(), entry.getAttributes(), new ArrayList<Attribute>()); replicationDomain.publish(addMsg); } /** * Send (from the fake replication domain) a ModifyMsg for the passed entry * modifying attributes defined in the passed fractional configuration */ private void sendModifyMsg(boolean firstBackend, String... fractionalConf) { // Create modifications on the fractional attributes List<Modification> mods = new ArrayList<Modification>(); boolean first = true; for (String fracCfgValue : fractionalConf) { if (!first) { // First string is the class Attribute attr = Attributes.create(fracCfgValue.toLowerCase(), fracCfgValue + "NewValue"); Modification mod = new Modification(ModificationType.REPLACE, attr); mods.add(mod); } first = false; } // Add modification for the special attribute (modified attribute) Attribute attr = Attributes.create(OPTIONAL_ATTR.toLowerCase(), OPTIONAL_ATTR + "NewValue"); Modification mod = new Modification(ModificationType.REPLACE, attr); mods.add(mod); // Add modification for the synchro attribute (added attribute) attr = Attributes.create(SYNCHRO_OPTIONAL_ATTR.toLowerCase(), SYNCHRO_OPTIONAL_ATTR + "Value"); mod = new Modification(ModificationType.ADD, attr); mods.add(mod); DN entryDn = null; try { entryDn = DN.decode((firstBackend ? ENTRY_DN : ENTRY_DN2)); } catch (Exception e) { fail("Cannot create dn entry: " + e.getMessage()); } ModifyMsg modifyMsg = new ModifyMsg(gen.newChangeNumber(), entryDn, mods, ENTRY_UUID); replicationDomain.publish(modifyMsg); } /** * Utility method : Add an entry in the database */ private void addEntry(Entry entry) throws Exception { AddOperationBasis addOp = new AddOperationBasis(connection, InternalClientConnection.nextOperationID(), InternalClientConnection. nextMessageID(), null, entry.getDN(), entry.getObjectClasses(), entry.getUserAttributes(), entry.getOperationalAttributes()); addOp.setInternalOperation(true); addOp.run(); assertNotNull(getEntry(entry.getDN(), 1000, true)); } /** * Check that the just added entry (newEntry) meets the fractional criteria * regarding the passed configuration : mode and attributes to be filtered/not * filtered */ private void checkEntryFilteredAfterAdd(Entry newEntry, int fractionalMode, String... fractionalConf) throws Exception { try { // Is the added entry of the expected object class ? String objectClassStr = fractionalConf[0]; if (!objectClassStr.equals("*")) { ObjectClass objectClass = DirectoryServer.getObjectClass(objectClassStr.toLowerCase()); assertTrue(newEntry.hasObjectClass(objectClass)); } // Go through each interesting attribute and check it is present or not // according to the fractional mode boolean first = true; switch (fractionalMode) { case EXCLUDE_FRAC_MODE: // Exclude mode: attributes should not be there, but OPTIONAL_ATTR // attribute should for (String fracAttr : fractionalConf) { if (!first) { assertFalse(newEntry.hasAttribute(DirectoryServer. getAttributeType(fracAttr.toLowerCase()))); } first = false; } checkEntryAttributeValue(newEntry, OPTIONAL_ATTR, OPTIONAL_ATTR + "Value"); break; case INCLUDE_FRAC_MODE: // Include mode: attributes should be there, but OPTIONAL_ATTR // attribute should not for (String fracAttr : fractionalConf) { if (!first) { checkEntryAttributeValue(newEntry, fracAttr, fracAttr + "Value"); } first = false; } assertFalse(newEntry.hasAttribute(DirectoryServer. getAttributeType(OPTIONAL_ATTR.toLowerCase()))); break; default: fail("Unexpected fractional mode."); } } catch(Exception e) { fail("checkEntryFilteredAfterAdd error: " + e.getMessage()); } } /** * Check that the just modified entry (entry) meets the fractional criteria * regarding the passed configuration : mode and attributes to be filtered/not * filtered */ private void checkEntryFilteredAfterModify(Entry entry, int fractionalMode, String... fractionalConf) throws Exception { try { // Is the added entry of the expected object class ? String objectClassStr = fractionalConf[0]; if (!objectClassStr.equals("*")) { ObjectClass objectClass = DirectoryServer.getObjectClass(objectClassStr.toLowerCase()); assertTrue(entry.hasObjectClass(objectClass)); } // Go through each interesting attribute and check it has been modifed or // not according to the fractional mode boolean first = true; switch (fractionalMode) { case EXCLUDE_FRAC_MODE: // Exclude mode: attributes should not be there, but OPTIONAL_ATTR // attribute should have been modified for (String fracAttr : fractionalConf) { if (!first) { assertFalse(entry.hasAttribute(DirectoryServer. getAttributeType(fracAttr.toLowerCase()))); } first = false; } checkEntryAttributeValue(entry, OPTIONAL_ATTR, OPTIONAL_ATTR + "NewValue"); break; case INCLUDE_FRAC_MODE: // Include mode: attributes should have been modified, but OPTIONAL_ATTR // attribute should not be there for (String fracAttr : fractionalConf) { if (!first) { checkEntryAttributeValue(entry, fracAttr, fracAttr + "NewValue"); } first = false; } assertFalse(entry.hasAttribute(DirectoryServer. getAttributeType(OPTIONAL_ATTR.toLowerCase()))); break; default: fail("Unexpected fractional mode."); } // In both modes, SYNCHRO_OPTIONAL_ATTR attribute should have been added checkEntryAttributeValue(entry, SYNCHRO_OPTIONAL_ATTR, SYNCHRO_OPTIONAL_ATTR + "Value"); } catch(Exception e) { fail("checkEntryFilteredAfterAdd error: " + e.getMessage()); } } /** * Check that the provided entry has a single value attribute which has the * expected attribute value */ private static void checkEntryAttributeValue(Entry entry, String attributeName, String attributeValue) { List<Attribute> attrs = entry.getAttribute(attributeName.toLowerCase()); assertNotNull(attrs, "Was expecting attribute " + attributeName + "=" + attributeValue + " but got no attribute"); assertEquals(attrs.size(), 1); Attribute attr = attrs.get(0); assertNotNull(attr); Iterator<AttributeValue> attrValues = attr.iterator(); assertNotNull(attrValues); assertTrue(attrValues.hasNext()); AttributeValue attrValue = attrValues.next(); assertNotNull(attrValue); assertFalse(attrValues.hasNext()); assertEquals(attrValue.toString(), attributeValue, "Was expecting attribute " + attributeName + "=" + attributeValue + " but got value: " + attrValue.toString()); } /** * Returns a bunch of single values for fractional configuration * attributes */ @DataProvider(name = "testInitWithFullUpdateExcludePrecommitProvider") private Object[][] testInitWithFullUpdateExcludePrecommitProvider() { return new Object[][] { { 1, true, new String[] {"inetOrgPerson", "displayName"}} }; } /** * Returns a bunch of single values for fractional configuration * attributes */ @DataProvider(name = "testInitWithFullUpdateExcludeNightlyProvider") private Object[][] testInitWithFullUpdateExcludeNightlyProvider() { return new Object[][] { { 1, false, new String[] {"inetOrgPerson", "displayName"}} }; } /** * Calls the testInitWithFullUpdateExclude test with a small set of data, for precommit test * purpose */ @Test(dataProvider = "testInitWithFullUpdateExcludePrecommitProvider") public void testInitWithFullUpdateExcludePrecommit(int testProviderLineId, boolean importedDomainIsFractional, String... fractionalConf) throws Exception { testInitWithFullUpdateExclude(testProviderLineId, importedDomainIsFractional, fractionalConf); } /** * Calls the testInitWithFullUpdateExclude test with a larger set of data, for nightly tests * purpose */ @Test(dataProvider = "testInitWithFullUpdateExcludeNightlyProvider", groups = "slow") public void testInitWithFullUpdateExcludeNightly(int testProviderLineId, boolean importedDomainIsFractional, String... fractionalConf) throws Exception { testInitWithFullUpdateExclude(testProviderLineId, importedDomainIsFractional, fractionalConf); } /** * Configures a domain which is not fractional to fractional exclusive, * then emulates an online full update to initialize the fractional domain and * have it operational. * Note: testProviderLineId just here to know what is the provider problematic * line if the test fail: prevent some display like: * [testng] parameter[0]: [Ljava.lang.String;@151e824 * but have instead: * [testng] parameter[0]: 6 * [testng] parameter[1]: [Ljava.lang.String;@151e824 */ private void testInitWithFullUpdateExclude(int testProviderLineId, boolean importedDomainIsFractional, String... fractionalConf) throws Exception { String testcase = "testInitWithFullUpdateExclude" + testProviderLineId; initTest(); // We need a backend with a real configuration in cn=config as at import time // the real domain will check for backend existence in cn=config. So we use // dc=example,dc=com for this particular test. // Clear the backend LDAPReplicationDomain.clearJEBackend(false, "userRoot", TEST2_ROOT_DN_STRING); try { /* * Create replication server and connect fractional domain to it then fake * domain */ // create replication server createReplicationServer(testcase); // create fractional domain with the passed fractional configuration // without initializing the backend createFractionalDomain(false, EXCLUDE_FRAC_MODE, fractionalConf); // The domain should go in bad gen as backend is not initialized with // fractional data LDAPReplicationDomain fractionalReplicationDomain = MultimasterReplication.findDomain(DN.decode(TEST2_ROOT_DN_STRING), null); waitForDomainStatus(fractionalReplicationDomain, ServerStatus.BAD_GEN_ID_STATUS, 5); // create fake domain to perform the full update long generationId = readGenIdFromSuffixRootEntry(TEST2_ROOT_DN_STRING); assertTrue(generationId != 0L); createFakeReplicationDomain(false, generationId); /* * Create the LDIF that will be used to initialize the domain from the * fake one. Initialize the fake domain with it. */ // Top Entry String exportLdif = "dn: " + TEST2_ROOT_DN_STRING + "\n" + "objectClass: top\n" + "objectClass: domain\n" + "dc: example\n" + "ds-sync-generation-id: " + generationId + "\n"; if (importedDomainIsFractional) { // Add fractional config int i=0; int size = fractionalConf.length; for (String fracCfgValue : fractionalConf) // Add fractional operational attributes { if (i==0) { // First string is the class exportLdif += "ds-sync-fractional-exclude: " + fracCfgValue + ":"; } else { // Other strings are attributes exportLdif += fracCfgValue + ( (i<size-1) ? "," : "\n"); } i++; } } // Org Entry exportLdif += "\ndn: " + TEST2_ORG_DN_STRING + "\n" + "objectClass: top\n" + "objectClass: organization\n" + "o: test2\n\n"; // User entry exportLdif += "dn: " + ENTRY_DN2 + "\n" + "objectClass: top\n" + "objectClass: person\n" + "objectClass: organizationalPerson\n" + "objectClass: inetOrgPerson\n" + "sn: snValue\n" + "cn: cnValue\n" + "uid: 1\n" + "entryUUID: " + ENTRY_UUID + "\n" + OPTIONAL_ATTR + ": " + OPTIONAL_ATTR + "Value\n"; if (!importedDomainIsFractional) { // Add attributes concerned by fractional configuration boolean first = true; for (String fracCfgValue : fractionalConf) { if (!first) { // First string is the class exportLdif += fracCfgValue + ": " + fracCfgValue + "Value\n"; } first = false; } } exportLdif += "\n"; // Needed ? replicationDomain.initExport(exportLdif, 2); // Perform full update from fake domain to fractional domain replicationDomain.initializeRemote(DS1_ID); /* * Check fractional domain is operational and that filtering has been done * during the full update */ // The domain should go back in normal status waitForDomainStatus(fractionalReplicationDomain, ServerStatus.NORMAL_STATUS, 5); // check that entry has been created and that it does not contain // forbidden attributes Entry newEntry = null; try { newEntry = getEntry(DN.decode(ENTRY_DN2), TIMEOUT, true); } catch(Exception e) { fail("Entry has not been created: " + e.getMessage()); } checkEntryFilteredAfterAdd(newEntry, EXCLUDE_FRAC_MODE, fractionalConf); // perform modify operation (modify forbidden attributes + // modify authorized attribute (not a no op)) sendModifyMsg(false, fractionalConf); // Wait for modify operation being replayed and // check that entry does not contain forbidden attributes Entry entry = null; boolean synchroAttrFound = false; int timeout = TIMEOUT; while(timeout>0) { try { entry = getEntry(DN.decode(ENTRY_DN2), TIMEOUT, true); if (entry.hasAttribute(DirectoryServer.getAttributeType(SYNCHRO_OPTIONAL_ATTR.toLowerCase()))) { synchroAttrFound = true; break; } Thread.sleep(1000); timeout--; } catch (Exception e) { fail("Error waiting for modify operation being replayed : " + e.getMessage()); } } assertTrue(synchroAttrFound, "Modify operation not replayed"); checkEntryFilteredAfterModify(entry, EXCLUDE_FRAC_MODE, fractionalConf); } finally { endTest(); } } /** * Wait for the passed domain to have the desired status or fail if timeout * waiting. */ private void waitForDomainStatus(ReplicationDomain replicationDomain, ServerStatus expectedStatus, int nSec) { int toWait = nSec; ServerStatus serverStatus = null; while(nSec > 0) { serverStatus = replicationDomain.getStatus(); if ( serverStatus == expectedStatus ) { debugInfo("waitForDomainStatus: expected replication " + "domain status obtained after " + (toWait-nSec) + " second(s)."); return; } sleep(1000); nSec--; } fail("Did not get expected replication domain status: expected <" + expectedStatus + "> but got <" + serverStatus + ">, after " + toWait + " second(s)"); } /** * Returns a bunch of single values for fractional configuration * attributes */ @DataProvider(name = "testInitWithFullUpdateIncludePrecommitProvider") private Object[][] testInitWithFullUpdateIncludePrecommitProvider() { return new Object[][] { { 1, true, new String[] {"inetOrgPerson", "displayName"}} }; } /** * Returns a bunch of single values for fractional configuration * attributes */ @DataProvider(name = "testInitWithFullUpdateIncludeNightlyProvider") private Object[][] testInitWithFullUpdateIncludeNightlyProvider() { return new Object[][] { { 1, false, new String[] {"inetOrgPerson", "displayName"}} }; } /** * Calls the testInitWithFullUpdateExclude test with a small set of data, for precommit test * purpose */ @Test(dataProvider = "testInitWithFullUpdateIncludePrecommitProvider") public void testInitWithFullUpdateIncludePrecommit(int testProviderLineId, boolean importedDomainIsFractional, String... fractionalConf) throws Exception { testInitWithFullUpdateInclude(testProviderLineId, importedDomainIsFractional, fractionalConf); } /** * Calls the testInitWithFullUpdateExclude test with a larger set of data, for nightly tests * purpose */ @Test(dataProvider = "testInitWithFullUpdateIncludeNightlyProvider", groups = "slow") public void testInitWithFullUpdateIncludeNightly(int testProviderLineId, boolean importedDomainIsFractional, String... fractionalConf) throws Exception { testInitWithFullUpdateInclude(testProviderLineId, importedDomainIsFractional, fractionalConf); } /** * Configures a domain which is not fractional to fractional inclusive, * then emulates an online full update to initialize the fractional domain and * have it operational. * Note: testProviderLineId just here to know what is the provider problematic * line if the test fail: prevent some display like: * [testng] parameter[0]: [Ljava.lang.String;@151e824 * but have instead: * [testng] parameter[0]: 6 * [testng] parameter[1]: [Ljava.lang.String;@151e824 */ private void testInitWithFullUpdateInclude(int testProviderLineId, boolean importedDomainIsFractional, String... fractionalConf) throws Exception { String testcase = "testInitWithFullUpdateInclude" + testProviderLineId; initTest(); // We need a backend with a real configuration in cn=config as at import time // the real domain will check for backend existence in cn=config. So we use // dc=example,dc=com for this particular test. // Clear the backend LDAPReplicationDomain.clearJEBackend(false, "userRoot", TEST2_ROOT_DN_STRING); try { /* * Create replication server and connect fractional domain to it then fake * domain */ // create replication server createReplicationServer(testcase); // create fractional domain with the passed fractional configuration // without initializing the backend createFractionalDomain(false, INCLUDE_FRAC_MODE, fractionalConf); // The domain should go in bad gen as backend is not initialized with // fractional data LDAPReplicationDomain fractionalReplicationDomain = MultimasterReplication.findDomain(DN.decode(TEST2_ROOT_DN_STRING), null); waitForDomainStatus(fractionalReplicationDomain, ServerStatus.BAD_GEN_ID_STATUS, 5); // create fake domain to perform the full update long generationId = readGenIdFromSuffixRootEntry(TEST2_ROOT_DN_STRING); assertTrue(generationId != 0L); createFakeReplicationDomain(false, generationId); /* * Create the LDIF that will be used to initialize the domain from the * fake one. Initialize the fake domain with it. */ // Top Entry String exportLdif = "dn: " + TEST2_ROOT_DN_STRING + "\n" + "objectClass: top\n" + "objectClass: domain\n" + "dc: example\n" + "ds-sync-generation-id: " + generationId + "\n"; if (importedDomainIsFractional) { // Add fractional config int i=0; int size = fractionalConf.length; for (String fracCfgValue : fractionalConf) // Add fractional operational attributes { if (i==0) { // First string is the class exportLdif += "ds-sync-fractional-include: " + fracCfgValue + ":"; } else { // Other strings are attributes exportLdif += fracCfgValue + ( (i<size-1) ? "," : "," + SYNCHRO_OPTIONAL_ATTR + "\n"); } i++; } } // Org Entry exportLdif += "\ndn: " + TEST2_ORG_DN_STRING + "\n" + "objectClass: top\n" + "objectClass: organization\n" + "o: test2\n\n"; // User entry exportLdif += "dn: " + ENTRY_DN2 + "\n" + "objectClass: top\n" + "objectClass: person\n" + "objectClass: organizationalPerson\n" + "objectClass: inetOrgPerson\n" + "sn: snValue\n" + "cn: cnValue\n" + "uid: 1\n" + "entryUUID: " + ENTRY_UUID + "\n" + OPTIONAL_ATTR + ": " + OPTIONAL_ATTR + "Value\n"; // Add attributes concerned by fractional configuration boolean first = true; for (String fracCfgValue : fractionalConf) { if (!first) { // First string is the class exportLdif += fracCfgValue + ": " + fracCfgValue + "Value\n"; } first = false; } exportLdif += "\n"; // Needed ? replicationDomain.initExport(exportLdif, 2); // Perform full update from fake domain to fractional domain replicationDomain.initializeRemote(DS1_ID); /* * Chack fractional domain is operational and that filtering has been done * during the full update */ // The domain should go back in normal status waitForDomainStatus(fractionalReplicationDomain, ServerStatus.NORMAL_STATUS, 5); // check that entry has been created and that it does not contain // forbidden attributes Entry newEntry = null; try { newEntry = getEntry(DN.decode(ENTRY_DN2), TIMEOUT, true); } catch(Exception e) { fail("Entry has not been created: " + e.getMessage()); } checkEntryFilteredAfterAdd(newEntry, INCLUDE_FRAC_MODE, fractionalConf); // perform modify operation (modify forbidden attributes + // modify authorized attribute (not a no op)) sendModifyMsg(false, fractionalConf); // Wait for modify operation being replayed and // check that entry does not contain forbidden attributes Entry entry = null; boolean synchroAttrFound = false; int timeout = TIMEOUT; while(timeout>0) { try { entry = getEntry(DN.decode(ENTRY_DN2), TIMEOUT, true); if (entry.hasAttribute(DirectoryServer.getAttributeType(SYNCHRO_OPTIONAL_ATTR.toLowerCase()))) { synchroAttrFound = true; break; } Thread.sleep(1000); timeout--; } catch (Exception e) { fail("Error waiting for modify operation being replayed : " + e.getMessage()); } } assertTrue(synchroAttrFound, "Modify operation not replayed"); checkEntryFilteredAfterModify(entry, INCLUDE_FRAC_MODE, fractionalConf); } finally { endTest(); } } /** * Tests an add operation on an entry with RDN containing forbidden attribute * by fractional exclude configuration */ @Test public void testAddWithForbiddenAttrInRDNExclude() { String testcase = "testAddWithForbiddenAttrInRDNExclude"; initTest(); try { // create replication server createReplicationServer(testcase); // create fractional domain with the passed fractional configuration createFractionalDomain(true, EXCLUDE_FRAC_MODE, new String[] {"inetOrgPerson", "displayName", "description"}); // create fake domain to send operations createFakeReplicationDomain(true, readGenIdFromSuffixRootEntry(TEST_ROOT_DN_STRING)); /* * Perform add operation with fornbidden attribute in RDN */ String entryLdif = "dn: displayName=ValueToBeKept," + TEST_ROOT_DN_STRING + "\n" + "objectClass: top\n" + "objectClass: person\n" + "objectClass: organizationalPerson\n" + "objectClass: inetOrgPerson\n" + "sn: snValue\n" + "cn: cnValue\n" + "entryUUID: " + ENTRY_UUID + "\n" + "displayName: ValueToBeKept\ndisplayName: displayNameValue\n"; Entry entry = null; try { entry = TestCaseUtils.entryFromLdifString(entryLdif); } catch (Exception e) { fail(e.getMessage()); } // Create an update message to add an entry. AddMsg addMsg = new AddMsg(gen.newChangeNumber(), entry.getDN().toString(), ENTRY_UUID, null, entry.getObjectClassAttribute(), entry.getAttributes(), new ArrayList<Attribute>()); replicationDomain.publish(addMsg); /* * check that entry has been created and has attribute values from RDN * only */ Entry newEntry = null; try { newEntry = getEntry(entry.getDN(), TIMEOUT, true); } catch(Exception e) { fail("Entry has not been added: " + e.getMessage()); } assertNotNull(newEntry); assertEquals(entry.getDN(), newEntry.getDN()); ObjectClass objectClass = DirectoryServer.getObjectClass("inetOrgPerson".toLowerCase()); assertTrue(newEntry.hasObjectClass(objectClass)); checkEntryAttributeValue(newEntry, "displayName", "ValueToBeKept"); /** * Now perform same test, but with 2 forbidden attributes in RDN, using '+' */ /* * Perform add operation with fornbidden attribute in RDN */ entryLdif = "dn: displayName=ValueToBeKept+description=ValueToBeKeptToo," + TEST_ROOT_DN_STRING + "\n" + "objectClass: top\n" + "objectClass: person\n" + "objectClass: organizationalPerson\n" + "objectClass: inetOrgPerson\n" + "entryUUID: " + ENTRY_UUID2 + "\n" + "sn: snValue\n" + "cn: cnValue\n" + "displayName: ValueToBeKept\ndisplayName: displayNameValue\n" + "description: descriptionValue\ndescription: ValueToBeKeptToo\n"; try { entry = TestCaseUtils.entryFromLdifString(entryLdif); } catch (Exception e) { fail(e.getMessage()); } // Create an update message to add an entry. addMsg = new AddMsg(gen.newChangeNumber(), entry.getDN().toString(), ENTRY_UUID2, null, entry.getObjectClassAttribute(), entry.getAttributes(), new ArrayList<Attribute>()); replicationDomain.publish(addMsg); /* * check that entry has been created and has attribute values from RDN * only */ try { newEntry = getEntry(entry.getDN(), TIMEOUT, true); } catch(Exception e) { fail("Entry has not been added: " + e.getMessage()); } assertNotNull(newEntry); assertEquals(entry.getDN(), newEntry.getDN()); objectClass = DirectoryServer.getObjectClass("inetOrgPerson".toLowerCase()); assertTrue(newEntry.hasObjectClass(objectClass)); checkEntryAttributeValue(newEntry, "displayName", "ValueToBeKept"); checkEntryAttributeValue(newEntry, "description", "ValueToBeKeptToo"); } finally { endTest(); } } /** * Tests an add operation on an entry with RDN containing forbidden attribute * by fractional include configuration */ @Test public void testAddWithForbiddenAttrInRDNInclude() { String testcase = "testAddWithForbiddenAttrInRDNInclude"; initTest(); try { // create replication server createReplicationServer(testcase); // create fractional domain with the passed fractional configuration createFractionalDomain(true, INCLUDE_FRAC_MODE, new String[] {"inetOrgPerson", "carLicense"}); // create fake domain to send operations createFakeReplicationDomain(true, readGenIdFromSuffixRootEntry(TEST_ROOT_DN_STRING)); /* * Perform add operation with fornbidden attribute in RDN */ String entryLdif = "dn: displayName=ValueToBeKept," + TEST_ROOT_DN_STRING + "\n" + "objectClass: top\n" + "objectClass: person\n" + "objectClass: organizationalPerson\n" + "objectClass: inetOrgPerson\n" + "sn: snValue\n" + "cn: cnValue\n" + "entryUUID: " + ENTRY_UUID + "\n" + "displayName: ValueToBeKept\ndisplayName: displayNameValue\n" + "carLicense: cirLicenseValue\n"; Entry entry = null; try { entry = TestCaseUtils.entryFromLdifString(entryLdif); } catch (Exception e) { fail(e.getMessage()); } // Create an update message to add an entry. AddMsg addMsg = new AddMsg(gen.newChangeNumber(), entry.getDN().toString(), ENTRY_UUID, null, entry.getObjectClassAttribute(), entry.getAttributes(), new ArrayList<Attribute>()); replicationDomain.publish(addMsg); /* * check that entry has been created and has attribute values from RDN * only */ Entry newEntry = null; try { newEntry = getEntry(entry.getDN(), TIMEOUT, true); } catch(Exception e) { fail("Entry has not been added: " + e.getMessage()); } assertNotNull(newEntry); assertEquals(entry.getDN(), newEntry.getDN()); ObjectClass objectClass = DirectoryServer.getObjectClass("inetOrgPerson".toLowerCase()); assertTrue(newEntry.hasObjectClass(objectClass)); checkEntryAttributeValue(newEntry, "displayName", "ValueToBeKept"); checkEntryAttributeValue(newEntry, "carLicense", "cirLicenseValue"); /** * Now perform same test, but with 2 forbidden attributes in RDN, using '+' */ /* * Perform add operation with fornbidden attribute in RDN */ entryLdif = "dn: displayName=ValueToBeKept+description=ValueToBeKeptToo," + TEST_ROOT_DN_STRING + "\n" + "objectClass: top\n" + "objectClass: person\n" + "objectClass: organizationalPerson\n" + "objectClass: inetOrgPerson\n" + "sn: snValue\n" + "cn: cnValue\n" + "entryUUID: " + ENTRY_UUID2 + "\n" + "displayName: ValueToBeKept\ndisplayName: displayNameValue\n" + "description: descriptionValue\ndescription: ValueToBeKeptToo\n" + "carLicense: cirLicenseValue\n"; try { entry = TestCaseUtils.entryFromLdifString(entryLdif); } catch (Exception e) { fail(e.getMessage()); } // Create an update message to add an entry. addMsg = new AddMsg(gen.newChangeNumber(), entry.getDN().toString(), ENTRY_UUID2, null, entry.getObjectClassAttribute(), entry.getAttributes(), new ArrayList<Attribute>()); replicationDomain.publish(addMsg); /* * check that entry has been created and has attribute values from RDN * only */ try { newEntry = getEntry(entry.getDN(), TIMEOUT, true); } catch(Exception e) { fail("Entry has not been added: " + e.getMessage()); } assertNotNull(newEntry); assertEquals(entry.getDN(), newEntry.getDN()); objectClass = DirectoryServer.getObjectClass("inetOrgPerson".toLowerCase()); assertTrue(newEntry.hasObjectClass(objectClass)); checkEntryAttributeValue(newEntry, "displayName", "ValueToBeKept"); checkEntryAttributeValue(newEntry, "description", "ValueToBeKeptToo"); checkEntryAttributeValue(newEntry, "carLicense", "cirLicenseValue"); } finally { endTest(); } } /** * Tests modify dn operation on an entry with old RDN containing forbidden * attribute by fractional exclude configuration */ @Test public void testModifyDnWithForbiddenAttrInRDNExclude() { String testcase = "testModifyDnWithForbiddenAttrInRDNExclude"; initTest(); try { // create replication server createReplicationServer(testcase); // create fractional domain with the passed fractional configuration createFractionalDomain(true, EXCLUDE_FRAC_MODE, new String[] {"inetOrgPerson", "displayName", "description"}); // create fake domain to send operations createFakeReplicationDomain(true, readGenIdFromSuffixRootEntry(TEST_ROOT_DN_STRING)); /* * Perform add operation with fornbidden attribute in RDN */ String entryName = "displayName=ValueToBeKept+description=ValueToBeRemoved," + TEST_ROOT_DN_STRING ; String entryLdif = "dn: " + entryName + "\n" + "objectClass: top\n" + "objectClass: person\n" + "objectClass: organizationalPerson\n" + "objectClass: inetOrgPerson\n" + "sn: snValue\n" + "cn: cnValue\n" + "entryUUID: " + ENTRY_UUID + "\n" + "displayName: ValueToBeKept\ndescription: ValueToBeRemoved\n"; Entry entry = null; try { entry = TestCaseUtils.entryFromLdifString(entryLdif); } catch (Exception e) { fail(e.getMessage()); } // Create an update message to add an entry. AddMsg addMsg = new AddMsg(gen.newChangeNumber(), entry.getDN().toString(), ENTRY_UUID, null, entry.getObjectClassAttribute(), entry.getAttributes(), new ArrayList<Attribute>()); replicationDomain.publish(addMsg); /* * check that entry has been created and has attribute values from RDN */ Entry newEntry = null; try { newEntry = getEntry(entry.getDN(), TIMEOUT, true); } catch(Exception e) { fail("Entry has not been added: " + e.getMessage()); } assertNotNull(newEntry); assertEquals(entry.getDN(), newEntry.getDN()); ObjectClass objectClass = DirectoryServer.getObjectClass("inetOrgPerson".toLowerCase()); assertTrue(newEntry.hasObjectClass(objectClass)); checkEntryAttributeValue(newEntry, "displayName", "ValueToBeKept"); checkEntryAttributeValue(newEntry, "description", "ValueToBeRemoved"); /* * Perform modify dn operation by renaming the entry keeping only one of * the forbidden attributes */ String newEntryName = "displayName=ValueToBeKept," + TEST_ROOT_DN_STRING ; DN newEntryDn = null; try { newEntryDn = DN.decode(newEntryName); } catch(DirectoryException e) { fail("Could not get DN from string: " + newEntryName); } // Create modify dn message to modify the entry. ModifyDNMsg modDnMsg = new ModifyDNMsg(entryName, gen.newChangeNumber(), ENTRY_UUID, ENTRY_UUID3, false, TEST_ROOT_DN_STRING, "displayName=ValueToBeKept", null); replicationDomain.publish(modDnMsg); /* * check that entry has been renamed and has only attribute left in the * new RDN */ try { newEntry = getEntry(newEntryDn, TIMEOUT, true); } catch(Exception e) { fail("Entry has not been added: " + e.getMessage()); } assertNotNull(newEntry); assertEquals(newEntryDn, newEntry.getDN()); objectClass = DirectoryServer.getObjectClass("inetOrgPerson".toLowerCase()); assertTrue(newEntry.hasObjectClass(objectClass)); checkEntryAttributeValue(newEntry, "displayName", "ValueToBeKept"); assertNull(newEntry.getAttribute("description")); } finally { endTest(); } } /** * Tests modify dn operation on an entry with old RDN containing forbidden * attribute by fractional include configuration */ @Test public void testModifyDnWithForbiddenAttrInRDNInclude() { String testcase = "testModifyDnWithForbiddenAttrInRDNInclude"; initTest(); try { // create replication server createReplicationServer(testcase); // create fractional domain with the passed fractional configuration createFractionalDomain(true, INCLUDE_FRAC_MODE, new String[] {"inetOrgPerson", "carLicense"}); // create fake domain to send operations createFakeReplicationDomain(true, readGenIdFromSuffixRootEntry(TEST_ROOT_DN_STRING)); /* * Perform add operation with fornbidden attribute in RDN */ String entryName = "displayName=ValueToBeKept+description=ValueToBeRemoved," + TEST_ROOT_DN_STRING ; String entryLdif = "dn: " + entryName + "\n" + "objectClass: top\n" + "objectClass: person\n" + "objectClass: organizationalPerson\n" + "objectClass: inetOrgPerson\n" + "sn: snValue\n" + "cn: cnValue\n" + "entryUUID: " + ENTRY_UUID + "\n" + "displayName: ValueToBeKept\ndescription: ValueToBeRemoved\n"; Entry entry = null; try { entry = TestCaseUtils.entryFromLdifString(entryLdif); } catch (Exception e) { fail(e.getMessage()); } // Create an update message to add an entry. AddMsg addMsg = new AddMsg(gen.newChangeNumber(), entry.getDN().toString(), ENTRY_UUID, null, entry.getObjectClassAttribute(), entry.getAttributes(), new ArrayList<Attribute>()); replicationDomain.publish(addMsg); /* * check that entry has been created and has attribute values from RDN */ Entry newEntry = null; try { newEntry = getEntry(entry.getDN(), TIMEOUT, true); } catch(Exception e) { fail("Entry has not been added: " + e.getMessage()); } assertNotNull(newEntry); assertEquals(entry.getDN(), newEntry.getDN()); ObjectClass objectClass = DirectoryServer.getObjectClass("inetOrgPerson".toLowerCase()); assertTrue(newEntry.hasObjectClass(objectClass)); checkEntryAttributeValue(newEntry, "displayName", "ValueToBeKept"); checkEntryAttributeValue(newEntry, "description", "ValueToBeRemoved"); /* * Perform modify dn operation by renaming the entry keeping only one of * the forbidden attributes */ String newEntryName = "displayName=ValueToBeKept," + TEST_ROOT_DN_STRING ; DN newEntryDn = null; try { newEntryDn = DN.decode(newEntryName); } catch(DirectoryException e) { fail("Could not get DN from string: " + newEntryName); } // Create modify dn message to modify the entry. ModifyDNMsg modDnMsg = new ModifyDNMsg(entryName, gen.newChangeNumber(), ENTRY_UUID, ENTRY_UUID3, false, TEST_ROOT_DN_STRING, "displayName=ValueToBeKept", null); replicationDomain.publish(modDnMsg); /* * check that entry has been renamed and has only attribute left in the * new RDN */ try { newEntry = getEntry(newEntryDn, TIMEOUT, true); } catch(Exception e) { fail("Entry has not been added: " + e.getMessage()); } assertNotNull(newEntry); assertEquals(newEntryDn, newEntry.getDN()); objectClass = DirectoryServer.getObjectClass("inetOrgPerson".toLowerCase()); assertTrue(newEntry.hasObjectClass(objectClass)); checkEntryAttributeValue(newEntry, "displayName", "ValueToBeKept"); assertNull(newEntry.getAttribute("description")); } finally { endTest(); } } }