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-2009 Sun Microsystems, Inc. 025 * Portions Copyright 2012-2015 ForgeRock AS. 026 */ 027package org.opends.server.tools; 028 029import static org.opends.messages.ToolMessages.*; 030import static org.opends.server.config.ConfigConstants.*; 031import static org.opends.server.util.StaticUtils.*; 032 033import static com.forgerock.opendj.cli.ArgumentConstants.*; 034import static com.forgerock.opendj.cli.Utils.*; 035 036import java.io.OutputStream; 037import java.io.PrintStream; 038import java.text.DateFormat; 039import java.text.SimpleDateFormat; 040import java.util.ArrayList; 041import java.util.HashSet; 042import java.util.Iterator; 043import java.util.List; 044 045import org.forgerock.i18n.LocalizableMessage; 046import org.forgerock.i18n.slf4j.LocalizedLogger; 047import org.forgerock.opendj.config.server.ConfigException; 048import org.opends.server.admin.std.server.BackendCfg; 049import org.opends.server.api.Backend; 050import org.opends.server.api.Backend.BackendOperation; 051import org.opends.server.core.CoreConfigManager; 052import org.opends.server.core.DirectoryServer; 053import org.opends.server.core.LockFileManager; 054import org.opends.server.extensions.ConfigFileHandler; 055import org.opends.server.loggers.DebugLogger; 056import org.opends.server.loggers.ErrorLogPublisher; 057import org.opends.server.loggers.ErrorLogger; 058import org.opends.server.loggers.JDKLogging; 059import org.opends.server.loggers.TextErrorLogPublisher; 060import org.opends.server.loggers.TextWriter; 061import org.opends.server.protocols.ldap.LDAPAttribute; 062import org.opends.server.tasks.RestoreTask; 063import org.opends.server.tools.tasks.TaskTool; 064import org.opends.server.types.BackupDirectory; 065import org.opends.server.types.BackupInfo; 066import org.opends.server.types.DN; 067import org.opends.server.types.DirectoryException; 068import org.opends.server.types.InitializationException; 069import org.opends.server.types.NullOutputStream; 070import org.opends.server.types.RawAttribute; 071import org.opends.server.types.RestoreConfig; 072import org.opends.server.util.args.LDAPConnectionArgumentParser; 073 074import com.forgerock.opendj.cli.Argument; 075import com.forgerock.opendj.cli.ArgumentException; 076import com.forgerock.opendj.cli.BooleanArgument; 077import com.forgerock.opendj.cli.ClientException; 078import com.forgerock.opendj.cli.CommonArguments; 079import com.forgerock.opendj.cli.StringArgument; 080 081/** 082 * This program provides a utility that may be used to restore a binary backup 083 * of a Directory Server backend generated using the BackUpDB tool. This will 084 * be a process that is intended to run separate from Directory Server and not 085 * internally within the server process (e.g., via the tasks interface). 086 */ 087public class RestoreDB extends TaskTool { 088 089 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 090 091 /** 092 * The main method for RestoreDB tool. 093 * 094 * @param args The command-line arguments provided to this program. 095 */ 096 097 public static void main(String[] args) 098 { 099 int retCode = mainRestoreDB(args, true, System.out, System.err); 100 101 if(retCode != 0) 102 { 103 System.exit(filterExitCode(retCode)); 104 } 105 } 106 107 /** 108 * Processes the command-line arguments and invokes the restore process. 109 * 110 * @param args The command-line arguments provided to this program. 111 * 112 * @return The error code. 113 */ 114 public static int mainRestoreDB(String[] args) 115 { 116 return mainRestoreDB(args, true, System.out, System.err); 117 } 118 119 /** 120 * Processes the command-line arguments and invokes the restore process. 121 * 122 * @param args The command-line arguments provided to this 123 * program. 124 * @param initializeServer Indicates whether to initialize the server. 125 * @param outStream The output stream to use for standard output, or 126 * {@code null} if standard output is not needed. 127 * @param errStream The output stream to use for standard error, or 128 * {@code null} if standard error is not needed. 129 * 130 * @return The error code. 131 */ 132 public static int mainRestoreDB(String[] args, boolean initializeServer, 133 OutputStream outStream, 134 OutputStream errStream) 135 { 136 RestoreDB tool = new RestoreDB(); 137 return tool.process(args, initializeServer, outStream, errStream); 138 } 139 140 141 /** Define the command-line arguments that may be used with this program. */ 142 private BooleanArgument displayUsage; 143 private BooleanArgument listBackups; 144 private BooleanArgument verifyOnly; 145 private StringArgument backupIDString; 146 private StringArgument configClass; 147 private StringArgument configFile; 148 private StringArgument backupDirectory; 149 150 151 private int process(String[] args, boolean initializeServer, 152 OutputStream outStream, OutputStream errStream) 153 { 154 PrintStream out = NullOutputStream.wrapOrNullStream(outStream); 155 PrintStream err = NullOutputStream.wrapOrNullStream(errStream); 156 JDKLogging.disableLogging(); 157 158 // Create the command-line argument parser for use with this program. 159 LDAPConnectionArgumentParser argParser = 160 createArgParser("org.opends.server.tools.RestoreDB", 161 INFO_RESTOREDB_TOOL_DESCRIPTION.get()); 162 163 164 // Initialize all the command-line argument types and register them with the 165 // parser. 166 try 167 { 168 argParser.setShortToolDescription(REF_SHORT_DESC_RESTORE.get()); 169 170 configClass = 171 new StringArgument("configclass", OPTION_SHORT_CONFIG_CLASS, 172 OPTION_LONG_CONFIG_CLASS, true, false, 173 true, INFO_CONFIGCLASS_PLACEHOLDER.get(), 174 ConfigFileHandler.class.getName(), null, 175 INFO_DESCRIPTION_CONFIG_CLASS.get()); 176 configClass.setHidden(true); 177 argParser.addArgument(configClass); 178 179 180 configFile = 181 new StringArgument("configfile", 'f', "configFile", true, false, 182 true, INFO_CONFIGFILE_PLACEHOLDER.get(), null, 183 null, 184 INFO_DESCRIPTION_CONFIG_FILE.get()); 185 configFile.setHidden(true); 186 argParser.addArgument(configFile); 187 188 backupIDString = 189 new StringArgument("backupid", 'I', "backupID", false, false, true, 190 INFO_BACKUPID_PLACEHOLDER.get(), null, null, 191 INFO_RESTOREDB_DESCRIPTION_BACKUP_ID.get()); 192 argParser.addArgument(backupIDString); 193 194 195 backupDirectory = 196 new StringArgument("backupdirectory", 'd', "backupDirectory", true, 197 false, true, INFO_BACKUPDIR_PLACEHOLDER.get(), 198 null, null, 199 INFO_RESTOREDB_DESCRIPTION_BACKUP_DIR.get()); 200 argParser.addArgument(backupDirectory); 201 202 203 listBackups = new BooleanArgument( 204 "listbackups", 'l', "listBackups", 205 INFO_RESTOREDB_DESCRIPTION_LIST_BACKUPS.get()); 206 argParser.addArgument(listBackups); 207 208 209 verifyOnly = new BooleanArgument( 210 "verifyonly", OPTION_SHORT_DRYRUN, 211 OPTION_LONG_DRYRUN, 212 INFO_RESTOREDB_DESCRIPTION_VERIFY_ONLY.get()); 213 argParser.addArgument(verifyOnly); 214 215 216 displayUsage = CommonArguments.getShowUsage(); 217 argParser.addArgument(displayUsage); 218 argParser.setUsageArgument(displayUsage); 219 } 220 catch (ArgumentException ae) 221 { 222 printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage())); 223 return 1; 224 } 225 226 // Init the default values so that they can appear also on the usage. 227 try 228 { 229 argParser.getArguments().initArgumentsWithConfiguration(); 230 } 231 catch (ConfigException ce) 232 { 233 // Ignore. 234 } 235 236 // Parse the command-line arguments provided to this program. 237 try 238 { 239 argParser.parseArguments(args); 240 validateTaskArgs(); 241 } 242 catch (ArgumentException ae) 243 { 244 argParser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage())); 245 return 1; 246 } 247 catch (ClientException ce) 248 { 249 // No need to display the usage since the problem comes with a provided value. 250 printWrappedText(err, ce.getMessageObject()); 251 return 1; 252 } 253 254 255 // If we should just display usage or version information, 256 // then print it and exit. 257 if (argParser.usageOrVersionDisplayed()) 258 { 259 return 0; 260 } 261 262 263 if (listBackups.isPresent() && argParser.connectionArgumentsPresent()) { 264 printWrappedText(err, ERR_LDAP_CONN_INCOMPATIBLE_ARGS.get(listBackups.getLongIdentifier())); 265 return 1; 266 } 267 268 // Checks the version - if upgrade required, the tool is unusable 269 try 270 { 271 checkVersion(); 272 } 273 catch (InitializationException e) 274 { 275 printWrappedText(err, e.getMessage()); 276 return 1; 277 } 278 279 return process(argParser, initializeServer, out, err); 280 } 281 282 283 /** {@inheritDoc} */ 284 @Override 285 public void addTaskAttributes(List<RawAttribute> attributes) 286 { 287 addAttribute(attributes, ATTR_BACKUP_DIRECTORY_PATH, backupDirectory); 288 addAttribute(attributes, ATTR_BACKUP_ID, backupIDString); 289 addAttribute(attributes, ATTR_TASK_RESTORE_VERIFY_ONLY, verifyOnly); 290 } 291 292 private void addAttribute(List<RawAttribute> attributes, String attrName, Argument arg) 293 { 294 if (arg.getValue() != null && !arg.getValue().equals(arg.getDefaultValue())) 295 { 296 attributes.add(new LDAPAttribute(attrName, arg.getValue())); 297 } 298 } 299 300 /** {@inheritDoc} */ 301 @Override 302 public String getTaskObjectclass() { 303 return "ds-task-restore"; 304 } 305 306 /** {@inheritDoc} */ 307 @Override 308 public Class<?> getTaskClass() { 309 return RestoreTask.class; 310 } 311 312 /** {@inheritDoc} */ 313 @Override 314 protected int processLocal(boolean initializeServer, 315 PrintStream out, 316 PrintStream err) { 317 318 319 // Perform the initial bootstrap of the Directory Server and process the 320 // configuration. 321 DirectoryServer directoryServer = DirectoryServer.getInstance(); 322 if (initializeServer) 323 { 324 try 325 { 326 DirectoryServer.bootstrapClient(); 327 DirectoryServer.initializeJMX(); 328 } 329 catch (Exception e) 330 { 331 printWrappedText(err, ERR_SERVER_BOOTSTRAP_ERROR.get(getExceptionMessage(e))); 332 return 1; 333 } 334 335 try 336 { 337 directoryServer.initializeConfiguration(configClass.getValue(), 338 configFile.getValue()); 339 } 340 catch (InitializationException ie) 341 { 342 printWrappedText(err, ERR_CANNOT_LOAD_CONFIG.get(ie.getMessage())); 343 return 1; 344 } 345 catch (Exception e) 346 { 347 printWrappedText(err, ERR_CANNOT_LOAD_CONFIG.get(getExceptionMessage(e))); 348 return 1; 349 } 350 351 352 353 // Initialize the Directory Server schema elements. 354 try 355 { 356 directoryServer.initializeSchema(); 357 } 358 catch (ConfigException | InitializationException e) 359 { 360 printWrappedText(err, ERR_CANNOT_LOAD_SCHEMA.get(e.getMessage())); 361 return 1; 362 } 363 catch (Exception e) 364 { 365 printWrappedText(err, ERR_CANNOT_LOAD_SCHEMA.get(getExceptionMessage(e))); 366 return 1; 367 } 368 369 370 // Initialize the Directory Server core configuration. 371 try 372 { 373 CoreConfigManager coreConfigManager = new CoreConfigManager(directoryServer.getServerContext()); 374 coreConfigManager.initializeCoreConfig(); 375 } 376 catch (ConfigException | InitializationException e) 377 { 378 printWrappedText(err, ERR_CANNOT_INITIALIZE_CORE_CONFIG.get(e.getMessage())); 379 return 1; 380 } 381 catch (Exception e) 382 { 383 printWrappedText(err, ERR_CANNOT_INITIALIZE_CORE_CONFIG.get(getExceptionMessage(e))); 384 return 1; 385 } 386 387 388 // Initialize the Directory Server crypto manager. 389 try 390 { 391 directoryServer.initializeCryptoManager(); 392 } 393 catch (ConfigException | InitializationException e) 394 { 395 printWrappedText(err, ERR_CANNOT_INITIALIZE_CRYPTO_MANAGER.get(e.getMessage())); 396 return 1; 397 } 398 catch (Exception e) 399 { 400 printWrappedText(err, ERR_CANNOT_INITIALIZE_CRYPTO_MANAGER.get(getExceptionMessage(e))); 401 return 1; 402 } 403 404 405 try 406 { 407 ErrorLogPublisher errorLogPublisher = 408 TextErrorLogPublisher.getToolStartupTextErrorPublisher(new TextWriter.STREAM(out)); 409 ErrorLogger.getInstance().addLogPublisher(errorLogPublisher); 410 DebugLogger.getInstance().addPublisherIfRequired(new TextWriter.STREAM(out)); 411 } 412 catch(Exception e) 413 { 414 err.println("Error installing the custom error logger: " + 415 stackTraceToSingleLineString(e)); 416 } 417 } 418 419 420 // Open the backup directory and make sure it is valid. 421 BackupDirectory backupDir; 422 try 423 { 424 backupDir = BackupDirectory.readBackupDirectoryDescriptor( 425 backupDirectory.getValue()); 426 } 427 catch (Exception e) 428 { 429 logger.error(ERR_RESTOREDB_CANNOT_READ_BACKUP_DIRECTORY, backupDirectory.getValue(), getExceptionMessage(e)); 430 return 1; 431 } 432 433 434 // If we're just going to be listing backups, then do that now. 435 DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_LOCAL_TIME); 436 if (listBackups.isPresent()) 437 { 438 for (BackupInfo backupInfo : backupDir.getBackups().values()) 439 { 440 LocalizableMessage message = INFO_RESTOREDB_LIST_BACKUP_ID.get( 441 backupInfo.getBackupID()); 442 out.println(message); 443 444 message = INFO_RESTOREDB_LIST_BACKUP_DATE.get( 445 dateFormat.format(backupInfo.getBackupDate())); 446 out.println(message); 447 448 message = INFO_RESTOREDB_LIST_INCREMENTAL.get(backupInfo.isIncremental()); 449 out.println(message); 450 451 message = INFO_RESTOREDB_LIST_COMPRESSED.get(backupInfo.isCompressed()); 452 out.println(message); 453 454 message = INFO_RESTOREDB_LIST_ENCRYPTED.get(backupInfo.isEncrypted()); 455 out.println(message); 456 457 byte[] hash = backupInfo.getUnsignedHash(); 458 459 message = INFO_RESTOREDB_LIST_HASHED.get(hash != null); 460 out.println(message); 461 462 byte[] signature = backupInfo.getSignedHash(); 463 464 message = INFO_RESTOREDB_LIST_SIGNED.get(signature != null); 465 out.println(message); 466 467 StringBuilder dependencyList = new StringBuilder(); 468 HashSet<String> dependencyIDs = backupInfo.getDependencies(); 469 if (! dependencyIDs.isEmpty()) 470 { 471 Iterator<String> iterator = dependencyIDs.iterator(); 472 dependencyList.append(iterator.next()); 473 474 while (iterator.hasNext()) 475 { 476 dependencyList.append(", "); 477 dependencyList.append(iterator.next()); 478 } 479 } 480 else 481 { 482 dependencyList.append("none"); 483 } 484 485 486 message = INFO_RESTOREDB_LIST_DEPENDENCIES.get(dependencyList); 487 out.println(message); 488 out.println(); 489 } 490 491 return 0; 492 } 493 494 495 // If a backup ID was specified, then make sure it is valid. If none was 496 // provided, then choose the latest backup from the archive. Encrypted 497 // or signed backups cannot be restored to a local (offline) server 498 // instance. 499 String backupID; 500 { 501 BackupInfo backupInfo = backupDir.getLatestBackup(); 502 if (backupInfo == null) 503 { 504 logger.error(ERR_RESTOREDB_NO_BACKUPS_IN_DIRECTORY, backupDirectory.getValue()); 505 return 1; 506 } 507 backupID = backupInfo.getBackupID(); 508 if (backupIDString.isPresent()) 509 { 510 backupID = backupIDString.getValue(); 511 backupInfo = backupDir.getBackupInfo(backupID); 512 if (backupInfo == null) 513 { 514 logger.error(ERR_RESTOREDB_INVALID_BACKUP_ID, backupID, backupDirectory.getValue()); 515 return 1; 516 } 517 } 518 if (backupInfo.isEncrypted() || null != backupInfo.getSignedHash()) { 519 logger.error(ERR_RESTOREDB_ENCRYPT_OR_SIGN_REQUIRES_ONLINE); 520 return 1; 521 } 522 } 523 524 525 // Get the DN of the backend configuration entry from the backup and load 526 // the associated backend from the configuration. 527 DN configEntryDN = backupDir.getConfigEntryDN(); 528 529 530 // Get information about the backends defined in the server and determine 531 // which to use for the restore. 532 ArrayList<Backend> backendList = new ArrayList<>(); 533 ArrayList<BackendCfg> entryList = new ArrayList<>(); 534 ArrayList<List<DN>> dnList = new ArrayList<>(); 535 BackendToolUtils.getBackends(backendList, entryList, dnList); 536 537 538 Backend backend = null; 539 int numBackends = backendList.size(); 540 for (int i=0; i < numBackends; i++) 541 { 542 Backend b = backendList.get(i); 543 BackendCfg e = entryList.get(i); 544 if (e.dn().equals(configEntryDN)) 545 { 546 backend = b; 547 break; 548 } 549 } 550 551 if (backend == null) 552 { 553 logger.error(ERR_RESTOREDB_NO_BACKENDS_FOR_DN, backupDirectory.getValue(), configEntryDN); 554 return 1; 555 } 556 else if (!backend.supports(BackendOperation.RESTORE)) 557 { 558 logger.error(ERR_RESTOREDB_CANNOT_RESTORE, backend.getBackendID()); 559 return 1; 560 } 561 562 563 // Create the restore config object from the information available. 564 RestoreConfig restoreConfig = new RestoreConfig(backupDir, backupID, 565 verifyOnly.isPresent()); 566 567 568 // Acquire an exclusive lock for the backend. 569 try 570 { 571 String lockFile = LockFileManager.getBackendLockFileName(backend); 572 StringBuilder failureReason = new StringBuilder(); 573 if (! LockFileManager.acquireExclusiveLock(lockFile, failureReason)) 574 { 575 logger.error(ERR_RESTOREDB_CANNOT_LOCK_BACKEND, backend.getBackendID(), failureReason); 576 return 1; 577 } 578 } 579 catch (Exception e) 580 { 581 logger.error(ERR_RESTOREDB_CANNOT_LOCK_BACKEND, backend.getBackendID(), getExceptionMessage(e)); 582 return 1; 583 } 584 585 586 // Perform the restore. 587 try 588 { 589 backend.restoreBackup(restoreConfig); 590 } 591 catch (DirectoryException de) 592 { 593 logger.error(ERR_RESTOREDB_ERROR_DURING_BACKUP, backupID, backupDir.getPath(), de.getMessageObject()); 594 } 595 catch (Exception e) 596 { 597 logger.error(ERR_RESTOREDB_ERROR_DURING_BACKUP, backupID, backupDir.getPath(), getExceptionMessage(e)); 598 } 599 600 601 // Release the exclusive lock on the backend. 602 try 603 { 604 String lockFile = LockFileManager.getBackendLockFileName(backend); 605 StringBuilder failureReason = new StringBuilder(); 606 if (! LockFileManager.releaseLock(lockFile, failureReason)) 607 { 608 logger.warn(WARN_RESTOREDB_CANNOT_UNLOCK_BACKEND, backend.getBackendID(), failureReason); 609 } 610 } 611 catch (Exception e) 612 { 613 logger.warn(WARN_RESTOREDB_CANNOT_UNLOCK_BACKEND, backend.getBackendID(), getExceptionMessage(e)); 614 } 615 return 0; 616 } 617 618 /** {@inheritDoc} */ 619 @Override 620 public String getTaskId() { 621 return backupIDString != null? backupIDString.getValue() : null; 622 } 623}