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