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