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 }