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"); }