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