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