From 7ccaa46b4a749896b2daabda390d8ddd3ae7743f Mon Sep 17 00:00:00 2001
From: Fabio Pistolesi <fabio.pistolesi@forgerock.com>
Date: Fri, 10 Apr 2015 14:55:54 +0000
Subject: [PATCH] 

---
 opendj-server-legacy/src/main/java/org/opends/server/extensions/DiskSpaceMonitor.java |  585 +++++++++++++++++++++++++++++++++++++++-------------------
 1 files changed, 393 insertions(+), 192 deletions(-)

diff --git a/opendj-server-legacy/src/main/java/org/opends/server/extensions/DiskSpaceMonitor.java b/opendj-server-legacy/src/main/java/org/opends/server/extensions/DiskSpaceMonitor.java
index 5a88036..90ac02a 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/extensions/DiskSpaceMonitor.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/extensions/DiskSpaceMonitor.java
@@ -28,23 +28,39 @@
 
 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.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.opends.server.api.AttributeSyntax;
 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;
+import org.opends.server.util.Platform;
 
 /**
  * This class provides an application-wide disk space monitoring service.
@@ -56,243 +72,428 @@
  * 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
+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<Attribute>();
+      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, AttributeSyntax<?> 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<MonitoredDirectory>();
+
+    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 volatile File directory;
-  private volatile long lowThreshold;
-  private volatile long fullThreshold;
-  private final DiskSpaceMonitorHandler handler;
-  private final int interval;
-  private final TimeUnit unit;
-  private final String instanceName;
-  private int lastState;
+  private static final String INSTANCENAME = "Disk Space Monitor";
+  private final HashMap<File, List<MonitoredDirectory>> monitoredDirs =
+      new HashMap<File, List<MonitoredDirectory>>();
 
   /**
-   * Constructs a new DiskSpaceMonitor that will notify the specified
-   * DiskSpaceMonitorHandler when the specified disk
-   * falls below the provided thresholds.
-   *
-   * @param instanceName A unique name for this monitor.
-   * @param directory The directory to monitor.
-   * @param lowThreshold The "low" threshold.
-   * @param fullThreshold   The "full" threshold.
-   * @param interval  The polling interval for checking free space.
-   * @param unit the time unit of the interval parameter.
-   * @param handler The handler to get notified when the provided thresholds are
-   *                reached or <code>null</code> if no notification is needed;
+   * Constructs a new DiskSpaceMonitor that will notify registered DiskSpaceMonitorHandler objects when filesystems
+   * on which configured directories reside, fall below the provided thresholds.
    */
-  public DiskSpaceMonitor(String instanceName, File directory,
-                          long lowThreshold,
-                          long fullThreshold, int interval, TimeUnit unit,
-                          DiskSpaceMonitorHandler handler) {
-    this.directory = directory;
-    this.lowThreshold = lowThreshold;
-    this.fullThreshold = fullThreshold;
-    this.interval = interval;
-    this.unit = unit;
-    this.handler = handler;
-    this.instanceName = instanceName+",cn=Disk Space Monitor";
-  }
-
-  /**
-   * Retrieves the directory currently being monitored.
-   *
-   * @return The directory currently being monitored.
-   */
-  public File getDirectory() {
-    return directory;
-  }
-
-  /**
-   * Sets the directory to monitor.
-   *
-   * @param directory The directory to monitor.
-   */
-  public void setDirectory(File directory) {
-    this.directory = directory;
-  }
-
-  /**
-   * Retrieves the currently "low" space threshold currently being enforced.
-   *
-   * @return The currently "low" space threshold currently being enforced.
-   */
-  public long getLowThreshold() {
-    return lowThreshold;
-  }
-
-  /**
-   * Sets the "low" space threshold to enforce.
-   *
-   * @param lowThreshold The "low" space threshold to enforce.
-   */
-  public void setLowThreshold(long lowThreshold) {
-    this.lowThreshold = lowThreshold;
-  }
-
-  /**
-   * Retrieves the currently full threshold currently being enforced.
-   *
-   * @return The currently full space threshold currently being enforced.
-   */
-  public long getFullThreshold() {
-    return fullThreshold;
-  }
-
-  /**
-   * Sets the full threshold to enforce.
-   *
-   * @param fullThreshold The full space threshold to enforce.
-   */
-  public void setFullThreshold(long fullThreshold) {
-    this.fullThreshold = fullThreshold;
-  }
-
-  /**
-   * Retrieves the free space currently on the disk.
-   *
-   * @return The free space currently on the disk.
-   */
-  public long getFreeSpace() {
-    return directory.getUsableSpace();
-  }
-
-  /**
-   * Indicates if the "full" threshold is reached.
-   *
-   * @return <code>true</code> if the free space is lower than the "full"
-   *         threshold or <code>false</code> otherwise.
-   */
-  public boolean isFullThresholdReached()
+  public DiskSpaceMonitor()
   {
-    return lastState >= FULL;
   }
 
   /**
-   * Indicates if the "low" threshold is reached.
-   *
-   * @return <code>true</code> if the free space is lower than the "low"
-   *         threshold or <code>false</code> otherwise.
+   * Starts periodic monitoring of all registered directories.
    */
-  public boolean isLowThresholdReached()
+  public void startDiskSpaceMonitor()
   {
-    return lastState >= LOW;
+    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 = Platform.getFilesystem(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<MonitoredDirectory>();
+        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);
+    }
+  }
+
+  /**
+   * 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 {
-    scheduleUpdate(this, 0, interval, unit);
+    // Not used...
   }
 
   /** {@inheritDoc} */
   @Override
   public String getMonitorInstanceName() {
-    return instanceName;
+    return INSTANCENAME;
   }
 
   /** {@inheritDoc} */
   @Override
   public List<Attribute> getMonitorData() {
-    final ArrayList<Attribute> monitorAttrs = new ArrayList<Attribute>();
-    monitorAttrs.add(attr("disk-dir", getDefaultStringSyntax(), directory.getPath()));
-    monitorAttrs.add(attr("disk-free", getDefaultIntegerSyntax(), getFreeSpace()));
-    monitorAttrs.add(attr("disk-state", getDefaultStringSyntax(), getState()));
-    return monitorAttrs;
+    return new ArrayList<Attribute>();
   }
 
-  private Attribute attr(String name, AttributeSyntax<?> syntax, Object value)
+  /** {@inheritDoc} */
+  @Override
+  public void run()
   {
-    AttributeType attrType = DirectoryServer.getDefaultAttributeType(name, syntax);
-    return Attributes.create(attrType, String.valueOf(value));
-  }
+    List<HandlerNotifier> diskFull = new ArrayList<HandlerNotifier>();
+    List<HandlerNotifier> diskLow = new ArrayList<HandlerNotifier>();
+    List<HandlerNotifier> diskRestored = new ArrayList<HandlerNotifier>();
 
-  private String getState()
-  {
-    switch(lastState)
+    synchronized (monitoredDirs)
     {
-    case NORMAL:
-      return "normal";
-    case LOW:
-      return "low";
-    case FULL:
-      return "full";
-    default:
-      return null;
+      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 void run() {
+  public DN getComponentEntryDN()
+  {
     try
     {
-      long lastFreeSpace = directory.getUsableSpace();
-
-      if(logger.isTraceEnabled())
-      {
-        logger.trace("Free space for %s: %d, " +
-            "low threshold: %d, full threshold: %d, state: %d",
-            directory.getPath(), lastFreeSpace, lowThreshold, fullThreshold,
-            lastState);
-      }
-
-      if(lastFreeSpace < fullThreshold)
-      {
-        if (lastState < FULL)
-        {
-          if(logger.isTraceEnabled())
-          {
-            logger.trace("State change: %d -> %d", lastState, FULL);
-          }
-
-          lastState = FULL;
-          if(handler != null)
-          {
-            handler.diskFullThresholdReached(this);
-          }
-        }
-      }
-      else if(lastFreeSpace < lowThreshold)
-      {
-        if (lastState < LOW)
-        {
-          if(logger.isTraceEnabled())
-          {
-            logger.trace("State change: %d -> %d", lastState, LOW);
-          }
-          lastState = LOW;
-          if(handler != null)
-          {
-            handler.diskLowThresholdReached(this);
-          }
-        }
-      }
-      else if (lastState != NORMAL)
-      {
-        if(logger.isTraceEnabled())
-        {
-          logger.trace("State change: %d -> %d", lastState, NORMAL);
-        }
-        lastState = NORMAL;
-        if(handler != null)
-        {
-          handler.diskSpaceRestored(this);
-        }
-      }
+      return DN.valueOf(INSTANCENAME);
     }
-    catch(Exception e)
+    catch (DirectoryException de)
     {
-      logger.error(ERR_DISK_SPACE_MONITOR_UPDATE_FAILED, directory.getPath(), e);
-      logger.traceException(e);
+      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<String, String>();
+    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);
+        }
+      }
+    }
+    DirectoryServer.deregisterMonitorProvider(this);
+  }
 }

--
Gitblit v1.10.0