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