mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

neil_a_wilson
02.12.2007 3af39927fd1bba93a2769cd5d6bc7b6c5f924c64
Update the server so that it has the ability to save a copy of its current
configuration into a ".startok" file whenever it starts successfully. The
start-ds script and DirectoryEnvironmentConfig class have been updated to
expose an option to try to start the server using this "last known good"
configuration rather than the active config file.

OpenDS Issue Number: 1945
12 files modified
511 ■■■■■ changed files
opends/resource/config/config.ldif 1 ●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 7 ●●●● patch | view | raw | blame | history
opends/src/admin/defn/org/opends/server/admin/std/GlobalConfiguration.xml 26 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/config.properties 17 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/core.properties 3 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/ConfigHandler.java 20 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/CoreConfigManager.java 3 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DirectoryServer.java 71 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/ConfigFileHandler.java 268 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/DirectoryEnvironmentConfig.java 71 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/ServerConstants.java 10 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/ConfigFileHandlerTestCase.java 14 ●●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -48,6 +48,7 @@
ds-cfg-default-password-policy: cn=Default Password Policy,cn=Password Policies,cn=config
ds-cfg-return-bind-error-messages: false
ds-cfg-idle-time-limit: 0 seconds
ds-cfg-save-config-on-successful-startup: true
ds-cfg-allowed-task: org.opends.server.tasks.AddSchemaFileTask
ds-cfg-allowed-task: org.opends.server.tasks.BackupTask
ds-cfg-allowed-task: org.opends.server.tasks.DisconnectClientTask
opends/resource/schema/02-config.ldif
@@ -1611,6 +1611,10 @@
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.478
  NAME 'ds-cfg-message-template-file' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.485
  NAME 'ds-cfg-save-config-on-successful-startup'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
  NAME 'ds-cfg-access-control-handler' SUP top STRUCTURAL
  MUST ( cn $ ds-cfg-acl-handler-class $ ds-cfg-acl-handler-enabled )
@@ -1830,7 +1834,8 @@
  ds-cfg-reject-unauthenticated-requests  $
  ds-cfg-bind-with-dn-requires-password $ ds-cfg-lookthrough-limit $
  ds-cfg-smtp-server $ ds-cfg-allowed-task $ ds-cfg-disabled-privilege $
  ds-cfg-return-bind-error-messages $ ds-cfg-idle-time-limit )
  ds-cfg-return-bind-error-messages $ ds-cfg-idle-time-limit $
  ds-cfg-save-config-on-successful-startup )
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.41 NAME 'ds-cfg-root-dn' SUP top
  AUXILIARY MAY ds-cfg-alternate-bind-dn X-ORIGIN 'OpenDS Directory Server' )
opends/src/admin/defn/org/opends/server/admin/std/GlobalConfiguration.xml
@@ -702,7 +702,7 @@
  <adm:property name="idle-time-limit" mandatory="false" multi-valued="false">
    <adm:synopsis>
      Specifies the maximum lenght of time that a client connection may remain
      Specifies the maximum length of time that a client connection may remain
      established since its last completed operation.  A value of "0 seconds"
      indicates that no idle time limit will be enforced.
    </adm:synopsis>
@@ -722,5 +722,29 @@
    </adm:profile>
  </adm:property>
  <adm:property name="save-config-on-successful-startup" mandatory="false">
    <adm:synopsis>
      Indicates whether the Directory Server should save a copy of its
      configuration whenever the startup process completes successfully.  This
      can ensure that the server provides a "last known good" configuration,
      which can be used as a reference (or copied into the active config) if the
      server fails to start with the current "active" configuration.
    </adm:synopsis>
    <adm:default-behavior>
      <adm:defined>
        <adm:value>true</adm:value>
      </adm:defined>
    </adm:default-behavior>
    <adm:syntax>
      <adm:boolean />
    </adm:syntax>
    <adm:profile name="ldap">
      <ldap:attribute>
        <ldap:oid>1.3.6.1.4.1.26027.1.1.485</ldap:oid>
        <ldap:name>ds-cfg-save-config-on-successful-startup</ldap:name>
      </ldap:attribute>
    </adm:profile>
  </adm:property>
</adm:managed-object>
opends/src/messages/messages/config.properties
@@ -2131,3 +2131,20 @@
 '%s' is invalid.  An SMTP server value must have an IP address or a \
 resolvable name, and it may optionally be followed by a colon and an integer \
 value between 1 and 65535 to specify the server port number
SEVERE_ERR_STARTOK_CANNOT_OPEN_FOR_READING_698=An error occurred while \
 attempting to open the current configuration file %s for reading in order to \
 copy it to the ".startok" file:  %s
SEVERE_ERR_STARTOK_CANNOT_OPEN_FOR_WRITING_699=An error occurred while \
 attempting to open file %s in order to write the ".startok" configuration \
 file:  %s
SEVERE_ERR_STARTOK_CANNOT_WRITE_700=An error occurred while attempting to \
 copy the current configuration from file %s into temporary file %s for use \
 as the ".startok" configuration file:  %s
SEVERE_ERR_STARTOK_CANNOT_RENAME_701=An error occurred while attempting to \
 rename file %s to %s for use as the ".startok" configuration file:  %s
NOTICE_CONFIG_FILE_USING_STARTOK_FILE_702=The Directory Server is starting \
 using the last known good configuration file %s rather than the active \
 configuration file %s
SEVERE_WARN_CONFIG_FILE_NO_STARTOK_FILE_703=No last known good configuration \
 file %s exists.  The server will attempt to start using the active \
 configuration file %s
opends/src/messages/messages/core.properties
@@ -1652,3 +1652,6 @@
 store backend %s is not enabled
SEVERE_ERR_CRYPTOMGR_ADS_TRUST_STORE_BACKEND_WRONG_CLASS_651=The backend %s \
 is not a trust store backend
INFO_DSCORE_DESCRIPTION_LASTKNOWNGOODCFG_652=Attempt to start using the \
 configuration that was in place at the last successful startup (if it is \
 available) rather than using the current active configuration
opends/src/server/org/opends/server/api/ConfigHandler.java
@@ -132,5 +132,25 @@
   */
  public abstract void writeUpdatedConfig()
         throws DirectoryException;
  /**
   * Indicates that the Directory Server has started successfully and
   * that the configuration handler should save a copy of the current
   * configuration for use as a "last known good" reference.  Note
   * that this may not be possible with some kinds of configuration
   * repositories, so it should be a best effort attempt.
   * <BR><BR>
   * This method should only be called by the Directory Server itself
   * when the server has started successfully.  It should not be
   * invoked by any other component at any other time.
   */
  @org.opends.server.types.PublicAPI(
       stability=org.opends.server.types.StabilityLevel.VOLATILE,
       mayInstantiate=false,
       mayExtend=true,
       mayInvoke=false)
  public abstract void writeSuccessfulStartupConfig();
}
opends/src/server/org/opends/server/core/CoreConfigManager.java
@@ -337,6 +337,9 @@
         globalConfig.isReturnBindErrorMessages());
    DirectoryServer.setIdleTimeLimit(globalConfig.getIdleTimeLimit());
    DirectoryServer.setSaveConfigOnSuccessfulStartup(
         globalConfig.isSaveConfigOnSuccessfulStartup());
  }
opends/src/server/org/opends/server/core/DirectoryServer.java
@@ -359,6 +359,10 @@
  // been abandoned.
  private boolean notifyAbandonedOperations;
  // Indicates whether to save a copy of the configuration on successful
  // startup.
  private boolean saveConfigOnSuccessfulStartup;
  // Indicates whether the server is currently in the process of shutting down.
  private boolean shuttingDown;
@@ -1428,6 +1432,14 @@
      }
      // If we should write a copy of the config on successful startup, then do
      // so now.
      if (saveConfigOnSuccessfulStartup)
      {
        configHandler.writeSuccessfulStartupConfig();
      }
      // Mark the current time as the start time and indicate that the server is
      // now running.
      startUpTime  = System.currentTimeMillis();
@@ -7727,6 +7739,37 @@
  /**
   * Indicates whether the Directory Server should save a copy of its
   * configuration whenever it is started successfully.
   *
   * @return  {@code true} if the server should save a copy of its configuration
   *          whenever it is started successfully, or {@code false} if not.
   */
  public static boolean saveConfigOnSuccessfulStartup()
  {
    return directoryServer.saveConfigOnSuccessfulStartup;
  }
  /**
   * Specifies whether the Directory Server should save a copy of its
   * configuration whenever it is started successfully.
   *
   * @param  saveConfigOnSuccessfulStartup  Specifies whether the server should
   *                                        save a copy of its configuration
   *                                        whenever it is started successfully.
   */
  public static void setSaveConfigOnSuccessfulStartup(
                          boolean saveConfigOnSuccessfulStartup)
  {
    directoryServer.saveConfigOnSuccessfulStartup =
         saveConfigOnSuccessfulStartup;
  }
  /**
   * Registers the provided backup task listener with the Directory Server.
   *
   * @param  listener  The backup task listener to register with the Directory
@@ -8999,15 +9042,16 @@
  public static void main(String[] args)
  {
    // Define the arguments that may be provided to the server.
    BooleanArgument checkStartability = null;
    BooleanArgument quietMode         = null;
    BooleanArgument windowsNetStart   = null;
    BooleanArgument displayUsage      = null;
    BooleanArgument fullVersion       = null;
    BooleanArgument noDetach          = null;
    BooleanArgument systemInfo        = null;
    StringArgument  configClass       = null;
    StringArgument  configFile        = null;
    BooleanArgument checkStartability      = null;
    BooleanArgument quietMode              = null;
    BooleanArgument windowsNetStart        = null;
    BooleanArgument displayUsage           = null;
    BooleanArgument fullVersion            = null;
    BooleanArgument noDetach               = null;
    BooleanArgument systemInfo             = null;
    BooleanArgument useLastKnownGoodConfig = null;
    StringArgument  configClass            = null;
    StringArgument  configFile             = null;
    // Create the command-line argument parser for use with this program.
@@ -9064,6 +9108,13 @@
      argParser.addArgument(systemInfo);
      useLastKnownGoodConfig =
           new BooleanArgument("lastknowngoodconfig", 'L',
                               "useLastKnownGoodConfig",
                               INFO_DSCORE_DESCRIPTION_LASTKNOWNGOODCFG.get());
      argParser.addArgument(useLastKnownGoodConfig);
      noDetach = new BooleanArgument("nodetach", 'N', "nodetach",
                                     INFO_DSCORE_DESCRIPTION_NODETACH.get());
      argParser.addArgument(noDetach);
@@ -9389,6 +9440,8 @@
                                    configClass.getValue());
      environmentConfig.setProperty(PROPERTY_CONFIG_FILE,
                                    configFile.getValue());
      environmentConfig.setProperty(PROPERTY_USE_LAST_KNOWN_GOOD_CONFIG,
           String.valueOf(useLastKnownGoodConfig.isPresent()));
      startupErrorLogPublisher =
opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
@@ -25,7 +25,6 @@
 *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 */
package org.opends.server.extensions;
import org.opends.messages.Message;
@@ -60,6 +59,7 @@
import javax.crypto.CipherOutputStream;
import javax.crypto.Mac;
import org.opends.messages.Message;
import org.opends.server.api.AlertGenerator;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.ConfigAddListener;
@@ -135,6 +135,9 @@
  // Indicates whether to maintain a configuration archive.
  private boolean maintainConfigArchive;
  // Indicates whether to start using the last known good configuration.
  private boolean useLastKnownGoodConfig;
  // A SHA-1 digest of the last known configuration.  This should only be
  // incorrect if the server configuration file has been manually edited with
  // the server online, which is a bad thing.
@@ -199,15 +202,41 @@
    configLock = new ReentrantLock();
    // Make sure that the configuration file exists.
    // Determine whether we should try to start using the last known good
    // configuration.  If so, then only do so if such a file exists.  If it
    // doesn't exist, then fall back on the active configuration file.
    this.configFile = configFile;
    File f = new File(configFile);
    DirectoryEnvironmentConfig envConfig =
         DirectoryServer.getEnvironmentConfig();
    useLastKnownGoodConfig = envConfig.useLastKnownGoodConfiguration();
    File f = null;
    if (useLastKnownGoodConfig)
    {
      f = new File(configFile + ".startok");
      if (! f.exists())
      {
        logError(WARN_CONFIG_FILE_NO_STARTOK_FILE.get(f.getAbsolutePath(),
                                                      configFile));
        useLastKnownGoodConfig = false;
        f = new File(configFile);
      }
      else
      {
        logError(NOTE_CONFIG_FILE_USING_STARTOK_FILE.get(f.getAbsolutePath(),
                                                         configFile));
      }
    }
    else
    {
      f = new File(configFile);
    }
    try
    {
      if (! f.exists())
      {
        Message message = ERR_CONFIG_FILE_DOES_NOT_EXIST.get(configFile);
        Message message = ERR_CONFIG_FILE_DOES_NOT_EXIST.get(
                               f.getAbsolutePath());
        throw new InitializationException(message);
      }
    }
@@ -228,7 +257,7 @@
      }
      Message message = ERR_CONFIG_FILE_CANNOT_VERIFY_EXISTENCE.get(
          configFile, String.valueOf(e));
                             f.getAbsolutePath(), String.valueOf(e));
      throw new InitializationException(message);
    }
@@ -236,11 +265,9 @@
    // Check to see if a configuration archive exists.  If not, then create one.
    // If so, then check whether the current configuration matches the last
    // configuration in the archive.  If it doesn't, then archive it.
    DirectoryEnvironmentConfig envConfig =
         DirectoryServer.getEnvironmentConfig();
    maintainConfigArchive = envConfig.maintainConfigArchive();
    maxConfigArchiveSize  = envConfig.getMaxConfigArchiveSize();
    if (maintainConfigArchive)
    if (maintainConfigArchive & (! useLastKnownGoodConfig))
    {
      try
      {
@@ -307,7 +334,7 @@
    LDIFReader reader;
    try
    {
      LDIFImportConfig importConfig = new LDIFImportConfig(configFile);
      LDIFImportConfig importConfig = new LDIFImportConfig(f.getAbsolutePath());
      // FIXME -- Should we support encryption or compression for the config?
@@ -321,7 +348,7 @@
      }
      Message message = ERR_CONFIG_FILE_CANNOT_OPEN_FOR_READ.get(
          configFile, String.valueOf(e));
                             f.getAbsolutePath(), String.valueOf(e));
      throw new InitializationException(message, e);
    }
@@ -352,7 +379,7 @@
      }
      Message message = ERR_CONFIG_FILE_INVALID_LDIF_ENTRY.get(
          le.getLineNumber(), configFile, String.valueOf(le));
          le.getLineNumber(), f.getAbsolutePath(), String.valueOf(le));
      throw new InitializationException(message, le);
    }
    catch (Exception e)
@@ -375,7 +402,8 @@
      }
      Message message =
          ERR_CONFIG_FILE_READ_ERROR.get(configFile, String.valueOf(e));
          ERR_CONFIG_FILE_READ_ERROR.get(f.getAbsolutePath(),
                                       String.valueOf(e));
      throw new InitializationException(message, e);
    }
@@ -395,7 +423,7 @@
        }
      }
      Message message = ERR_CONFIG_FILE_EMPTY.get(configFile);
      Message message = ERR_CONFIG_FILE_EMPTY.get(f.getAbsolutePath());
      throw new InitializationException(message);
    }
@@ -407,7 +435,8 @@
      if (! entry.getDN().equals(configRootDN))
      {
        Message message = ERR_CONFIG_FILE_INVALID_BASE_DN.get(
            configFile, entry.getDN().toString(), DN_CONFIG_ROOT);
                               f.getAbsolutePath(), entry.getDN().toString(),
                               DN_CONFIG_ROOT);
        throw new InitializationException(message);
      }
    }
@@ -452,8 +481,8 @@
      }
      // This should not happen, so we can use a generic error here.
      Message message =
          ERR_CONFIG_FILE_GENERIC_ERROR.get(configFile, String.valueOf(e));
      Message message = ERR_CONFIG_FILE_GENERIC_ERROR.get(f.getAbsolutePath(),
                                                          String.valueOf(e));
      throw new InitializationException(message, e);
    }
@@ -494,7 +523,8 @@
        }
        Message message = ERR_CONFIG_FILE_INVALID_LDIF_ENTRY.get(
            le.getLineNumber(), configFile, String.valueOf(le));
                               le.getLineNumber(), f.getAbsolutePath(),
                               String.valueOf(le));
        throw new InitializationException(message, le);
      }
      catch (Exception e)
@@ -516,8 +546,8 @@
          }
        }
        Message message =
            ERR_CONFIG_FILE_READ_ERROR.get(configFile, String.valueOf(e));
        Message message = ERR_CONFIG_FILE_READ_ERROR.get(f.getAbsolutePath(),
                                                         String.valueOf(e));
        throw new InitializationException(message, e);
      }
@@ -559,8 +589,9 @@
        }
        Message message = ERR_CONFIG_FILE_DUPLICATE_ENTRY.get(
            entryDN.toString(), String.valueOf(reader.getLastEntryLineNumber()),
                configFile);
                               entryDN.toString(),
                               String.valueOf(reader.getLastEntryLineNumber()),
                               f.getAbsolutePath());
        throw new InitializationException(message);
      }
@@ -582,7 +613,9 @@
        }
        Message message = ERR_CONFIG_FILE_UNKNOWN_PARENT.get(
            entryDN.toString(), reader.getLastEntryLineNumber(), configFile);
                               entryDN.toString(),
                               reader.getLastEntryLineNumber(),
                               f.getAbsolutePath());
        throw new InitializationException(message);
      }
@@ -601,9 +634,9 @@
          }
        }
        Message message = ERR_CONFIG_FILE_NO_PARENT.
            get(entryDN.toString(), reader.getLastEntryLineNumber(), configFile,
                parentDN.toString());
        Message message = ERR_CONFIG_FILE_NO_PARENT.get(entryDN.toString(),
                               reader.getLastEntryLineNumber(),
                               f.getAbsolutePath(), parentDN.toString());
        throw new InitializationException(message);
      }
@@ -636,8 +669,8 @@
          }
        }
        Message message =
            ERR_CONFIG_FILE_GENERIC_ERROR.get(configFile, String.valueOf(e));
        Message message = ERR_CONFIG_FILE_GENERIC_ERROR.get(f.getAbsolutePath(),
                                                            String.valueOf(e));
        throw new InitializationException(message, e);
      }
    }
@@ -651,7 +684,7 @@
    {
      try
      {
        File configDirFile = new File(configFile).getParentFile();
        File configDirFile = f.getParentFile();
        if ((configDirFile != null) &&
            configDirFile.getName().equals(CONFIG_DIR_NAME))
        {
@@ -2265,6 +2298,185 @@
  /**
   * {@inheritDoc}
   */
  public void writeSuccessfulStartupConfig()
  {
    if (useLastKnownGoodConfig)
    {
      // The server was started with the "last known good" configuration, so we
      // shouldn't overwrite it with something that is probably bad.
      return;
    }
    String startOKFilePath = configFile + ".startok";
    String tempFilePath    = startOKFilePath + ".tmp";
    String oldFilePath     = startOKFilePath + ".old";
    // Copy the current config file to a temporary file.
    File tempFile = new File(tempFilePath);
    FileInputStream inputStream = null;
    try
    {
      inputStream = new FileInputStream(configFile);
      FileOutputStream outputStream = null;
      try
      {
        outputStream = new FileOutputStream(tempFilePath, false);
        try
        {
          byte[] buffer = new byte[8192];
          while (true)
          {
            int bytesRead = inputStream.read(buffer);
            if (bytesRead < 0)
            {
              break;
            }
            outputStream.write(buffer, 0, bytesRead);
          }
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
          logError(ERR_STARTOK_CANNOT_WRITE.get(configFile, tempFilePath,
                                                getExceptionMessage(e)));
          return;
        }
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
        logError(ERR_STARTOK_CANNOT_OPEN_FOR_WRITING.get(tempFilePath,
                      getExceptionMessage(e)));
        return;
      }
      finally
      {
        try
        {
          outputStream.close();
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            TRACER.debugCaught(DebugLogLevel.ERROR, e);
          }
        }
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      logError(ERR_STARTOK_CANNOT_OPEN_FOR_READING.get(configFile,
                                                       getExceptionMessage(e)));
      return;
    }
    finally
    {
      try
      {
        inputStream.close();
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          TRACER.debugCaught(DebugLogLevel.ERROR, e);
        }
      }
    }
    // If a ".startok" file already exists, then move it to an ".old" file.
    File oldFile = new File(oldFilePath);
    try
    {
      if (oldFile.exists())
      {
        oldFile.delete();
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
    File startOKFile = new File(startOKFilePath);
    try
    {
      if (startOKFile.exists())
      {
        startOKFile.renameTo(oldFile);
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
    // Rename the temp file to the ".startok" file.
    try
    {
      tempFile.renameTo(startOKFile);
    } catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
      logError(ERR_STARTOK_CANNOT_RENAME.get(tempFilePath, startOKFilePath,
                                             getExceptionMessage(e)));
      return;
    }
    // Remove the ".old" file if there is one.
    try
    {
      if (oldFile.exists())
      {
        oldFile.delete();
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }
  }
  /**
   * Retrieves the OIDs of the controls that may be supported by this backend.
   *
   * @return  The OIDs of the controls that may be supported by this backend.
opends/src/server/org/opends/server/types/DirectoryEnvironmentConfig.java
@@ -470,6 +470,77 @@
  /**
   * Indicates whether the Directory Server should attempt to start
   * with the "last known good" configuration rather than the current
   * active configuration file.  Note that if there is no "last known
   * good" configuration file available, then the server should try to
   * start using the current, active configuration file.  If no
   * explicit value is defined, then a default result of {@code false}
   * will be returned.
   *
   * @return  {@code true} if the Directory Server should attempt to
   *          start using the "last known good" configuration, or
   *          {@code false} if it should try to start using the
   *          active configuration.
   */
  public boolean useLastKnownGoodConfiguration()
  {
    String useLastKnownGoodStr =
         getProperty(PROPERTY_USE_LAST_KNOWN_GOOD_CONFIG);
    if (useLastKnownGoodStr == null)
    {
      return false;
    }
    return useLastKnownGoodStr.equalsIgnoreCase("true");
  }
  /**
   * Specifies whether the Directory Server should attempt to start
   * using the last known good configuration rather than the
   * current active configuration.
   *
   * @param  useLastKnownGoodConfiguration  Indicates whether the
   *                                        Directory Server should
   *                                        attempt to start using the
   *                                        last known good
   *                                        configuration.
   *
   * @return  The previous setting for this configuration option.  If
   *          no previous value was specified, then {@code false} will
   *          be returned.
   *
   * @throws  InitializationException  If the Directory Server is
   *                                   already running.
   */
  public boolean setUseLastKnownGoodConfiguration(
                      boolean useLastKnownGoodConfiguration)
         throws InitializationException
  {
    if (DirectoryServer.isRunning())
    {
      throw new InitializationException(
              ERR_DIRCFG_SERVER_ALREADY_RUNNING.get());
    }
    String oldUseLastKnownGoodStr =
         setProperty(PROPERTY_USE_LAST_KNOWN_GOOD_CONFIG,
                     String.valueOf(useLastKnownGoodConfiguration));
    if (oldUseLastKnownGoodStr == null)
    {
      return false;
    }
    else
    {
      return oldUseLastKnownGoodStr.equalsIgnoreCase("true");
    }
  }
  /**
   * Indicates whether the Directory Server should maintain an archive
   * of previous configurations.  If no explicit value is defined,
   * then a default result of {@code true} will be returned.
opends/src/server/org/opends/server/util/ServerConstants.java
@@ -2612,6 +2612,16 @@
  /**
   * The name of the system property that can be used to indicate that the
   * Directory Server should attempt to start using the last known good
   * configuration, rather than the current active configuration.
   */
  public static final String PROPERTY_USE_LAST_KNOWN_GOOD_CONFIG =
       "org.opends.server.UseLastKnownGoodConfiguration";
  /**
   * The column at which to wrap long lines of output in the command-line tools.
   */
  public static final int MAX_LINE_WIDTH;
opends/tests/unit-tests-testng/src/server/org/opends/server/extensions/ConfigFileHandlerTestCase.java
@@ -28,6 +28,7 @@
import java.io.File;
import java.util.ArrayList;
import org.testng.annotations.BeforeClass;
@@ -60,6 +61,19 @@
         throws Exception
  {
    TestCaseUtils.startServer();
    String buildRoot = System.getProperty(TestCaseUtils.PROPERTY_BUILD_ROOT);
    String startOKFile = buildRoot + File.separator + "build" + File.separator +
                         "unit-tests" + File.separator + "package" +
                         File.separator + "config" + File.separator +
                         "config.ldif.startok";
    assertTrue(new File(startOKFile).exists(),
               startOKFile + " does not exist but it should");
    assertFalse(new File(startOKFile + ".tmp").exists(),
                startOKFile + ".tmp exists but should not");
    assertFalse(new File(startOKFile + ".old").exists(),
                startOKFile + ".old exists but should not");
  }