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