001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2012-2015 ForgeRock AS.
026 */
027package org.opends.server.tasks;
028
029import static org.opends.messages.TaskMessages.*;
030import static org.opends.messages.ToolMessages.*;
031import static org.opends.server.config.ConfigConstants.*;
032import static org.opends.server.core.DirectoryServer.*;
033import static org.opends.server.util.StaticUtils.*;
034
035import java.util.ArrayList;
036import java.util.List;
037
038import org.forgerock.i18n.LocalizableMessage;
039import org.forgerock.i18n.slf4j.LocalizedLogger;
040import org.forgerock.opendj.ldap.ResultCode;
041import org.opends.messages.TaskMessages;
042import org.opends.server.api.Backend;
043import org.opends.server.api.Backend.BackendOperation;
044import org.opends.server.api.ClientConnection;
045import org.opends.server.backends.RebuildConfig;
046import org.opends.server.backends.RebuildConfig.RebuildMode;
047import org.opends.server.backends.task.Task;
048import org.opends.server.backends.task.TaskState;
049import org.opends.server.core.DirectoryServer;
050import org.opends.server.core.LockFileManager;
051import org.opends.server.types.Attribute;
052import org.opends.server.types.AttributeType;
053import org.opends.server.types.DN;
054import org.opends.server.types.DirectoryException;
055import org.opends.server.types.Entry;
056import org.opends.server.types.InitializationException;
057import org.opends.server.types.Operation;
058import org.opends.server.types.Privilege;
059
060/**
061 * This class provides an implementation of a Directory Server task that can be
062 * used to rebuild indexes in a backend.
063 */
064public class RebuildTask extends Task
065{
066  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
067
068  private String baseDN;
069  private ArrayList<String> indexes;
070  private String tmpDirectory;
071  private RebuildMode rebuildMode = RebuildMode.USER_DEFINED;
072  private boolean isClearDegradedState;
073
074  /** {@inheritDoc} */
075  @Override
076  public LocalizableMessage getDisplayName()
077  {
078    return TaskMessages.INFO_TASK_REBUILD_NAME.get();
079  }
080
081  /** {@inheritDoc} */
082  @Override
083  public void initializeTask() throws DirectoryException
084  {
085    // If the client connection is available, then make sure the associated
086    // client has the INDEX_REBUILD privilege.
087
088    Operation operation = getOperation();
089    if (operation != null)
090    {
091      ClientConnection clientConnection = operation.getClientConnection();
092      if (!clientConnection.hasPrivilege(Privilege.LDIF_IMPORT, operation))
093      {
094        LocalizableMessage message = ERR_TASK_INDEXREBUILD_INSUFFICIENT_PRIVILEGES.get();
095        throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
096            message);
097      }
098    }
099
100    Entry taskEntry = getTaskEntry();
101
102    baseDN = asString(taskEntry, ATTR_REBUILD_BASE_DN);
103    tmpDirectory = asString(taskEntry, ATTR_REBUILD_TMP_DIRECTORY);
104    final String val = asString(taskEntry, ATTR_REBUILD_INDEX_CLEARDEGRADEDSTATE);
105    isClearDegradedState = Boolean.parseBoolean(val);
106
107    AttributeType typeIndex = getAttributeTypeOrDefault(ATTR_REBUILD_INDEX);
108    List<Attribute> attrList = taskEntry.getAttribute(typeIndex);
109    indexes = TaskUtils.getMultiValueString(attrList);
110
111    rebuildMode = getRebuildMode(indexes);
112    if (rebuildMode != RebuildMode.USER_DEFINED)
113    {
114      if (indexes.size() != 1)
115      {
116        LocalizableMessage msg = ERR_TASK_INDEXREBUILD_ALL_ERROR.get();
117        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, msg);
118      }
119      indexes.clear();
120    }
121  }
122
123  private String asString(Entry taskEntry, String attrName)
124  {
125    final AttributeType attrType = getAttributeTypeOrDefault(attrName);
126    final List<Attribute> attrList = taskEntry.getAttribute(attrType);
127    return TaskUtils.getSingleValueString(attrList);
128  }
129
130  private RebuildMode getRebuildMode(List<String> indexList)
131  {
132    for (String s : indexList)
133    {
134      if (REBUILD_ALL.equalsIgnoreCase(s))
135      {
136        return RebuildMode.ALL;
137      }
138      else if (REBUILD_DEGRADED.equalsIgnoreCase(s))
139      {
140        return RebuildMode.DEGRADED;
141      }
142    }
143    return RebuildMode.USER_DEFINED;
144  }
145
146  /** {@inheritDoc} */
147  @Override
148  protected TaskState runTask()
149  {
150    RebuildConfig rebuildConfig = new RebuildConfig();
151
152    try
153    {
154      rebuildConfig.setBaseDN(DN.valueOf(baseDN));
155    }
156    catch (DirectoryException de)
157    {
158      logger.error(ERR_CANNOT_DECODE_BASE_DN, baseDN, de.getMessageObject());
159      return TaskState.STOPPED_BY_ERROR;
160    }
161
162    for (final String index : indexes)
163    {
164      rebuildConfig.addRebuildIndex(index);
165    }
166
167    // The degraded state is set(if present in args)
168    // during the initialization.
169    rebuildConfig.isClearDegradedState(isClearDegradedState);
170    boolean isBackendNeedToBeEnabled = false;
171
172    if (tmpDirectory == null)
173    {
174      tmpDirectory = "import-tmp";
175    }
176    rebuildConfig.setTmpDirectory(tmpDirectory);
177    rebuildConfig.setRebuildMode(rebuildMode);
178
179    final Backend<?> backend = DirectoryServer.getBackendWithBaseDN(rebuildConfig.getBaseDN());
180    if (backend == null)
181    {
182      logger.error(ERR_NO_BACKENDS_FOR_BASE, baseDN);
183      return TaskState.STOPPED_BY_ERROR;
184    }
185    if (!backend.supports(BackendOperation.INDEXING))
186    {
187      logger.error(ERR_REBUILDINDEX_WRONG_BACKEND_TYPE);
188      return TaskState.STOPPED_BY_ERROR;
189    }
190
191    // If we are rebuilding one or more system indexes, we have
192    // to acquire exclusive lock. Shared lock in 'cleardegradedstate' mode.
193    String lockFile = LockFileManager.getBackendLockFileName(backend);
194    StringBuilder failureReason = new StringBuilder();
195
196    // Disable the backend
197    // Except in 'cleardegradedstate' mode we don't need to disable it.
198    if (!isClearDegradedState)
199    {
200      try
201      {
202        TaskUtils.disableBackend(backend.getBackendID());
203      }
204      catch (DirectoryException e)
205      {
206        logger.traceException(e);
207
208        logger.error(e.getMessageObject());
209        return TaskState.STOPPED_BY_ERROR;
210      }
211
212      try
213      {
214        if (!LockFileManager.acquireExclusiveLock(lockFile, failureReason))
215        {
216          logger.error(ERR_REBUILDINDEX_CANNOT_EXCLUSIVE_LOCK_BACKEND, backend.getBackendID(), failureReason);
217          return TaskState.STOPPED_BY_ERROR;
218        }
219      }
220      catch (Exception e)
221      {
222        logger.error(ERR_REBUILDINDEX_CANNOT_EXCLUSIVE_LOCK_BACKEND, backend
223                .getBackendID(), getExceptionMessage(e));
224        return TaskState.STOPPED_BY_ERROR;
225      }
226    }
227    else
228    {
229      // We just need a shared lock on the backend for this part.
230      try
231      {
232        if (!LockFileManager.acquireSharedLock(lockFile, failureReason))
233        {
234          logger.error(ERR_REBUILDINDEX_CANNOT_SHARED_LOCK_BACKEND, backend.getBackendID(), failureReason);
235          return TaskState.STOPPED_BY_ERROR;
236        }
237      }
238      catch (Exception e)
239      {
240        logger.error(ERR_REBUILDINDEX_CANNOT_SHARED_LOCK_BACKEND, backend
241                .getBackendID(), getExceptionMessage(e));
242        return TaskState.STOPPED_BY_ERROR;
243      }
244    }
245
246    TaskState returnCode = TaskState.COMPLETED_SUCCESSFULLY;
247
248    // Launch the rebuild process.
249    try
250    {
251      backend.rebuildBackend(rebuildConfig, DirectoryServer.getInstance().getServerContext());
252    }
253    catch (InitializationException e)
254    {
255      // This exception catches all 'index not found'
256      // The backend needs to be re-enabled at the end of the process.
257      LocalizableMessage message =
258          ERR_REBUILDINDEX_ERROR_DURING_REBUILD.get(e.getMessage());
259      logger.traceException(e);
260      logger.error(message);
261      isBackendNeedToBeEnabled = true;
262      returnCode = TaskState.STOPPED_BY_ERROR;
263    }
264    catch (Exception e)
265    {
266      logger.traceException(e);
267
268      logger.error(ERR_REBUILDINDEX_ERROR_DURING_REBUILD, e.getMessage());
269      returnCode = TaskState.STOPPED_BY_ERROR;
270    }
271    finally
272    {
273      // Release the lock on the backend.
274      try
275      {
276        lockFile = LockFileManager.getBackendLockFileName(backend);
277        failureReason = new StringBuilder();
278        if (!LockFileManager.releaseLock(lockFile, failureReason))
279        {
280          logger.warn(WARN_REBUILDINDEX_CANNOT_UNLOCK_BACKEND, backend.getBackendID(), failureReason);
281          returnCode = TaskState.COMPLETED_WITH_ERRORS;
282        }
283      }
284      catch (Throwable t)
285      {
286        logger.warn(WARN_REBUILDINDEX_CANNOT_UNLOCK_BACKEND, backend.getBackendID(),
287                getExceptionMessage(t));
288        returnCode = TaskState.COMPLETED_WITH_ERRORS;
289      }
290    }
291
292    // The backend must be enabled only if the task is successful
293    // for prevent potential risks of database corruption.
294    if ((returnCode == TaskState.COMPLETED_SUCCESSFULLY || isBackendNeedToBeEnabled)
295        && !isClearDegradedState)
296    {
297      // Enable the backend.
298      try
299      {
300        TaskUtils.enableBackend(backend.getBackendID());
301      }
302      catch (DirectoryException e)
303      {
304        logger.traceException(e);
305
306        logger.error(e.getMessageObject());
307        returnCode = TaskState.STOPPED_BY_ERROR;
308      }
309    }
310
311    return returnCode;
312  }
313}