Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,15 @@
all the property parameters is available in the example.properties
file of Berkeley DB Java Edition distribution.
</adm:description>
<adm:requires-admin-action>
<adm:none>
<adm:synopsis>
A change of a property which Berkeley DB Java Edition does not
accept while the environment runs takes effect when the backend
is restarted, and the change result says so.
</adm:synopsis>
</adm:none>
</adm:requires-admin-action>
<adm:default-behavior>
<adm:undefined />
</adm:default-behavior>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@
disk, but also potentially causes recovery from an abrupt termination
(crash) to take more time.
</adm:description>
<adm:requires-admin-action>
<adm:component-restart />
</adm:requires-admin-action>
<adm:default-behavior>
<adm:defined>
<adm:value>15s</adm:value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,25 @@ private static EnvironmentConfig defaultConfig()
static EnvironmentConfig parseConfigEntry(JEBackendCfg cfg) throws ConfigException
{
validateDbCacheSize(cfg.getDBCacheSize());
final EnvironmentConfig envConfig = toEnvironmentConfig(cfg);
// The JE loggers are shared by every environment of the JVM: their level is set by the open, not
// built into the configuration of one environment.
Logger.getLogger("com.sleepycat.je").setLevel(parseLoggingLevel(cfg.getDBLoggingLevel(), cfg.dn()));
return envConfig;
}

/**
* Build the environment configuration the given configuration describes, and nothing else: no
* check of the cache size against the memory quota, no level set on the JE loggers. What a
* configuration change is checked as, applied to a running environment and held against, is
* built here.
*
* @param cfg The configuration to be parsed.
* @return An environment config instance corresponding to the configuration.
* @throws ConfigException If there is an error in the provided configuration.
*/
static EnvironmentConfig toEnvironmentConfig(JEBackendCfg cfg) throws ConfigException
{
EnvironmentConfig envConfig = defaultConfig();
setDurability(envConfig, cfg.isDBTxnNoSync(), cfg.isDBTxnWriteNoSync());
setJEProperties(cfg, envConfig, cfg.dn().rdn().getFirstAVA().getAttributeValue());
Expand All @@ -389,6 +407,19 @@ static EnvironmentConfig parseConfigEntry(JEBackendCfg cfg) throws ConfigExcepti
return setJEProperties(envConfig, cfg.getJEProperty(), attrMap);
}

/**
* Get the name a JE property is configured under: the property of the backend configuration
* which is mapped to it, or the JE property's own name when it is set through je-property alone.
*
* @param jeProperty The JE property name.
* @return The name the operator changes it by.
*/
static String configuredNameOf(String jeProperty)
{
final String attrName = attrMap.get(jeProperty);
return attrName != null ? attrName.substring(ConfigConstants.NAME_PREFIX_CFG.length()) : jeProperty;
}

private static void validateDbCacheSize(long dbCacheSize) throws ConfigException
{
if (dbCacheSize != 0)
Expand Down Expand Up @@ -430,6 +461,12 @@ else if (dbTxnWriteNoSync)
{
envConfig.setDurability(Durability.COMMIT_WRITE_NO_SYNC);
}
else
{
// What JE falls back on when a configuration sets none - but set, so that a change back from
// either flag replaces the durability the environment runs with rather than leaving it be.
envConfig.setDurability(Durability.COMMIT_SYNC);
}
}

private static void setJEProperties(BackendCfg cfg, EnvironmentConfig envConfig, ByteString backendId)
Expand All @@ -447,18 +484,22 @@ private static void setJEProperties(BackendCfg cfg, EnvironmentConfig envConfig,
private static void setDBLoggingLevel(EnvironmentConfig envConfig, String loggingLevel, DN dn,
boolean loggingFileHandlerOn) throws ConfigException
{
Logger parent = Logger.getLogger("com.sleepycat.je");
// Refused as a whole here; the level itself is set on the JE loggers by the open.
parseLoggingLevel(loggingLevel, dn);
final Level level = loggingFileHandlerOn ? Level.ALL : Level.OFF;
envConfig.setConfigParam(FILE_LOGGING_LEVEL, level.getName());
}

private static Level parseLoggingLevel(String loggingLevel, DN dn) throws ConfigException
{
try
{
parent.setLevel(Level.parse(loggingLevel));
return Level.parse(loggingLevel);
}
catch (Exception e)
{
throw new ConfigException(ERR_JEB_INVALID_LOGGING_LEVEL.get(loggingLevel, dn));
}

final Level level = loggingFileHandlerOn ? Level.ALL : Level.OFF;
envConfig.setConfigParam(FILE_LOGGING_LEVEL, level.getName());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
Expand All @@ -52,6 +53,7 @@
import org.forgerock.opendj.config.server.ConfigException;
import org.forgerock.opendj.ldap.ByteSequence;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.util.Reject;
import org.forgerock.opendj.config.server.ConfigurationChangeListener;
import org.forgerock.opendj.server.config.server.JEBackendCfg;
Expand Down Expand Up @@ -95,6 +97,8 @@
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.TransactionConfig;
import com.sleepycat.je.config.ConfigParam;
import com.sleepycat.je.config.EnvironmentParams;

/** Berkeley DB Java Edition (JE for short) database implementation of the {@link Storage} engine. */
public final class JEStorage implements Storage, Backupable, ConfigurationChangeListener<JEBackendCfg>,
Expand Down Expand Up @@ -1209,7 +1213,9 @@ public boolean supportsBackupAndRestore()
@Override
public File getDirectory()
{
return getBackendDirectory(config);
// The directory the storage runs on: a db-directory moved while it runs is used from the next open,
// which a new storage makes.
return backendDirectory;
}

private static File getBackendDirectory(JEBackendCfg cfg)
Expand Down Expand Up @@ -1470,7 +1476,8 @@ public boolean isConfigurationChangeAcceptable(JEBackendCfg newCfg,
final MemoryQuota quota = serverContext.getMemoryQuota();
return (newSize <= Math.max(reservedCacheSize, computeSize(config))
|| quota.isMemoryAvailable(newSize - reservedCacheSize))
&& checkConfigurationDirectories(newCfg, unacceptableReasons);
&& checkConfigurationDirectories(newCfg, unacceptableReasons)
&& checkEnvironmentConfiguration(newCfg, unacceptableReasons);
}

private long computeSize(JEBackendCfg cfg)
Expand Down Expand Up @@ -1506,7 +1513,28 @@ else if (!memQuota.isMemoryAvailable(memQuota.memPercentToBytes(cfg.getDBCachePe
return false;
}
}
return checkConfigurationDirectories(cfg, unacceptableReasons);
return checkConfigurationDirectories(cfg, unacceptableReasons)
&& checkEnvironmentConfiguration(cfg, unacceptableReasons);
}

/**
* Whether an environment can be configured from the given configuration. A durability which
* sets both flags, or a native property JE does not know, is refused here, before the change
* is written - rather than by the next open of the backend, which is where a configuration
* nothing checked used to fail.
*/
private static boolean checkEnvironmentConfiguration(JEBackendCfg cfg, List<LocalizableMessage> unacceptableReasons)
{
try
{
ConfigurableEnvironment.toEnvironmentConfig(cfg);
return true;
}
catch (ConfigException e)
{
unacceptableReasons.add(e.getMessageObject());
return false;
}
}

private static boolean checkConfigurationDirectories(JEBackendCfg cfg,
Expand All @@ -1533,9 +1561,12 @@ public ConfigChangeResult applyConfigurationChange(JEBackendCfg cfg)
try
{
File newBackendDirectory = getBackendDirectory(cfg);
// Against the directory the storage runs on rather than the configuration as last changed, so
// that a later change still asks for the restart a move is waiting for.
final boolean moved = !newBackendDirectory.equals(backendDirectory);

// Create the directory if it doesn't exist.
if (!cfg.getDBDirectory().equals(config.getDBDirectory()))
if (moved)
{
checkDBDirExistsOrCanCreate(newBackendDirectory, ccr, false);
if (!ccr.getMessages().isEmpty())
Expand All @@ -1544,20 +1575,22 @@ public ConfigChangeResult applyConfigurationChange(JEBackendCfg cfg)
}

ccr.setAdminActionRequired(true);
ccr.addMessage(NOTE_CONFIG_DB_DIR_REQUIRES_RESTART.get(config.getDBDirectory(), cfg.getDBDirectory()));
ccr.addMessage(NOTE_CONFIG_DB_DIR_REQUIRES_RESTART.get(backendDirectory, newBackendDirectory));
}

if (!cfg.getDBDirectoryPermissions().equalsIgnoreCase(config.getDBDirectoryPermissions())
|| !cfg.getDBDirectory().equals(config.getDBDirectory()))
|| moved)
{
checkDBDirPermissions(cfg.getDBDirectoryPermissions(), cfg.dn(), ccr);
if (!ccr.getMessages().isEmpty())
// By its result code: the note of a moved directory is in the result already, and the rest of
// the change is still applied and reported alongside it.
if (ccr.getResultCode() != ResultCode.SUCCESS)
{
return ccr;
}

setDBDirPermissions(newBackendDirectory, cfg.getDBDirectoryPermissions(), cfg.dn(), ccr);
if (!ccr.getMessages().isEmpty())
if (ccr.getResultCode() != ResultCode.SUCCESS)
{
return ccr;
}
Expand All @@ -1572,6 +1605,12 @@ public ConfigChangeResult applyConfigurationChange(JEBackendCfg cfg)
ccr.addMessage(
NOTE_CONFIG_DB_CACHE_REQUIRES_RESTART.get(cfg.getBackendId(), configuredCacheSize, newCacheSize));
}
// An import runs the environment on a configuration of its own, which goes with it: the backend
// opens again on the configuration as changed once the import is over.
if (env != null && envConfig.getTransactional())
{
applyToEnvironment(cfg, ccr);
}
registerMonitoredDirectory(cfg);
config = cfg;
}
Expand All @@ -1582,6 +1621,75 @@ public ConfigChangeResult applyConfigurationChange(JEBackendCfg cfg)
return ccr;
}

/**
* Applies to the running environment what JE takes while it runs, and asks for a restart for what
* it takes at the open alone. The environment is configured when it opens, from the configuration
* as it is then: a change of a property JE accepts as mutable is handed to the environment here,
* and a change of one it does not is reported - the change result reaches the error log, where a
* change reported as applied while the environment ran on unchanged until its next open did not.
* <p>
* The cache is the one mutable setting left where the open put it: it is sized with the memory
* reserved for it, and a change of its size asks for a restart above, at which the next open
* reserves the new size.
*/
private void applyToEnvironment(JEBackendCfg cfg, ConfigChangeResult ccr) throws ConfigException
{
final EnvironmentConfig next = ConfigurableEnvironment.toEnvironmentConfig(cfg);
final EnvironmentConfig running = env.getConfig();
for (ConfigParam param : new TreeMap<>(EnvironmentParams.SUPPORTED_PARAMS).values())
{
// Replication parameters are not set through an environment configuration; a multi-value
// parameter is not read as one value. Neither is set by this storage.
if (param.isForReplication() || param.isMultiValueParam())
{
continue;
}
final String runningValue = running.getConfigParam(param.getName());
final String nextValue = next.getConfigParam(param.getName());
if (Objects.equals(runningValue, nextValue)
|| (param.isMutable() && (next.isConfigParamSet(param.getName())
|| resetsToDefault(next, param.getName(), nextValue))))
{
continue;
}
ccr.setAdminActionRequired(true);
ccr.addMessage(NOTE_CONFIG_DB_PROPERTY_REQUIRES_RESTART.get(
ConfigurableEnvironment.configuredNameOf(param.getName()), cfg.getBackendId(), runningValue, nextValue));
}
next.setConfigParam(MAX_MEMORY, running.getConfigParam(MAX_MEMORY));
next.setConfigParam(MAX_MEMORY_PERCENT, running.getConfigParam(MAX_MEMORY_PERCENT));
// What JE takes while it runs, of the properties the configuration sets; the rest it ignores.
env.setMutableConfig(next);
}

/**
* Sets a mutable parameter the configuration no longer sets - a je-property removed - to JE's
* default, since the environment keeps the value it runs with of every parameter it is not handed.
* A default JE does not take as a value, such as the 0 of je.cleaner.readSize, which JE reads as
* "computed at the open", leaves the parameter to the next open.
*
* @param next the environment configuration handed to the running environment
* @param name the name of the parameter
* @param defaultValue JE's default of the parameter, as the configuration reads it
* @return whether the default is handed to the environment along with the rest
*/
private static boolean resetsToDefault(EnvironmentConfig next, String name, String defaultValue)
{
if (defaultValue == null)
{
return false;
}
try
{
next.setConfigParam(name, defaultValue);
return true;
}
catch (IllegalArgumentException e)
{
return false;
}
}

private void registerMonitoredDirectory(JEBackendCfg cfg)
{
diskMonitor.registerMonitoredDirectory(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
import org.forgerock.opendj.config.server.ConfigurationChangeListener;
import org.forgerock.opendj.ldap.ByteSequence;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.server.config.meta.PDBBackendCfgDefn;
import org.forgerock.opendj.server.config.server.PDBBackendCfg;
import org.forgerock.util.Reject;
import org.opends.server.api.Backupable;
Expand Down Expand Up @@ -1335,7 +1337,9 @@ public boolean supportsBackupAndRestore()
@Override
public File getDirectory()
{
return getBackendDirectory(config);
// The directory the storage runs on: a db-directory moved while it runs is used from the next open,
// which a new storage makes.
return backendDirectory;
}

private static File getBackendDirectory(PDBBackendCfg cfg)
Expand Down Expand Up @@ -1621,9 +1625,12 @@ public ConfigChangeResult applyConfigurationChange(PDBBackendCfg cfg)
try
{
File newBackendDirectory = getBackendDirectory(cfg);
// Against the directory the storage runs on rather than the configuration as last changed, so
// that a later change still asks for the restart a move is waiting for.
final boolean moved = !newBackendDirectory.equals(backendDirectory);

// Create the directory if it doesn't exist.
if(!cfg.getDBDirectory().equals(config.getDBDirectory()))
if (moved)
{
checkDBDirExistsOrCanCreate(newBackendDirectory, ccr, false);
if (!ccr.getMessages().isEmpty())
Expand All @@ -1632,20 +1639,22 @@ public ConfigChangeResult applyConfigurationChange(PDBBackendCfg cfg)
}

ccr.setAdminActionRequired(true);
ccr.addMessage(NOTE_CONFIG_DB_DIR_REQUIRES_RESTART.get(config.getDBDirectory(), cfg.getDBDirectory()));
ccr.addMessage(NOTE_CONFIG_DB_DIR_REQUIRES_RESTART.get(backendDirectory, newBackendDirectory));
}

if (!cfg.getDBDirectoryPermissions().equalsIgnoreCase(config.getDBDirectoryPermissions())
|| !cfg.getDBDirectory().equals(config.getDBDirectory()))
|| moved)
{
checkDBDirPermissions(cfg.getDBDirectoryPermissions(), cfg.dn(), ccr);
if (!ccr.getMessages().isEmpty())
// By its result code: the note of a moved directory is in the result already, and the rest of
// the change is still applied and reported alongside it.
if (ccr.getResultCode() != ResultCode.SUCCESS)
{
return ccr;
}

setDBDirPermissions(newBackendDirectory, cfg.getDBDirectoryPermissions(), cfg.dn(), ccr);
if (!ccr.getMessages().isEmpty())
if (ccr.getResultCode() != ResultCode.SUCCESS)
{
return ccr;
}
Expand All @@ -1660,6 +1669,15 @@ public ConfigChangeResult applyConfigurationChange(PDBBackendCfg cfg)
ccr.addMessage(
NOTE_CONFIG_DB_CACHE_REQUIRES_RESTART.get(cfg.getBackendId(), configuredCacheSize, newCacheSize));
}
if (db != null && cfg.getDBCheckpointerWakeupInterval() != db.getConfiguration().getCheckpointInterval())
{
// The checkpoint interval is set on the PersistIt configuration when the database opens, and
// PersistIt takes no configuration once one is set: the next open of the backend applies it.
ccr.setAdminActionRequired(true);
ccr.addMessage(NOTE_CONFIG_DB_PROPERTY_REQUIRES_RESTART.get(
PDBBackendCfgDefn.getInstance().getDBCheckpointerWakeupIntervalPropertyDefinition().getName(),
cfg.getBackendId(), db.getConfiguration().getCheckpointInterval(), cfg.getDBCheckpointerWakeupInterval()));
}
registerMonitoredDirectory(cfg);
config = cfg;
commitPolicy = config.isDBTxnNoSync() ? SOFT : GROUP;
Expand Down
Loading
Loading