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 }