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