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