| | |
| | | * |
| | | * |
| | | * Copyright 2006-2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2012-2013 ForgeRock AS. |
| | | * Portions copyright 2012-2014 ForgeRock AS. |
| | | */ |
| | | package org.opends.server.replication.plugin; |
| | | |
| | |
| | | import java.util.LinkedList; |
| | | import java.util.List; |
| | | |
| | | import org.opends.messages.Message; |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.core.ModifyOperationBasis; |
| | | import org.opends.server.protocols.internal.InternalClientConnection; |
| | | import org.opends.server.protocols.internal.InternalSearchOperation; |
| | | import org.opends.server.protocols.ldap.LDAPAttribute; |
| | | import org.opends.server.protocols.ldap.LDAPModification; |
| | |
| | | |
| | | import static org.opends.messages.ReplicationMessages.*; |
| | | import static org.opends.server.loggers.ErrorLogger.*; |
| | | import static org.opends.server.protocols.internal.InternalClientConnection.*; |
| | | |
| | | /** |
| | | * This class implements a ServerState that is stored in the backend |
| | | * used to store the synchronized data and that is therefore persistent |
| | | * across server reboot. |
| | | */ |
| | | public class PersistentServerState |
| | | class PersistentServerState |
| | | { |
| | | private final DN baseDn; |
| | | private final InternalClientConnection conn = |
| | | InternalClientConnection.getRootConnection(); |
| | | private final int serverId; |
| | | |
| | | private final ServerState state; |
| | | |
| | | /** |
| | | * The attribute name used to store the state in the backend. |
| | | */ |
| | | protected static final String REPLICATION_STATE = "ds-sync-state"; |
| | | |
| | | /** |
| | | * create a new ServerState. |
| | | * @param baseDn The baseDN for which the ServerState is created |
| | | * @param serverId The serverId |
| | | */ |
| | | public PersistentServerState(DN baseDn, int serverId) |
| | | { |
| | | this.baseDn = baseDn; |
| | | this.serverId = serverId; |
| | | this.state = new ServerState(); |
| | | loadState(); |
| | | } |
| | | private static final String REPLICATION_STATE = "ds-sync-state"; |
| | | |
| | | /** |
| | | * Create a new PersistentServerState based on an already existing |
| | |
| | | * @param serverId The serverId. |
| | | * @param state The serverState. |
| | | */ |
| | | public PersistentServerState(DN baseDn, int serverId, ServerState state) |
| | | PersistentServerState(DN baseDn, int serverId, ServerState state) |
| | | { |
| | | this.baseDn = baseDn; |
| | | this.serverId = serverId; |
| | |
| | | * @return A boolean indicating if this ServerState contains the CSN |
| | | * given in parameter. |
| | | */ |
| | | public boolean cover(CSN covered) |
| | | boolean cover(CSN covered) |
| | | { |
| | | return state.cover(covered); |
| | | } |
| | |
| | | * The committed CSN. |
| | | * @return a boolean indicating if the update was meaningful. |
| | | */ |
| | | public boolean update(CSN csn) |
| | | boolean update(CSN csn) |
| | | { |
| | | return state.update(csn); |
| | | } |
| | |
| | | */ |
| | | public void save() |
| | | { |
| | | if (state.isSaved()) |
| | | return; |
| | | |
| | | state.setSaved(true); |
| | | ResultCode resultCode = updateStateEntry(); |
| | | if (resultCode != ResultCode.SUCCESS) |
| | | if (!state.isSaved()) |
| | | { |
| | | state.setSaved(false); |
| | | state.setSaved(updateStateEntry() == ResultCode.SUCCESS); |
| | | } |
| | | } |
| | | |
| | |
| | | */ |
| | | LinkedHashSet<String> attributes = new LinkedHashSet<String>(1); |
| | | attributes.add(REPLICATION_STATE); |
| | | InternalSearchOperation search = conn.processSearch(baseDn, |
| | | SearchScope.BASE_OBJECT, |
| | | DereferencePolicy.NEVER_DEREF_ALIASES, |
| | | final InternalSearchOperation search = getRootConnection().processSearch( |
| | | baseDn, |
| | | SearchScope.BASE_OBJECT, DereferencePolicy.NEVER_DEREF_ALIASES, |
| | | 0, 0, false, filter, attributes); |
| | | if (((search.getResultCode() != ResultCode.SUCCESS)) && |
| | | ((search.getResultCode() != ResultCode.NO_SUCH_OBJECT))) |
| | | { |
| | | Message message = ERR_ERROR_SEARCHING_RUV. |
| | | get(search.getResultCode().getResultCodeName(), search.toString(), |
| | | search.getErrorMessage(), baseDn.toString()); |
| | | logError(message); |
| | | logError(ERR_ERROR_SEARCHING_RUV.get( |
| | | search.getResultCode().getResultCodeName(), search.toString(), |
| | | search.getErrorMessage(), baseDn.toString())); |
| | | return null; |
| | | } |
| | | |
| | | SearchResultEntry stateEntry = null; |
| | | if (search.getResultCode() == ResultCode.SUCCESS) |
| | | { |
| | | // Read the serverState from the REPLICATION_STATE attribute |
| | | LinkedList<SearchResultEntry> result = search.getSearchEntries(); |
| | | if (!result.isEmpty()) |
| | | { |
| | | stateEntry = result.getFirst(); |
| | | } |
| | | } |
| | | return stateEntry; |
| | | return getFirstResult(search); |
| | | } |
| | | catch (DirectoryException e) |
| | | { |
| | |
| | | { |
| | | try |
| | | { |
| | | SearchFilter filter = |
| | | SearchFilter.createFilterFromString( |
| | | SearchFilter filter = SearchFilter.createFilterFromString( |
| | | "(&(objectclass=ds-cfg-replication-domain)" |
| | | +"(ds-cfg-base-dn="+baseDn+"))"); |
| | | |
| | | LinkedHashSet<String> attributes = new LinkedHashSet<String>(1); |
| | | attributes.add(REPLICATION_STATE); |
| | | InternalSearchOperation op = |
| | | conn.processSearch(DN.decode("cn=config"), |
| | | final InternalSearchOperation op = getRootConnection().processSearch( |
| | | DN.decode("cn=config"), |
| | | SearchScope.SUBORDINATE_SUBTREE, |
| | | DereferencePolicy.NEVER_DEREF_ALIASES, |
| | | 1, 0, false, filter, attributes); |
| | | |
| | | if (op.getResultCode() == ResultCode.SUCCESS) |
| | | { |
| | | // Read the serverState from the REPLICATION_STATE attribute |
| | | LinkedList<SearchResultEntry> resultEntries = |
| | | op.getSearchEntries(); |
| | | if (!resultEntries.isEmpty()) |
| | | { |
| | | return resultEntries.getFirst(); |
| | | return getFirstResult(op); |
| | | } |
| | | } |
| | | return null; |
| | | } catch (DirectoryException e) |
| | | catch (DirectoryException e) |
| | | { |
| | | // can not happen |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private SearchResultEntry getFirstResult(InternalSearchOperation search) |
| | | { |
| | | if (search.getResultCode() == ResultCode.SUCCESS) |
| | | { |
| | | final LinkedList<SearchResultEntry> results = search.getSearchEntries(); |
| | | if (!results.isEmpty()) |
| | | { |
| | | return results.getFirst(); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * Update this ServerState from the provided entry. |
| | | * |
| | |
| | | */ |
| | | private ResultCode updateStateEntry() |
| | | { |
| | | /* |
| | | * Generate a modify operation on the Server State baseD Entry. |
| | | */ |
| | | // Generate a modify operation on the Server State baseDN Entry. |
| | | ResultCode result = runUpdateStateEntry(baseDn); |
| | | |
| | | if (result == ResultCode.NO_SUCH_OBJECT) |
| | | { |
| | | // The base entry does not exist yet in the database or |
| | | // has been deleted, save the state to the config entry instead. |
| | | // The base entry does not exist yet in the database or has been deleted, |
| | | // save the state to the config entry instead. |
| | | SearchResultEntry configEntry = searchConfigEntry(); |
| | | if (configEntry != null) |
| | | { |
| | |
| | | { |
| | | ArrayList<ByteString> values = state.toASN1ArrayList(); |
| | | |
| | | LDAPAttribute attr = |
| | | new LDAPAttribute(REPLICATION_STATE, values); |
| | | LDAPAttribute attr = new LDAPAttribute(REPLICATION_STATE, values); |
| | | LDAPModification mod = new LDAPModification(ModificationType.REPLACE, attr); |
| | | ArrayList<RawModification> mods = new ArrayList<RawModification>(1); |
| | | mods.add(mod); |
| | | |
| | | ModifyOperationBasis op = |
| | | new ModifyOperationBasis(conn, InternalClientConnection.nextOperationID(), |
| | | InternalClientConnection.nextMessageID(), |
| | | new ArrayList<Control>(0), |
| | | ModifyOperationBasis op = new ModifyOperationBasis(getRootConnection(), |
| | | nextOperationID(), nextMessageID(), null, |
| | | ByteString.valueOf(serverStateEntryDN.toString()), |
| | | mods); |
| | | op.setInternalOperation(true); |
| | |
| | | op.run(); |
| | | if (op.getResultCode() != ResultCode.SUCCESS) |
| | | { |
| | | Message message = DEBUG_ERROR_UPDATING_RUV.get( |
| | | logError(DEBUG_ERROR_UPDATING_RUV.get( |
| | | op.getResultCode().getResultCodeName().toString(), |
| | | op.toString(), |
| | | op.getErrorMessage().toString(), |
| | | baseDn.toString()); |
| | | logError(message); |
| | | baseDn.toString())); |
| | | } |
| | | return op.getResultCode(); |
| | | } |
| | |
| | | * After this call the Server State will be in the same state |
| | | * as if it was just created. |
| | | */ |
| | | public void clear() |
| | | void clear() |
| | | { |
| | | clearInMemory(); |
| | | save(); |
| | |
| | | * This is done by using the HistoricalCsnOrderingMatchingRule |
| | | * and an ordering index for historical attribute |
| | | */ |
| | | public final void checkAndUpdateServerState() { |
| | | Message message; |
| | | InternalSearchOperation op; |
| | | CSN serverStateMaxCsn; |
| | | CSN dbMaxCsn; |
| | | private final void checkAndUpdateServerState() |
| | | { |
| | | final AttributeType histType = DirectoryServer.getAttributeType( |
| | | EntryHistorical.HISTORICAL_ATTRIBUTE_NAME); |
| | | |
| | |
| | | // maxCsn stored in the serverState |
| | | synchronized (this) |
| | | { |
| | | serverStateMaxCsn = state.getCSN(serverId); |
| | | |
| | | if (serverStateMaxCsn == null) |
| | | CSN serverStateMaxCSN = state.getCSN(serverId); |
| | | if (serverStateMaxCSN == null) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | InternalSearchOperation op; |
| | | try |
| | | { |
| | | op = LDAPReplicationDomain.searchForChangedEntries(baseDn, |
| | | serverStateMaxCsn, null); |
| | | serverStateMaxCSN, null); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | if (op.getResultCode() != ResultCode.SUCCESS) |
| | | { |
| | | // An error happened trying to search for the updates |
| | | // Log an error |
| | | message = ERR_CANNOT_RECOVER_CHANGES.get( |
| | | baseDn.toNormalizedString()); |
| | | logError(message); |
| | | logError(ERR_CANNOT_RECOVER_CHANGES.get(baseDn.toNormalizedString())); |
| | | return; |
| | | } |
| | | else |
| | | { |
| | | dbMaxCsn = serverStateMaxCsn; |
| | | |
| | | CSN dbMaxCSN = serverStateMaxCSN; |
| | | for (SearchResultEntry resEntry : op.getSearchEntries()) |
| | | { |
| | | for (AttributeValue attrValue : |
| | | resEntry.getAttribute(histType).get(0)) |
| | | final Attribute historyAttr = resEntry.getAttribute(histType).get(0); |
| | | for (AttributeValue attrValue : historyAttr) |
| | | { |
| | | HistoricalAttributeValue histVal = |
| | | new HistoricalAttributeValue(attrValue.toString()); |
| | | CSN csn = histVal.getCSN(); |
| | | if (csn != null && csn.getServerId() == serverId) |
| | | if (csn != null |
| | | && csn.getServerId() == serverId |
| | | && dbMaxCSN.isOlderThan(csn)) |
| | | { |
| | | // compare the csn regarding the maxCsn we know and |
| | | // store the biggest |
| | | if (CSN.compare(dbMaxCsn, csn) < 0) |
| | | { |
| | | dbMaxCsn = csn; |
| | | } |
| | | dbMaxCSN = csn; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (CSN.compare(dbMaxCsn, serverStateMaxCsn) > 0) |
| | | if (dbMaxCSN.isNewerThan(serverStateMaxCSN)) |
| | | { |
| | | // Update the serverState with the new maxCsn |
| | | // present in the database |
| | | this.update(dbMaxCsn); |
| | | message = NOTE_SERVER_STATE_RECOVERY.get( |
| | | baseDn.toNormalizedString(), dbMaxCsn.toString()); |
| | | logError(message); |
| | | } |
| | | // Update the serverState with the new maxCSN present in the database |
| | | update(dbMaxCSN); |
| | | logError(NOTE_SERVER_STATE_RECOVERY.get(baseDn.toNormalizedString(), |
| | | dbMaxCSN.toString())); |
| | | } |
| | | } |
| | | } |