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-2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2014-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.io.File;
036import java.util.HashMap;
037import java.util.List;
038import java.util.Map;
039
040import org.forgerock.i18n.LocalizableMessage;
041import org.forgerock.i18n.slf4j.LocalizedLogger;
042import org.forgerock.opendj.config.server.ConfigException;
043import org.forgerock.opendj.ldap.ResultCode;
044import org.opends.messages.Severity;
045import org.opends.messages.TaskMessages;
046import org.opends.server.api.Backend;
047import org.opends.server.api.Backend.BackendOperation;
048import org.opends.server.api.ClientConnection;
049import org.opends.server.backends.task.Task;
050import org.opends.server.backends.task.TaskState;
051import org.opends.server.config.ConfigEntry;
052import org.opends.server.core.DirectoryServer;
053import org.opends.server.core.LockFileManager;
054import org.opends.server.types.Attribute;
055import org.opends.server.types.AttributeType;
056import org.opends.server.types.BackupDirectory;
057import org.opends.server.types.BackupInfo;
058import org.opends.server.types.DN;
059import org.opends.server.types.DirectoryException;
060import org.opends.server.types.Entry;
061import org.opends.server.types.Operation;
062import org.opends.server.types.Privilege;
063import org.opends.server.types.RestoreConfig;
064
065/**
066 * This class provides an implementation of a Directory Server task that can
067 * be used to restore a binary backup of a Directory Server backend.
068 */
069public class RestoreTask extends Task
070{
071  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
072
073
074  /** Stores mapping between configuration attribute name and its label. */
075  private static Map<String,LocalizableMessage> argDisplayMap = new HashMap<>();
076  static {
077    argDisplayMap.put(ATTR_BACKUP_DIRECTORY_PATH, INFO_RESTORE_ARG_BACKUP_DIR.get());
078    argDisplayMap.put(ATTR_BACKUP_ID, INFO_RESTORE_ARG_BACKUP_ID.get());
079    argDisplayMap.put(ATTR_TASK_RESTORE_VERIFY_ONLY, INFO_RESTORE_ARG_VERIFY_ONLY.get());
080  }
081
082
083  /** The task arguments. */
084  private File backupDirectory;
085  private String backupID;
086  private boolean verifyOnly;
087
088  private RestoreConfig restoreConfig;
089
090  /** {@inheritDoc} */
091  @Override
092  public LocalizableMessage getDisplayName() {
093    return INFO_TASK_RESTORE_NAME.get();
094  }
095
096  /** {@inheritDoc} */
097  @Override
098  public LocalizableMessage getAttributeDisplayName(String name) {
099    return argDisplayMap.get(name);
100  }
101
102  /** {@inheritDoc} */
103  @Override
104  public void initializeTask() throws DirectoryException
105  {
106    // If the client connection is available, then make sure the associated
107    // client has the BACKEND_RESTORE privilege.
108    Operation operation = getOperation();
109    if (operation != null)
110    {
111      ClientConnection clientConnection = operation.getClientConnection();
112      if (! clientConnection.hasPrivilege(Privilege.BACKEND_RESTORE, operation))
113      {
114        LocalizableMessage message = ERR_TASK_RESTORE_INSUFFICIENT_PRIVILEGES.get();
115        throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
116                                     message);
117      }
118    }
119
120
121    Entry taskEntry = getTaskEntry();
122
123    AttributeType typeBackupDirectory = getAttributeTypeOrDefault(ATTR_BACKUP_DIRECTORY_PATH);
124    AttributeType typebackupID = getAttributeTypeOrDefault(ATTR_BACKUP_ID);
125    AttributeType typeVerifyOnly = getAttributeTypeOrDefault(ATTR_TASK_RESTORE_VERIFY_ONLY);
126
127    List<Attribute> attrList;
128
129    attrList = taskEntry.getAttribute(typeBackupDirectory);
130    String backupDirectoryPath = TaskUtils.getSingleValueString(attrList);
131    backupDirectory = new File(backupDirectoryPath);
132    if (! backupDirectory.isAbsolute())
133    {
134      backupDirectory =
135           new File(DirectoryServer.getInstanceRoot(), backupDirectoryPath);
136    }
137
138    attrList = taskEntry.getAttribute(typebackupID);
139    backupID = TaskUtils.getSingleValueString(attrList);
140
141    attrList = taskEntry.getAttribute(typeVerifyOnly);
142    verifyOnly = TaskUtils.getBoolean(attrList, false);
143
144  }
145
146  /**
147   * Acquire an exclusive lock on a backend.
148   * @param backend The backend on which the lock is to be acquired.
149   * @return true if the lock was successfully acquired.
150   */
151  private boolean lockBackend(Backend<?> backend)
152  {
153    try
154    {
155      String lockFile = LockFileManager.getBackendLockFileName(backend);
156      StringBuilder failureReason = new StringBuilder();
157      if (! LockFileManager.acquireExclusiveLock(lockFile, failureReason))
158      {
159        logger.error(ERR_RESTOREDB_CANNOT_LOCK_BACKEND, backend.getBackendID(), failureReason);
160        return false;
161      }
162    }
163    catch (Exception e)
164    {
165      logger.error(ERR_RESTOREDB_CANNOT_LOCK_BACKEND, backend.getBackendID(), getExceptionMessage(e));
166      return false;
167    }
168    return true;
169  }
170
171  /**
172   * Release a lock on a backend.
173   * @param backend The backend on which the lock is held.
174   * @return true if the lock was successfully released.
175   */
176  private boolean unlockBackend(Backend<?> backend)
177  {
178    try
179    {
180      String lockFile = LockFileManager.getBackendLockFileName(backend);
181      StringBuilder failureReason = new StringBuilder();
182      if (! LockFileManager.releaseLock(lockFile, failureReason))
183      {
184        logger.warn(WARN_RESTOREDB_CANNOT_UNLOCK_BACKEND, backend.getBackendID(), failureReason);
185        return false;
186      }
187    }
188    catch (Exception e)
189    {
190      logger.warn(WARN_RESTOREDB_CANNOT_UNLOCK_BACKEND, backend.getBackendID(), getExceptionMessage(e));
191      return false;
192    }
193    return true;
194  }
195
196  /** {@inheritDoc} */
197  @Override
198  public void interruptTask(TaskState interruptState, LocalizableMessage interruptReason)
199  {
200    if (TaskState.STOPPED_BY_ADMINISTRATOR.equals(interruptState) &&
201            restoreConfig != null)
202    {
203      addLogMessage(Severity.INFORMATION, TaskMessages.INFO_TASK_STOPPED_BY_ADMIN.get(
204      interruptReason));
205      setTaskInterruptState(interruptState);
206      restoreConfig.cancel();
207    }
208  }
209
210  /** {@inheritDoc} */
211  @Override
212  public boolean isInterruptable() {
213    return true;
214  }
215
216  /** {@inheritDoc} */
217  @Override
218  protected TaskState runTask()
219  {
220    // Open the backup directory and make sure it is valid.
221    BackupDirectory backupDir;
222    try
223    {
224      backupDir = BackupDirectory.readBackupDirectoryDescriptor(
225           backupDirectory.getPath());
226    }
227    catch (Exception e)
228    {
229      logger.error(ERR_RESTOREDB_CANNOT_READ_BACKUP_DIRECTORY, backupDirectory, getExceptionMessage(e));
230      return TaskState.STOPPED_BY_ERROR;
231    }
232
233
234    // If a backup ID was specified, then make sure it is valid.  If none was
235    // provided, then choose the latest backup from the archive.
236    if (backupID != null)
237    {
238      BackupInfo backupInfo = backupDir.getBackupInfo(backupID);
239      if (backupInfo == null)
240      {
241        logger.error(ERR_RESTOREDB_INVALID_BACKUP_ID, backupID, backupDirectory);
242        return TaskState.STOPPED_BY_ERROR;
243      }
244    }
245    else
246    {
247      BackupInfo latestBackup = backupDir.getLatestBackup();
248      if (latestBackup == null)
249      {
250        logger.error(ERR_RESTOREDB_NO_BACKUPS_IN_DIRECTORY, backupDirectory);
251        return TaskState.STOPPED_BY_ERROR;
252      }
253      else
254      {
255        backupID = latestBackup.getBackupID();
256      }
257    }
258
259    // Get the DN of the backend configuration entry from the backup.
260    DN configEntryDN = backupDir.getConfigEntryDN();
261
262    ConfigEntry configEntry;
263    try
264    {
265      // Get the backend configuration entry.
266      configEntry = DirectoryServer.getConfigEntry(configEntryDN);
267    }
268    catch (ConfigException e)
269    {
270      logger.traceException(e);
271      logger.error(ERR_RESTOREDB_NO_BACKENDS_FOR_DN, backupDirectory, configEntryDN);
272      return TaskState.STOPPED_BY_ERROR;
273    }
274
275    // Get the backend ID from the configuration entry.
276    String backendID = TaskUtils.getBackendID(configEntry);
277
278    // Get the backend.
279    Backend<?> backend = DirectoryServer.getBackend(backendID);
280    if (!backend.supports(BackendOperation.RESTORE))
281    {
282      logger.error(ERR_RESTOREDB_CANNOT_RESTORE, backend.getBackendID());
283      return TaskState.STOPPED_BY_ERROR;
284    }
285
286    // Create the restore config object from the information available.
287    restoreConfig = new RestoreConfig(backupDir, backupID, verifyOnly);
288
289    // Notify the task listeners that a restore is going to start
290    // this must be done before disabling the backend to allow
291    // listener to get access to the backend configuration
292    // and to take appropriate actions.
293    DirectoryServer.notifyRestoreBeginning(backend, restoreConfig);
294
295    // Disable the backend.
296    if ( !verifyOnly)
297    {
298      try
299      {
300        TaskUtils.disableBackend(backendID);
301      } catch (DirectoryException e)
302      {
303        logger.traceException(e);
304
305        logger.error(e.getMessageObject());
306        return TaskState.STOPPED_BY_ERROR;
307      }
308    }
309
310    // From here we must make sure to re-enable the backend before returning.
311    boolean errorsEncountered = false;
312    try
313    {
314      // Acquire an exclusive lock for the backend.
315      if (verifyOnly || lockBackend(backend))
316      {
317        // From here we must make sure to release the backend exclusive lock.
318        try
319        {
320          // Perform the restore.
321          try
322          {
323            backend.restoreBackup(restoreConfig);
324          }
325          catch (DirectoryException de)
326          {
327            DirectoryServer.notifyRestoreEnded(backend, restoreConfig, false);
328            logger.error(ERR_RESTOREDB_ERROR_DURING_BACKUP, backupID, backupDir.getPath(), de.getMessageObject());
329            errorsEncountered = true;
330          }
331          catch (Exception e)
332          {
333            DirectoryServer.notifyRestoreEnded(backend, restoreConfig, false);
334            logger.error(ERR_RESTOREDB_ERROR_DURING_BACKUP, backupID, backupDir.getPath(), getExceptionMessage(e));
335            errorsEncountered = true;
336          }
337        }
338        finally
339        {
340          // Release the exclusive lock on the backend.
341          if (!verifyOnly && !unlockBackend(backend))
342          {
343            errorsEncountered = true;
344          }
345        }
346      }
347    }
348    finally
349    {
350      // Enable the backend.
351      if (! verifyOnly)
352      {
353        try
354        {
355          TaskUtils.enableBackend(backendID);
356          // it is necessary to retrieve the backend structure again
357          // because disabling and enabling it again may have resulted
358          // in a new backend being registered to the server.
359          backend = DirectoryServer.getBackend(backendID);
360        } catch (DirectoryException e)
361        {
362          logger.traceException(e);
363
364          logger.error(e.getMessageObject());
365          errorsEncountered = true;
366        }
367      }
368      DirectoryServer.notifyRestoreEnded(backend, restoreConfig, true);
369    }
370
371    if (errorsEncountered)
372    {
373      return TaskState.COMPLETED_WITH_ERRORS;
374    }
375    else
376    {
377      return getFinalTaskState();
378    }
379  }
380}