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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.tasks;
028    import org.opends.messages.Message;
029    import org.opends.messages.TaskMessages;
030    
031    import static org.opends.server.core.DirectoryServer.getAttributeType;
032    import static org.opends.server.config.ConfigConstants.*;
033    import static org.opends.messages.TaskMessages.*;
034    import static org.opends.messages.ToolMessages.*;
035    import static org.opends.server.util.StaticUtils.*;
036    import static org.opends.server.loggers.debug.DebugLogger.*;
037    import org.opends.server.loggers.debug.DebugTracer;
038    import org.opends.server.types.DebugLogLevel;
039    
040    import org.opends.server.backends.task.Task;
041    import org.opends.server.backends.task.TaskState;
042    import org.opends.server.core.DirectoryServer;
043    import org.opends.server.core.LockFileManager;
044    import org.opends.server.api.Backend;
045    import org.opends.server.api.ClientConnection;
046    import org.opends.server.config.ConfigEntry;
047    import org.opends.server.config.ConfigException;
048    import org.opends.server.types.Attribute;
049    import org.opends.server.types.AttributeType;
050    import org.opends.server.types.BackupDirectory;
051    import org.opends.server.types.BackupInfo;
052    import org.opends.server.types.DirectoryException;
053    import org.opends.server.types.DN;
054    import org.opends.server.types.Entry;
055    
056    
057    import org.opends.server.types.Operation;
058    import org.opends.server.types.Privilege;
059    import org.opends.server.types.RestoreConfig;
060    import org.opends.server.types.ResultCode;
061    
062    import java.util.List;
063    import java.util.Map;
064    import java.util.HashMap;
065    import java.io.File;
066    
067    /**
068     * This class provides an implementation of a Directory Server task that can
069     * be used to restore a binary backup of a Directory Server backend.
070     */
071    public class RestoreTask extends Task
072    {
073      /**
074       * The tracer object for the debug logger.
075       */
076      private static final DebugTracer TRACER = getTracer();
077    
078    
079      /**
080       * Stores mapping between configuration attribute name and its label.
081       */
082      static private Map<String,Message> argDisplayMap =
083              new HashMap<String,Message>();
084      static {
085        argDisplayMap.put(
086                ATTR_BACKUP_DIRECTORY_PATH,
087                INFO_RESTORE_ARG_BACKUP_DIR.get());
088    
089        argDisplayMap.put(
090                ATTR_BACKUP_ID,
091                INFO_RESTORE_ARG_BACKUP_ID.get());
092    
093        argDisplayMap.put(
094                ATTR_TASK_RESTORE_VERIFY_ONLY,
095                INFO_RESTORE_ARG_VERIFY_ONLY.get());
096      }
097    
098    
099      // The task arguments.
100      private File backupDirectory;
101      private String backupID;
102      private boolean verifyOnly;
103    
104      private RestoreConfig restoreConfig;
105    
106      /**
107       * {@inheritDoc}
108       */
109      public Message getDisplayName() {
110        return INFO_TASK_RESTORE_NAME.get();
111      }
112    
113      /**
114       * {@inheritDoc}
115       */
116      public Message getAttributeDisplayName(String name) {
117        return argDisplayMap.get(name);
118      }
119    
120      /**
121       * {@inheritDoc}
122       */
123      @Override public void initializeTask() throws DirectoryException
124      {
125        // If the client connection is available, then make sure the associated
126        // client has the BACKEND_RESTORE privilege.
127        Operation operation = getOperation();
128        if (operation != null)
129        {
130          ClientConnection clientConnection = operation.getClientConnection();
131          if (! clientConnection.hasPrivilege(Privilege.BACKEND_RESTORE, operation))
132          {
133            Message message = ERR_TASK_RESTORE_INSUFFICIENT_PRIVILEGES.get();
134            throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
135                                         message);
136          }
137        }
138    
139    
140        Entry taskEntry = getTaskEntry();
141    
142        AttributeType typeBackupDirectory;
143        AttributeType typebackupID;
144        AttributeType typeVerifyOnly;
145    
146    
147        typeBackupDirectory = getAttributeType(ATTR_BACKUP_DIRECTORY_PATH, true);
148        typebackupID        = getAttributeType(ATTR_BACKUP_ID, true);
149        typeVerifyOnly      = getAttributeType(ATTR_TASK_RESTORE_VERIFY_ONLY, true);
150    
151        List<Attribute> attrList;
152    
153        attrList = taskEntry.getAttribute(typeBackupDirectory);
154        String backupDirectoryPath = TaskUtils.getSingleValueString(attrList);
155        backupDirectory = new File(backupDirectoryPath);
156        if (! backupDirectory.isAbsolute())
157        {
158          backupDirectory =
159               new File(DirectoryServer.getServerRoot(), backupDirectoryPath);
160        }
161    
162        attrList = taskEntry.getAttribute(typebackupID);
163        backupID = TaskUtils.getSingleValueString(attrList);
164    
165        attrList = taskEntry.getAttribute(typeVerifyOnly);
166        verifyOnly = TaskUtils.getBoolean(attrList, false);
167    
168      }
169    
170      /**
171       * Acquire an exclusive lock on a backend.
172       * @param backend The backend on which the lock is to be acquired.
173       * @return true if the lock was successfully acquired.
174       */
175      private boolean lockBackend(Backend backend)
176      {
177        try
178        {
179          String lockFile = LockFileManager.getBackendLockFileName(backend);
180          StringBuilder failureReason = new StringBuilder();
181          if (! LockFileManager.acquireExclusiveLock(lockFile, failureReason))
182          {
183            Message message = ERR_RESTOREDB_CANNOT_LOCK_BACKEND.get(
184                backend.getBackendID(), String.valueOf(failureReason));
185            logError(message);
186            return false;
187          }
188        }
189        catch (Exception e)
190        {
191          Message message = ERR_RESTOREDB_CANNOT_LOCK_BACKEND.get(
192              backend.getBackendID(), getExceptionMessage(e));
193          logError(message);
194          return false;
195        }
196        return true;
197      }
198    
199      /**
200       * Release a lock on a backend.
201       * @param backend The backend on which the lock is held.
202       * @return true if the lock was successfully released.
203       */
204      private boolean unlockBackend(Backend backend)
205      {
206        try
207        {
208          String lockFile = LockFileManager.getBackendLockFileName(backend);
209          StringBuilder failureReason = new StringBuilder();
210          if (! LockFileManager.releaseLock(lockFile, failureReason))
211          {
212            Message message = WARN_RESTOREDB_CANNOT_UNLOCK_BACKEND.get(
213                backend.getBackendID(), String.valueOf(failureReason));
214            logError(message);
215            return false;
216          }
217        }
218        catch (Exception e)
219        {
220          Message message = WARN_RESTOREDB_CANNOT_UNLOCK_BACKEND.get(
221              backend.getBackendID(), getExceptionMessage(e));
222          logError(message);
223          return false;
224        }
225        return true;
226      }
227    
228    
229      /**
230       * {@inheritDoc}
231       */
232      public void interruptTask(TaskState interruptState, Message interruptReason)
233      {
234        if (TaskState.STOPPED_BY_ADMINISTRATOR.equals(interruptState) &&
235                restoreConfig != null)
236        {
237          addLogMessage(TaskMessages.INFO_TASK_STOPPED_BY_ADMIN.get(
238                  interruptReason));
239          setTaskInterruptState(interruptState);
240          restoreConfig.cancel();
241        }
242      }
243    
244    
245      /**
246       * {@inheritDoc}
247       */
248      public boolean isInterruptable() {
249        return true;
250      }
251    
252    
253      /**
254       * {@inheritDoc}
255       */
256      protected TaskState runTask()
257      {
258        // Open the backup directory and make sure it is valid.
259        BackupDirectory backupDir;
260        try
261        {
262          backupDir = BackupDirectory.readBackupDirectoryDescriptor(
263               backupDirectory.getPath());
264        }
265        catch (Exception e)
266        {
267          Message message = ERR_RESTOREDB_CANNOT_READ_BACKUP_DIRECTORY.get(
268              String.valueOf(backupDirectory), getExceptionMessage(e));
269          logError(message);
270          return TaskState.STOPPED_BY_ERROR;
271        }
272    
273    
274        // If a backup ID was specified, then make sure it is valid.  If none was
275        // provided, then choose the latest backup from the archive.
276        if (backupID != null)
277        {
278          BackupInfo backupInfo = backupDir.getBackupInfo(backupID);
279          if (backupInfo == null)
280          {
281            Message message =
282                ERR_RESTOREDB_INVALID_BACKUP_ID.get(
283                        backupID, String.valueOf(backupDirectory));
284            logError(message);
285            return TaskState.STOPPED_BY_ERROR;
286          }
287        }
288        else
289        {
290          BackupInfo latestBackup = backupDir.getLatestBackup();
291          if (latestBackup == null)
292          {
293            Message message =
294                ERR_RESTOREDB_NO_BACKUPS_IN_DIRECTORY.get(
295                        String.valueOf(backupDirectory));
296            logError(message);
297            return TaskState.STOPPED_BY_ERROR;
298          }
299          else
300          {
301            backupID = latestBackup.getBackupID();
302          }
303        }
304    
305        // Get the DN of the backend configuration entry from the backup.
306        DN configEntryDN = backupDir.getConfigEntryDN();
307    
308        ConfigEntry configEntry;
309        try
310        {
311          // Get the backend configuration entry.
312          configEntry = DirectoryServer.getConfigEntry(configEntryDN);
313        }
314        catch (ConfigException e)
315        {
316          if (debugEnabled())
317          {
318            TRACER.debugCaught(DebugLogLevel.ERROR, e);
319          }
320          Message message = ERR_RESTOREDB_NO_BACKENDS_FOR_DN.get(
321              String.valueOf(backupDirectory), configEntryDN.toString());
322          logError(message);
323          return TaskState.STOPPED_BY_ERROR;
324        }
325    
326        // Get the backend ID from the configuration entry.
327        String backendID = TaskUtils.getBackendID(configEntry);
328    
329        // Get the backend.
330        Backend backend = DirectoryServer.getBackend(backendID);
331    
332        if (! backend.supportsRestore())
333        {
334          Message message =
335              ERR_RESTOREDB_CANNOT_RESTORE.get(backend.getBackendID());
336          logError(message);
337          return TaskState.STOPPED_BY_ERROR;
338        }
339    
340        // Create the restore config object from the information available.
341        restoreConfig = new RestoreConfig(backupDir, backupID, verifyOnly);
342    
343        // Notify the task listeners that a restore is going to start
344        // this must be done before disabling the backend to allow
345        // listener to get access to the backend configuration
346        // and to take appropriate actions.
347        DirectoryServer.notifyRestoreBeginning(backend, restoreConfig);
348    
349        // Disable the backend.
350        if ( !verifyOnly)
351        {
352          try
353          {
354            TaskUtils.disableBackend(backendID);
355          } catch (DirectoryException e)
356          {
357            if (debugEnabled())
358            {
359              TRACER.debugCaught(DebugLogLevel.ERROR, e);
360            }
361    
362            logError(e.getMessageObject());
363            return TaskState.STOPPED_BY_ERROR;
364          }
365        }
366    
367        // From here we must make sure to re-enable the backend before returning.
368        boolean errorsEncountered = false;
369        try
370        {
371          // Acquire an exclusive lock for the backend.
372          if (verifyOnly || lockBackend(backend))
373          {
374            // From here we must make sure to release the backend exclusive lock.
375            try
376            {
377              // Perform the restore.
378              try
379              {
380                backend.restoreBackup(restoreConfig);
381              }
382              catch (DirectoryException de)
383              {
384                DirectoryServer.notifyRestoreEnded(backend, restoreConfig, false);
385                Message message = ERR_RESTOREDB_ERROR_DURING_BACKUP.get(
386                    backupID, backupDir.getPath(), de.getMessageObject());
387                logError(message);
388                errorsEncountered = true;
389              }
390              catch (Exception e)
391              {
392                DirectoryServer.notifyRestoreEnded(backend, restoreConfig, false);
393                Message message = ERR_RESTOREDB_ERROR_DURING_BACKUP.get(
394                    backupID, backupDir.getPath(), getExceptionMessage(e));
395                logError(message);
396                errorsEncountered = true;
397              }
398            }
399            finally
400            {
401              // Release the exclusive lock on the backend.
402              if ( (!verifyOnly) && !unlockBackend(backend))
403              {
404                errorsEncountered = true;
405              }
406            }
407          }
408        }
409        finally
410        {
411          // Enable the backend.
412          if (! verifyOnly)
413          {
414            try
415            {
416              TaskUtils.enableBackend(backendID);
417              // it is necessary to retrieve the backend structure again
418              // because disabling and enabling it again may have resulted
419              // in a new backend being registered to the server.
420              backend = DirectoryServer.getBackend(backendID);
421            } catch (DirectoryException e)
422            {
423              if (debugEnabled())
424              {
425                TRACER.debugCaught(DebugLogLevel.ERROR, e);
426              }
427    
428              logError(e.getMessageObject());
429              errorsEncountered = true;
430            }
431          }
432          DirectoryServer.notifyRestoreEnded(backend, restoreConfig, true);
433        }
434    
435        if (errorsEncountered)
436        {
437          return TaskState.COMPLETED_WITH_ERRORS;
438        }
439        else
440        {
441          return getFinalTaskState();
442        }
443      }
444    }