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