DiskSpaceMonitor.java revision a89f7014aeb71dba5c94404dfea7eb89e7eeee74
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at legal-notices/CDDLv1_0.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2010 Sun Microsystems, Inc.
* Portions Copyright 2014-2015 ForgeRock AS
*/
package org.opends.server.extensions;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.core.DirectoryServer.*;
import static org.opends.server.util.ServerConstants.ALERT_DESCRIPTION_DISK_FULL;
import static org.opends.server.util.ServerConstants.ALERT_DESCRIPTION_DISK_SPACE_LOW;
import static org.opends.server.util.ServerConstants.ALERT_TYPE_DISK_FULL;
import static org.opends.server.util.ServerConstants.ALERT_TYPE_DISK_SPACE_LOW;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.config.server.ConfigException;
import org.opends.server.admin.std.server.MonitorProviderCfg;
import org.opends.server.api.AlertGenerator;
import org.forgerock.opendj.ldap.schema.Syntax;
import org.opends.server.api.DiskSpaceMonitorHandler;
import org.opends.server.api.MonitorProvider;
import org.opends.server.api.ServerShutdownListener;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.Attributes;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.InitializationException;
/**
* This class provides an application-wide disk space monitoring service.
* It provides the ability for registered handlers to receive notifications
* when the free disk space falls below a certain threshold.
*
* The handler will only be notified once when when the free space
* have dropped below any of the thresholds. Once the "full" threshold
* have been reached, the handler will not be notified again until the
* free space raises above the "low" threshold.
*/
public class DiskSpaceMonitor extends MonitorProvider<MonitorProviderCfg> implements Runnable, AlertGenerator,
ServerShutdownListener
{
/**
* Helper class for each requestor for use with cn=monitor reporting and users of a spcific mountpoint.
*/
private class MonitoredDirectory extends MonitorProvider<MonitorProviderCfg>
{
private volatile File directory;
private volatile long lowThreshold;
private volatile long fullThreshold;
private final DiskSpaceMonitorHandler handler;
private final String instanceName;
private final String baseName;
private int lastState;
private MonitoredDirectory(File directory, String instanceName, String baseName, DiskSpaceMonitorHandler handler)
{
this.directory = directory;
this.instanceName = instanceName;
this.baseName = baseName;
this.handler = handler;
}
/** {@inheritDoc} */
@Override
public String getMonitorInstanceName() {
return instanceName + "," + "cn=" + baseName;
}
/** {@inheritDoc} */
@Override
public void initializeMonitorProvider(MonitorProviderCfg configuration)
throws ConfigException, InitializationException {
}
/** {@inheritDoc} */
@Override
public List<Attribute> getMonitorData() {
final List<Attribute> monitorAttrs = new ArrayList<>();
monitorAttrs.add(attr("disk-dir", getDefaultStringSyntax(), directory.getPath()));
monitorAttrs.add(attr("disk-free", getDefaultIntegerSyntax(), getFreeSpace()));
monitorAttrs.add(attr("disk-state", getDefaultStringSyntax(), getState()));
return monitorAttrs;
}
private File getDirectory() {
return directory;
}
private long getFreeSpace() {
return directory.getUsableSpace();
}
private long getFullThreshold() {
return fullThreshold;
}
private long getLowThreshold() {
return lowThreshold;
}
private void setFullThreshold(long fullThreshold) {
this.fullThreshold = fullThreshold;
}
private void setLowThreshold(long lowThreshold) {
this.lowThreshold = lowThreshold;
}
private Attribute attr(String name, Syntax syntax, Object value)
{
AttributeType attrType = DirectoryServer.getDefaultAttributeType(name, syntax);
return Attributes.create(attrType, String.valueOf(value));
}
private String getState()
{
switch(lastState)
{
case NORMAL:
return "normal";
case LOW:
return "low";
case FULL:
return "full";
default:
return null;
}
}
}
/**
* Helper class for building temporary list of handlers to notify on threshold hits.
* One object per directory per state will hold all the handlers matching directory and state.
*/
private class HandlerNotifier {
private File directory;
private int state;
/** printable list of handlers names, for reporting backend names in alert messages */
private final StringBuilder diskNames = new StringBuilder();
private final List<MonitoredDirectory> allHandlers = new ArrayList<>();
private HandlerNotifier(File directory, int state)
{
this.directory = directory;
this.state = state;
}
private void notifyHandlers()
{
for (MonitoredDirectory mdElem : allHandlers)
{
switch (state)
{
case FULL:
mdElem.handler.diskFullThresholdReached(mdElem.getDirectory(), mdElem.getFullThreshold());
break;
case LOW:
mdElem.handler.diskLowThresholdReached(mdElem.getDirectory(), mdElem.getLowThreshold());
break;
case NORMAL:
mdElem.handler.diskSpaceRestored(mdElem.getDirectory(), mdElem.getLowThreshold(),
mdElem.getFullThreshold());
break;
}
}
}
private boolean isEmpty()
{
return allHandlers.size() == 0;
}
private void addHandler(MonitoredDirectory handler)
{
logger.trace("State change: %d -> %d", handler.lastState, state);
handler.lastState = state;
if (handler.handler != null)
{
allHandlers.add(handler);
}
appendName(diskNames, handler.instanceName);
}
private void appendName(StringBuilder strNames, String strVal)
{
if (strNames.length() > 0)
{
strNames.append(", ");
}
strNames.append(strVal);
}
}
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
private static final int NORMAL = 0;
private static final int LOW = 1;
private static final int FULL = 2;
private static final String INSTANCENAME = "Disk Space Monitor";
private final HashMap<File, List<MonitoredDirectory>> monitoredDirs = new HashMap<>();
/**
* Constructs a new DiskSpaceMonitor that will notify registered DiskSpaceMonitorHandler objects when filesystems
* on which configured directories reside, fall below the provided thresholds.
*/
public DiskSpaceMonitor()
{
}
/**
* Starts periodic monitoring of all registered directories.
*/
public void startDiskSpaceMonitor()
{
DirectoryServer.registerMonitorProvider(this);
DirectoryServer.registerShutdownListener(this);
scheduleUpdate(this, 0, 5, TimeUnit.SECONDS);
}
/**
* Registers or reconfigures a directory for monitoring.
* If possible, we will try to get and use the mountpoint where the directory resides and monitor it instead.
* If the directory is already registered for the same <code>handler</code>, simply change its configuration.
* @param instanceName A name for the handler, as used by cn=monitor
* @param directory The directory to monitor
* @param lowThresholdBytes Disk slow threshold expressed in bytes
* @param fullThresholdBytes Disk full threshold expressed in bytes
* @param handler The class requesting to be called when a transition in disk space occurs
*/
public void registerMonitoredDirectory(String instanceName, File directory, long lowThresholdBytes,
long fullThresholdBytes, DiskSpaceMonitorHandler handler)
{
File fsMountPoint;
try
{
fsMountPoint = getMountPoint(directory);
}
catch (IOException ioe)
{
logger.warn(ERR_DISK_SPACE_GET_MOUNT_POINT, directory.getAbsolutePath(), ioe.getLocalizedMessage());
fsMountPoint = directory;
}
MonitoredDirectory newDSH = new MonitoredDirectory(directory, instanceName, INSTANCENAME, handler);
newDSH.setFullThreshold(fullThresholdBytes);
newDSH.setLowThreshold(lowThresholdBytes);
synchronized (monitoredDirs)
{
List<MonitoredDirectory> diskHelpers = monitoredDirs.get(fsMountPoint);
if (diskHelpers == null)
{
List<MonitoredDirectory> newList = new ArrayList<>();
newList.add(newDSH);
monitoredDirs.put(fsMountPoint, newList);
}
else
{
for (MonitoredDirectory elem : diskHelpers)
{
if (elem.handler.equals(handler) && elem.getDirectory().equals(directory))
{
elem.setFullThreshold(fullThresholdBytes);
elem.setLowThreshold(lowThresholdBytes);
return;
}
}
diskHelpers.add(newDSH);
}
DirectoryServer.registerMonitorProvider(newDSH);
}
}
private File getMountPoint(File directory) throws IOException
{
Path mountPoint = directory.getAbsoluteFile().toPath();
Path parentDir = mountPoint.getParent();
FileStore dirFileStore = Files.getFileStore(mountPoint);
/*
* Since there is no concept of mount point in the APIs, iterate on all parents of
* the given directory until the FileSystem Store changes (hint of a different
* device, hence a mount point) or we get to root, which works too.
*/
while (parentDir != null)
{
if (!Files.getFileStore(parentDir).equals(dirFileStore))
{
return mountPoint.toFile();
}
mountPoint = mountPoint.getParent();
parentDir = parentDir.getParent();
}
return mountPoint.toFile();
}
/**
* Removes a directory from the set of monitored directories.
*
* @param directory The directory to stop monitoring on
* @param handler The class that requested monitoring
*/
public void deregisterMonitoredDirectory(File directory, DiskSpaceMonitorHandler handler)
{
synchronized (monitoredDirs)
{
List<MonitoredDirectory> directories = monitoredDirs.get(directory);
if (directories != null)
{
Iterator<MonitoredDirectory> itr = directories.iterator();
while (itr.hasNext())
{
MonitoredDirectory curDirectory = itr.next();
if (curDirectory.handler.equals(handler))
{
DirectoryServer.deregisterMonitorProvider(curDirectory);
itr.remove();
}
}
if (directories.isEmpty())
{
monitoredDirs.remove(directory);
}
}
}
}
/** {@inheritDoc} */
@Override
public void initializeMonitorProvider(MonitorProviderCfg configuration)
throws ConfigException, InitializationException {
// Not used...
}
/** {@inheritDoc} */
@Override
public String getMonitorInstanceName() {
return INSTANCENAME;
}
/** {@inheritDoc} */
@Override
public List<Attribute> getMonitorData() {
return new ArrayList<>();
}
/** {@inheritDoc} */
@Override
public void run()
{
List<HandlerNotifier> diskFull = new ArrayList<>();
List<HandlerNotifier> diskLow = new ArrayList<>();
List<HandlerNotifier> diskRestored = new ArrayList<>();
synchronized (monitoredDirs)
{
for (Entry<File, List<MonitoredDirectory>> dirElem : monitoredDirs.entrySet())
{
File directory = dirElem.getKey();
HandlerNotifier diskFullClients = new HandlerNotifier(directory, FULL);
HandlerNotifier diskLowClients = new HandlerNotifier(directory, LOW);
HandlerNotifier diskRestoredClients = new HandlerNotifier(directory, NORMAL);
try
{
long lastFreeSpace = directory.getUsableSpace();
for (MonitoredDirectory handlerElem : dirElem.getValue())
{
if (lastFreeSpace < handlerElem.getFullThreshold() && handlerElem.lastState < FULL)
{
diskFullClients.addHandler(handlerElem);
}
else if (lastFreeSpace < handlerElem.getLowThreshold() && handlerElem.lastState < LOW)
{
diskLowClients.addHandler(handlerElem);
}
else if (handlerElem.lastState != NORMAL)
{
diskRestoredClients.addHandler(handlerElem);
}
}
addToList(diskFull, diskFullClients);
addToList(diskLow, diskLowClients);
addToList(diskRestored, diskRestoredClients);
}
catch(Exception e)
{
logger.error(ERR_DISK_SPACE_MONITOR_UPDATE_FAILED, directory, e);
logger.traceException(e);
}
}
}
// It is probably better to notify handlers outside of the synchronized section.
sendNotification(diskFull, FULL, ALERT_DESCRIPTION_DISK_FULL);
sendNotification(diskLow, LOW, ALERT_TYPE_DISK_SPACE_LOW);
sendNotification(diskRestored, NORMAL, null);
}
private void addToList(List<HandlerNotifier> hnList, HandlerNotifier notifier)
{
if (!notifier.isEmpty())
{
hnList.add(notifier);
}
}
private void sendNotification(List<HandlerNotifier> diskList, int state, String alert)
{
for (HandlerNotifier dirElem : diskList)
{
String dirPath = dirElem.directory.getAbsolutePath();
String handlerNames = dirElem.diskNames.toString();
long freeSpace = dirElem.directory.getFreeSpace();
if (state == FULL)
{
DirectoryServer.sendAlertNotification(this, alert,
ERR_DISK_SPACE_FULL_THRESHOLD_REACHED.get(dirPath, handlerNames, freeSpace));
}
else if (state == LOW)
{
DirectoryServer.sendAlertNotification(this, alert,
ERR_DISK_SPACE_LOW_THRESHOLD_REACHED.get(dirPath, handlerNames, freeSpace));
}
else
{
logger.error(NOTE_DISK_SPACE_RESTORED.get(freeSpace, dirPath));
}
dirElem.notifyHandlers();
}
}
/** {@inheritDoc} */
@Override
public DN getComponentEntryDN()
{
try
{
return DN.valueOf(INSTANCENAME);
}
catch (DirectoryException de)
{
return DN.NULL_DN;
}
}
/** {@inheritDoc} */
@Override
public String getClassName()
{
return DiskSpaceMonitor.class.getName();
}
/** {@inheritDoc} */
@Override
public Map<String, String> getAlerts()
{
Map<String, String> alerts = new LinkedHashMap<>();
alerts.put(ALERT_TYPE_DISK_SPACE_LOW, ALERT_DESCRIPTION_DISK_SPACE_LOW);
alerts.put(ALERT_TYPE_DISK_FULL, ALERT_DESCRIPTION_DISK_FULL);
return alerts;
}
/** {@inheritDoc} */
@Override
public String getShutdownListenerName()
{
return INSTANCENAME;
}
/** {@inheritDoc} */
@Override
public void processServerShutdown(LocalizableMessage reason)
{
synchronized (monitoredDirs)
{
for (Entry<File, List<MonitoredDirectory>> dirElem : monitoredDirs.entrySet())
{
for (MonitoredDirectory handlerElem : dirElem.getValue())
{
DirectoryServer.deregisterMonitorProvider(handlerElem);
}
}
}
}
}