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.config.ConfigConstants.*;
032    import static org.opends.server.core.DirectoryServer.getAttributeType;
033    import static org.opends.messages.TaskMessages.*;
034    import static org.opends.messages.ToolMessages.*;
035    import static org.opends.server.util.ServerConstants.DATE_FORMAT_GMT_TIME;
036    import static org.opends.server.util.StaticUtils.*;
037    import static org.opends.server.util.ServerConstants.
038         BACKUP_DIRECTORY_DESCRIPTOR_FILE;
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.BackupConfig;
051    import org.opends.server.types.BackupDirectory;
052    import org.opends.server.types.DirectoryException;
053    import org.opends.server.types.Entry;
054    import org.opends.server.types.Operation;
055    import org.opends.server.types.Privilege;
056    import org.opends.server.types.ResultCode;
057    import org.opends.server.admin.std.server.BackendCfg;
058    
059    import java.util.ArrayList;
060    import java.util.Date;
061    import java.util.List;
062    import java.util.Map;
063    import java.util.TimeZone;
064    import java.util.HashMap;
065    import java.text.SimpleDateFormat;
066    import java.io.File;
067    
068    /**
069     * This class provides an implementation of a Directory Server task that may be
070     * used to back up a Directory Server backend in a binary form that may be
071     * quickly archived and restored.
072     */
073    public class BackupTask extends Task
074    {
075    
076    
077      /**
078       * Stores mapping between configuration attribute name and its label.
079       */
080      static private Map<String,Message> argDisplayMap =
081              new HashMap<String,Message>();
082      static {
083        argDisplayMap.put(
084                ATTR_TASK_BACKUP_ALL,
085                INFO_BACKUP_ARG_BACKUPALL.get());
086    
087        argDisplayMap.put(
088                ATTR_TASK_BACKUP_COMPRESS,
089                INFO_BACKUP_ARG_COMPRESS.get());
090    
091        argDisplayMap.put(
092                ATTR_TASK_BACKUP_ENCRYPT,
093                INFO_BACKUP_ARG_ENCRYPT.get());
094    
095        argDisplayMap.put(
096                ATTR_TASK_BACKUP_HASH,
097                INFO_BACKUP_ARG_HASH.get());
098    
099        argDisplayMap.put(
100                ATTR_TASK_BACKUP_INCREMENTAL,
101                INFO_BACKUP_ARG_INCREMENTAL.get());
102    
103        argDisplayMap.put(
104                ATTR_TASK_BACKUP_SIGN_HASH,
105                INFO_BACKUP_ARG_SIGN_HASH.get());
106    
107        argDisplayMap.put(
108                ATTR_TASK_BACKUP_BACKEND_ID,
109                INFO_BACKUP_ARG_BACKEND_IDS.get());
110    
111        argDisplayMap.put(
112                ATTR_BACKUP_ID,
113                INFO_BACKUP_ARG_BACKUP_ID.get());
114    
115        argDisplayMap.put(
116                ATTR_BACKUP_DIRECTORY_PATH,
117                INFO_BACKUP_ARG_BACKUP_DIR.get());
118    
119        argDisplayMap.put(
120                ATTR_TASK_BACKUP_INCREMENTAL_BASE_ID,
121                INFO_BACKUP_ARG_INC_BASE_ID.get());
122      }
123    
124    
125      // The task arguments.
126      private boolean backUpAll;
127      private boolean compress;
128      private boolean encrypt;
129      private boolean hash;
130      private boolean incremental;
131      private boolean signHash;
132      private List<String>  backendIDList;
133      private String  backupID;
134      private File    backupDirectory;
135      private String  incrementalBase;
136    
137      private BackupConfig backupConfig;
138    
139      /**
140       * All the backend configuration entries defined in the server mapped
141       * by their backend ID.
142       */
143      private Map<String,ConfigEntry> configEntries;
144    
145      private ArrayList<Backend> backendsToArchive;
146    
147      /**
148       * {@inheritDoc}
149       */
150      public Message getDisplayName() {
151        return INFO_TASK_BACKUP_NAME.get();
152      }
153    
154      /**
155       * {@inheritDoc}
156       */
157      public Message getAttributeDisplayName(String attrName) {
158        return argDisplayMap.get(attrName);
159      }
160    
161      /**
162       * {@inheritDoc}
163       */
164      @Override public void initializeTask() throws DirectoryException
165      {
166        // If the client connection is available, then make sure the associated
167        // client has the BACKEND_BACKUP privilege.
168        Operation operation = getOperation();
169        if (operation != null)
170        {
171          ClientConnection clientConnection = operation.getClientConnection();
172          if (! clientConnection.hasPrivilege(Privilege.BACKEND_BACKUP, operation))
173          {
174            Message message = ERR_TASK_BACKUP_INSUFFICIENT_PRIVILEGES.get();
175            throw new DirectoryException(ResultCode.INSUFFICIENT_ACCESS_RIGHTS,
176                                         message);
177          }
178        }
179    
180    
181        Entry taskEntry = getTaskEntry();
182    
183        AttributeType typeBackupAll;
184        AttributeType typeCompress;
185        AttributeType typeEncrypt;
186        AttributeType typeHash;
187        AttributeType typeIncremental;
188        AttributeType typeSignHash;
189        AttributeType typeBackendID;
190        AttributeType typeBackupID;
191        AttributeType typeBackupDirectory;
192        AttributeType typeIncrementalBaseID;
193    
194    
195        typeBackupAll =
196             getAttributeType(ATTR_TASK_BACKUP_ALL, true);
197        typeCompress =
198             getAttributeType(ATTR_TASK_BACKUP_COMPRESS, true);
199        typeEncrypt =
200             getAttributeType(ATTR_TASK_BACKUP_ENCRYPT, true);
201        typeHash =
202             getAttributeType(ATTR_TASK_BACKUP_HASH, true);
203        typeIncremental =
204             getAttributeType(ATTR_TASK_BACKUP_INCREMENTAL, true);
205        typeSignHash =
206             getAttributeType(ATTR_TASK_BACKUP_SIGN_HASH, true);
207        typeBackendID =
208             getAttributeType(ATTR_TASK_BACKUP_BACKEND_ID, true);
209        typeBackupID =
210             getAttributeType(ATTR_BACKUP_ID, true);
211        typeBackupDirectory =
212             getAttributeType(ATTR_BACKUP_DIRECTORY_PATH, true);
213        typeIncrementalBaseID =
214             getAttributeType(ATTR_TASK_BACKUP_INCREMENTAL_BASE_ID, true);
215    
216    
217        List<Attribute> attrList;
218    
219        attrList = taskEntry.getAttribute(typeBackupAll);
220        backUpAll = TaskUtils.getBoolean(attrList, false);
221    
222        attrList = taskEntry.getAttribute(typeCompress);
223        compress = TaskUtils.getBoolean(attrList, false);
224    
225        attrList = taskEntry.getAttribute(typeEncrypt);
226        encrypt = TaskUtils.getBoolean(attrList, false);
227    
228        attrList = taskEntry.getAttribute(typeHash);
229        hash = TaskUtils.getBoolean(attrList, false);
230    
231        attrList = taskEntry.getAttribute(typeIncremental);
232        incremental = TaskUtils.getBoolean(attrList, false);
233    
234        attrList = taskEntry.getAttribute(typeSignHash);
235        signHash = TaskUtils.getBoolean(attrList, false);
236    
237        attrList = taskEntry.getAttribute(typeBackendID);
238        backendIDList = TaskUtils.getMultiValueString(attrList);
239    
240        attrList = taskEntry.getAttribute(typeBackupID);
241        backupID = TaskUtils.getSingleValueString(attrList);
242    
243        attrList = taskEntry.getAttribute(typeBackupDirectory);
244        String backupDirectoryPath = TaskUtils.getSingleValueString(attrList);
245        backupDirectory = new File(backupDirectoryPath);
246        if (! backupDirectory.isAbsolute())
247        {
248          backupDirectory =
249               new File(DirectoryServer.getServerRoot(), backupDirectoryPath);
250        }
251    
252        attrList = taskEntry.getAttribute(typeIncrementalBaseID);
253        incrementalBase = TaskUtils.getSingleValueString(attrList);
254    
255        configEntries = TaskUtils.getBackendConfigEntries();
256      }
257    
258    
259      /**
260       * Validate the task arguments and construct the list of backends to be
261       * archived.
262       * @return  true if the task arguments are valid.
263       */
264      private boolean argumentsAreValid()
265      {
266        // Make sure that either the backUpAll argument was provided or at least one
267        // backend ID was given.  They are mutually exclusive.
268        if (backUpAll)
269        {
270          if (!backendIDList.isEmpty())
271          {
272            Message message = ERR_BACKUPDB_CANNOT_MIX_BACKUP_ALL_AND_BACKEND_ID.get(
273                ATTR_TASK_BACKUP_ALL, ATTR_TASK_BACKUP_BACKEND_ID);
274            logError(message);
275            return false;
276          }
277        }
278        else if (backendIDList.isEmpty())
279        {
280          Message message = ERR_BACKUPDB_NEED_BACKUP_ALL_OR_BACKEND_ID.get(
281              ATTR_TASK_BACKUP_ALL, ATTR_TASK_BACKUP_BACKEND_ID);
282          logError(message);
283          return false;
284        }
285    
286    
287        // If no backup ID was provided, then create one with the current timestamp.
288        if (backupID == null)
289        {
290          SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
291          dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
292          backupID = dateFormat.format(new Date());
293        }
294    
295    
296        // If the incremental base ID was specified, then make sure it is an
297        // incremental backup.
298        if (incrementalBase != null)
299        {
300          if (! incremental)
301          {
302            Message message = ERR_BACKUPDB_INCREMENTAL_BASE_REQUIRES_INCREMENTAL.
303                get(ATTR_TASK_BACKUP_INCREMENTAL_BASE_ID,
304                    ATTR_TASK_BACKUP_INCREMENTAL);
305            logError(message);
306            return false;
307          }
308        }
309    
310    
311        // If the signHash option was provided, then make sure that the hash option
312        // was given.
313        if (signHash && (! hash))
314        {
315          Message message = ERR_BACKUPDB_SIGN_REQUIRES_HASH.get(
316              ATTR_TASK_BACKUP_SIGN_HASH, ATTR_TASK_BACKUP_HASH);
317          logError(message);
318          return false;
319        }
320    
321    
322        // Make sure that the backup directory exists.  If not, then create it.
323        if (! backupDirectory.exists())
324        {
325          try
326          {
327            backupDirectory.mkdirs();
328          }
329          catch (Exception e)
330          {
331            Message message = ERR_BACKUPDB_CANNOT_CREATE_BACKUP_DIR.get(
332                    backupDirectory.getPath(), getExceptionMessage(e));
333            System.err.println(message);
334            return false;
335          }
336        }
337    
338        int numBackends = configEntries.size();
339    
340    
341        backendsToArchive = new ArrayList<Backend>(numBackends);
342    
343        if (backUpAll)
344        {
345          for (Map.Entry<String,ConfigEntry> mapEntry : configEntries.entrySet())
346          {
347            Backend b = DirectoryServer.getBackend(mapEntry.getKey());
348            if (b != null && b.supportsBackup())
349            {
350              backendsToArchive.add(b);
351            }
352          }
353        }
354        else
355        {
356          // Iterate through the set of requested backends and make sure they can
357          // be used.
358          for (String id : backendIDList)
359          {
360            Backend b = DirectoryServer.getBackend(id);
361            if (b == null || configEntries.get(id) == null)
362            {
363              Message message = ERR_BACKUPDB_NO_BACKENDS_FOR_ID.get(id);
364              logError(message);
365            }
366            else if (! b.supportsBackup())
367            {
368              Message message =
369                  WARN_BACKUPDB_BACKUP_NOT_SUPPORTED.get(b.getBackendID());
370              logError(message);
371            }
372            else
373            {
374              backendsToArchive.add(b);
375            }
376          }
377    
378          // It is an error if any of the requested backends could not be used.
379          if (backendsToArchive.size() != backendIDList.size())
380          {
381            return false;
382          }
383        }
384    
385    
386        // If there are no backends to archive, then print an error and exit.
387        if (backendsToArchive.isEmpty())
388        {
389          Message message = WARN_BACKUPDB_NO_BACKENDS_TO_ARCHIVE.get();
390          logError(message);
391          return false;
392        }
393    
394    
395        return true;
396      }
397    
398    
399      /**
400       * Archive a single backend, where the backend is known to support backups.
401       * @param b The backend to be archived.
402       * @param backupLocation The backup directory.
403       * @return true if the backend was successfully archived.
404       */
405      private boolean backupBackend(Backend b, File backupLocation)
406      {
407        // Get the config entry for this backend.
408        BackendCfg cfg = TaskUtils.getConfigEntry(b);
409    
410    
411        // If the directory doesn't exist, then create it.  If it does exist, then
412        // see if it has a backup descriptor file.
413        BackupDirectory backupDir;
414        if (backupLocation.exists())
415        {
416          String descriptorPath = backupLocation.getPath() + File.separator +
417                                  BACKUP_DIRECTORY_DESCRIPTOR_FILE;
418          File descriptorFile = new File(descriptorPath);
419          if (descriptorFile.exists())
420          {
421            try
422            {
423              backupDir = BackupDirectory.readBackupDirectoryDescriptor(
424                   backupLocation.getPath());
425            }
426            catch (ConfigException ce)
427            {
428              Message message = ERR_BACKUPDB_CANNOT_PARSE_BACKUP_DESCRIPTOR.get(
429                  descriptorPath, ce.getMessage());
430              logError(message);
431              return false;
432            }
433            catch (Exception e)
434            {
435              Message message = ERR_BACKUPDB_CANNOT_PARSE_BACKUP_DESCRIPTOR.get(
436                  descriptorPath, getExceptionMessage(e));
437              logError(message);
438              return false;
439            }
440          }
441          else
442          {
443            backupDir = new BackupDirectory(backupLocation.getPath(), cfg.dn());
444          }
445        }
446        else
447        {
448          try
449          {
450            backupLocation.mkdirs();
451          }
452          catch (Exception e)
453          {
454            Message message = ERR_BACKUPDB_CANNOT_CREATE_BACKUP_DIR.get(
455                backupLocation.getPath(), getExceptionMessage(e));
456            logError(message);
457            return false;
458          }
459    
460          backupDir = new BackupDirectory(backupLocation.getPath(),
461                                          cfg.dn());
462        }
463    
464    
465        // Create a backup configuration.
466        backupConfig = new BackupConfig(backupDir, backupID,
467                                                     incremental);
468        backupConfig.setCompressData(compress);
469        backupConfig.setEncryptData(encrypt);
470        backupConfig.setHashData(hash);
471        backupConfig.setSignHash(signHash);
472        backupConfig.setIncrementalBaseID(incrementalBase);
473    
474    
475        // Perform the backup.
476        try
477        {
478          DirectoryServer.notifyBackupBeginning(b, backupConfig);
479          b.createBackup(backupConfig);
480          DirectoryServer.notifyBackupEnded(b, backupConfig, true);
481        }
482        catch (DirectoryException de)
483        {
484          DirectoryServer.notifyBackupEnded(b, backupConfig, false);
485          Message message = ERR_BACKUPDB_ERROR_DURING_BACKUP.get(
486              b.getBackendID(), de.getMessageObject());
487          logError(message);
488          return false;
489        }
490        catch (Exception e)
491        {
492          DirectoryServer.notifyBackupEnded(b, backupConfig, false);
493          Message message = ERR_BACKUPDB_ERROR_DURING_BACKUP.get(
494              b.getBackendID(), getExceptionMessage(e));
495          logError(message);
496          return false;
497        }
498    
499        return true;
500      }
501    
502      /**
503       * Acquire a shared lock on a backend.
504       * @param b The backend on which the lock is to be acquired.
505       * @return true if the lock was successfully acquired.
506       */
507      private boolean lockBackend(Backend b)
508      {
509        try
510        {
511          String        lockFile      = LockFileManager.getBackendLockFileName(b);
512          StringBuilder failureReason = new StringBuilder();
513          if (! LockFileManager.acquireSharedLock(lockFile, failureReason))
514          {
515            Message message = ERR_BACKUPDB_CANNOT_LOCK_BACKEND.get(
516                b.getBackendID(), String.valueOf(failureReason));
517            logError(message);
518            return false;
519          }
520        }
521        catch (Exception e)
522        {
523          Message message = ERR_BACKUPDB_CANNOT_LOCK_BACKEND.get(
524              b.getBackendID(), getExceptionMessage(e));
525          logError(message);
526          return false;
527        }
528    
529        return true;
530      }
531    
532      /**
533       * Release a lock on a backend.
534       * @param b The backend on which the lock is held.
535       * @return true if the lock was successfully released.
536       */
537      private boolean unlockBackend(Backend b)
538      {
539        try
540        {
541          String lockFile = LockFileManager.getBackendLockFileName(b);
542          StringBuilder failureReason = new StringBuilder();
543          if (! LockFileManager.releaseLock(lockFile, failureReason))
544          {
545            Message message = WARN_BACKUPDB_CANNOT_UNLOCK_BACKEND.get(
546                b.getBackendID(), String.valueOf(failureReason));
547            logError(message);
548            return false;
549          }
550        }
551        catch (Exception e)
552        {
553          Message message = WARN_BACKUPDB_CANNOT_UNLOCK_BACKEND.get(
554              b.getBackendID(), getExceptionMessage(e));
555          logError(message);
556          return false;
557        }
558    
559        return true;
560      }
561    
562    
563      /**
564       * {@inheritDoc}
565       */
566      public void interruptTask(TaskState interruptState, Message interruptReason)
567      {
568        if (TaskState.STOPPED_BY_ADMINISTRATOR.equals(interruptState) &&
569                backupConfig != null)
570        {
571          addLogMessage(TaskMessages.INFO_TASK_STOPPED_BY_ADMIN.get(
572                  interruptReason));
573          setTaskInterruptState(interruptState);
574          backupConfig.cancel();
575        }
576      }
577    
578    
579      /**
580       * {@inheritDoc}
581       */
582      public boolean isInterruptable() {
583        return true;
584      }
585    
586    
587      /**
588       * {@inheritDoc}
589       */
590      protected TaskState runTask()
591      {
592        if (!argumentsAreValid())
593        {
594          return TaskState.STOPPED_BY_ERROR;
595        }
596    
597        boolean multiple;
598        if (backUpAll)
599        {
600          // We'll proceed as if we're backing up multiple backends in this case
601          // even if there's just one.
602          multiple = true;
603        }
604        else
605        {
606          // See if there are multiple backends to archive.
607          multiple = (backendsToArchive.size() > 1);
608        }
609    
610    
611        // Iterate through the backends to archive and back them up individually.
612        boolean errorsEncountered = false;
613        for (Backend b : backendsToArchive)
614        {
615          if (isCancelled())
616          {
617            break;
618          }
619    
620          // Acquire a shared lock for this backend.
621          if (!lockBackend(b))
622          {
623            errorsEncountered = true;
624            continue;
625          }
626    
627    
628          try
629          {
630            Message message = NOTE_BACKUPDB_STARTING_BACKUP.get(b.getBackendID());
631            logError(message);
632    
633    
634            // Get the path to the directory to use for this backup.  If we will be
635            // backing up multiple backends (or if we are backing up all backends,
636            // even if there's only one of them), then create a subdirectory for
637            // each
638            // backend.
639            File backupLocation;
640            if (multiple)
641            {
642              backupLocation = new File(backupDirectory, b.getBackendID());
643            }
644            else
645            {
646              backupLocation = backupDirectory;
647            }
648    
649    
650            if (!backupBackend(b, backupLocation))
651            {
652              errorsEncountered = true;
653            }
654          }
655          finally
656          {
657            // Release the shared lock for the backend.
658            if (!unlockBackend(b))
659            {
660              errorsEncountered = true;
661            }
662          }
663        }
664    
665    
666        // Print a final completed message, indicating whether there were any errors
667        // in the process.  In this case it means that the backup could not be
668        // completed at least for one of the backends.
669        if (errorsEncountered)
670        {
671          Message message = NOTE_BACKUPDB_COMPLETED_WITH_ERRORS.get();
672          logError(message);
673          return TaskState.STOPPED_BY_ERROR;
674        }
675        else if (isCancelled())
676        {
677          Message message = NOTE_BACKUPDB_CANCELLED.get();
678          logError(message);
679          return getTaskInterruptState();
680        }
681        else
682        {
683          Message message = NOTE_BACKUPDB_COMPLETED_SUCCESSFULLY.get();
684          logError(message);
685          return TaskState.COMPLETED_SUCCESSFULLY;
686        }
687      }
688    
689    
690    }