You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sirona.apache.org by rm...@apache.org on 2015/11/02 04:59:48 UTC

svn commit: r1711893 - in /incubator/sirona/trunk: agent/pull/src/main/java/org/apache/sirona/agent/webapp/pull/repository/ api/src/main/java/org/apache/sirona/alert/ api/src/main/java/org/apache/sirona/configuration/ioc/ api/src/main/java/org/apache/s...

Author: rmannibucau
Date: Mon Nov  2 03:59:47 2015
New Revision: 1711893

URL: http://svn.apache.org/viewvc?rev=1711893&view=rev
Log:
SIRONA-47 adding AlertListener API

Added:
    incubator/sirona/trunk/api/src/main/java/org/apache/sirona/alert/
    incubator/sirona/trunk/api/src/main/java/org/apache/sirona/alert/AlertListener.java
    incubator/sirona/trunk/api/src/main/java/org/apache/sirona/alert/AlerterSupport.java
    incubator/sirona/trunk/core/src/main/java/org/apache/sirona/store/status/CollectorBaseNodeStatusDataStore.java
    incubator/sirona/trunk/core/src/test/java/org/apache/sirona/alert/
    incubator/sirona/trunk/core/src/test/java/org/apache/sirona/alert/AlerterSupportTest.java
    incubator/sirona/trunk/core/src/test/java/org/apache/sirona/repositories/
    incubator/sirona/trunk/core/src/test/java/org/apache/sirona/repositories/DefaultRepositoryTest.java
Modified:
    incubator/sirona/trunk/agent/pull/src/main/java/org/apache/sirona/agent/webapp/pull/repository/PullRepository.java
    incubator/sirona/trunk/api/src/main/java/org/apache/sirona/configuration/ioc/IoCs.java
    incubator/sirona/trunk/api/src/main/java/org/apache/sirona/store/status/EmptyStatuses.java
    incubator/sirona/trunk/api/src/main/java/org/apache/sirona/store/status/NodeStatusDataStore.java
    incubator/sirona/trunk/api/src/main/java/org/apache/sirona/store/status/PeriodicNodeStatusDataStore.java
    incubator/sirona/trunk/core/src/main/java/org/apache/sirona/repositories/DefaultRepository.java
    incubator/sirona/trunk/core/src/main/java/org/apache/sirona/store/status/InMemoryCollectorNodeStatusDataStore.java
    incubator/sirona/trunk/core/src/test/resources/sirona.properties
    incubator/sirona/trunk/plugins/hazelcast/agent/src/test/java/org/apache/sirona/plugin/hazelcast/BasicHazelcastTest.java
    incubator/sirona/trunk/server/store/cassandra/src/main/java/org/apache/sirona/cassandra/agent/status/CassandraStatusDataStore.java
    incubator/sirona/trunk/server/store/cassandra/src/main/java/org/apache/sirona/cassandra/collector/status/CassandraCollectorNodeStatusDataStore.java
    incubator/sirona/trunk/server/websocket-server/src/test/java/org/apache/sirona/websocket/WebSocketTest.java

Modified: incubator/sirona/trunk/agent/pull/src/main/java/org/apache/sirona/agent/webapp/pull/repository/PullRepository.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/agent/pull/src/main/java/org/apache/sirona/agent/webapp/pull/repository/PullRepository.java?rev=1711893&r1=1711892&r2=1711893&view=diff
==============================================================================
--- incubator/sirona/trunk/agent/pull/src/main/java/org/apache/sirona/agent/webapp/pull/repository/PullRepository.java (original)
+++ incubator/sirona/trunk/agent/pull/src/main/java/org/apache/sirona/agent/webapp/pull/repository/PullRepository.java Mon Nov  2 03:59:47 2015
@@ -28,8 +28,8 @@ import org.apache.sirona.repositories.Re
 import org.apache.sirona.status.NodeStatus;
 import org.apache.sirona.status.NodeStatusReporter;
 import org.apache.sirona.store.memory.counter.InMemoryCounterDataStore;
-import org.apache.sirona.store.status.EmptyStatuses;
 import org.apache.sirona.store.memory.tracking.InMemoryPathTrackingDataStore;
+import org.apache.sirona.store.status.EmptyStatuses;
 
 import java.util.Collection;
 import java.util.Collections;
@@ -44,7 +44,7 @@ public class PullRepository extends Defa
     private final boolean clearAfterCollect;
 
     public PullRepository() {
-        super(new InMemoryCounterDataStore(), new GaugeDataStoreAdapter(), new EmptyStatuses(), new InMemoryPathTrackingDataStore());
+        super(new InMemoryCounterDataStore(), new GaugeDataStoreAdapter(), new EmptyStatuses(), new InMemoryPathTrackingDataStore(), findAlerters());
         cube = IoCs.findOrCreateInstance(CubeBuilder.class).build();
         clearAfterCollect = Configuration.is(Configuration.CONFIG_PROPERTY_PREFIX + "pull.counter.clearOnCollect", false);
     }

Added: incubator/sirona/trunk/api/src/main/java/org/apache/sirona/alert/AlertListener.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/api/src/main/java/org/apache/sirona/alert/AlertListener.java?rev=1711893&view=auto
==============================================================================
--- incubator/sirona/trunk/api/src/main/java/org/apache/sirona/alert/AlertListener.java (added)
+++ incubator/sirona/trunk/api/src/main/java/org/apache/sirona/alert/AlertListener.java Mon Nov  2 03:59:47 2015
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sirona.alert;
+
+import org.apache.sirona.status.NodeStatus;
+
+public interface AlertListener {
+    void onAlert(Alert alert);
+
+    class Alert {
+        private final String marker;
+        private final NodeStatus status;
+
+        protected Alert(final String node, final NodeStatus status) {
+            this.marker = node;
+            this.status = status;
+        }
+
+        public String getMarker() {
+            return marker;
+        }
+
+        public NodeStatus getStatus() {
+            return status;
+        }
+    }
+}

Added: incubator/sirona/trunk/api/src/main/java/org/apache/sirona/alert/AlerterSupport.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/api/src/main/java/org/apache/sirona/alert/AlerterSupport.java?rev=1711893&view=auto
==============================================================================
--- incubator/sirona/trunk/api/src/main/java/org/apache/sirona/alert/AlerterSupport.java (added)
+++ incubator/sirona/trunk/api/src/main/java/org/apache/sirona/alert/AlerterSupport.java Mon Nov  2 03:59:47 2015
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sirona.alert;
+
+import org.apache.sirona.status.NodeStatus;
+import org.apache.sirona.status.Status;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+public class AlerterSupport {
+    protected final Collection<AlertListener> listeners = new CopyOnWriteArraySet<AlertListener>();
+
+    public void notify(final Map<String, NodeStatus> nodeStatus) {
+        if (nodeStatus == null) {
+            return;
+        }
+        for (final Map.Entry<String, NodeStatus> entry : nodeStatus.entrySet()) {
+            final NodeStatus status = entry.getValue();
+            if (status.getStatus() != Status.OK) {
+                final AlertListener.Alert alert = new AlertListener.Alert(entry.getKey(), status);
+                for (final AlertListener listener : listeners) {
+                    listener.onAlert(alert);
+                }
+            }
+        }
+    }
+
+    public void addAlerter(final AlertListener listener) {
+        listeners.add(listener);
+    }
+
+    public void removeAlerter(final AlertListener listener) {
+        listeners.remove(listener);
+    }
+
+    public boolean hasAlerter() {
+        return !listeners.isEmpty();
+    }
+}

Modified: incubator/sirona/trunk/api/src/main/java/org/apache/sirona/configuration/ioc/IoCs.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/api/src/main/java/org/apache/sirona/configuration/ioc/IoCs.java?rev=1711893&r1=1711892&r2=1711893&view=diff
==============================================================================
--- incubator/sirona/trunk/api/src/main/java/org/apache/sirona/configuration/ioc/IoCs.java (original)
+++ incubator/sirona/trunk/api/src/main/java/org/apache/sirona/configuration/ioc/IoCs.java Mon Nov  2 03:59:47 2015
@@ -187,7 +187,14 @@ public final class IoCs {
                     continue;
                 }
 
-                final String value = Configuration.getProperty(loadedClass.getName() + "." + field.getName(), null);
+                final String configKey;
+                if (key == null) {
+                    configKey = loadedClass.getName() + "." + field.getName();
+                } else {
+                    configKey = key + "." + field.getName();
+                }
+
+                final String value = Configuration.getProperty(configKey, null);
                 if (value != null) {
                     done.add(field.getName());
 
@@ -210,6 +217,10 @@ public final class IoCs {
     }
 
     public static void setSingletonInstance(final Class<?> clazz, final Object instance) {
+        if (instance == null) {
+            SINGLETONS.remove(clazz);
+            return;
+        }
         SINGLETONS.put(clazz, instance);
     }
 

Modified: incubator/sirona/trunk/api/src/main/java/org/apache/sirona/store/status/EmptyStatuses.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/api/src/main/java/org/apache/sirona/store/status/EmptyStatuses.java?rev=1711893&r1=1711892&r2=1711893&view=diff
==============================================================================
--- incubator/sirona/trunk/api/src/main/java/org/apache/sirona/store/status/EmptyStatuses.java (original)
+++ incubator/sirona/trunk/api/src/main/java/org/apache/sirona/store/status/EmptyStatuses.java Mon Nov  2 03:59:47 2015
@@ -16,6 +16,7 @@
  */
 package org.apache.sirona.store.status;
 
+import org.apache.sirona.alert.AlertListener;
 import org.apache.sirona.status.NodeStatus;
 
 import java.util.Map;
@@ -34,4 +35,16 @@ public class EmptyStatuses implements No
     public void reset() {
         // no-op
     }
+
+    public void addAlerter(AlertListener listener) {
+        noAlert();
+    }
+
+    public void removeAlerter(final AlertListener listener) {
+        noAlert();
+    }
+
+    private void noAlert() {
+        throw new UnsupportedOperationException(getClass().getSimpleName() + " doesn't support alerts");
+    }
 }

Modified: incubator/sirona/trunk/api/src/main/java/org/apache/sirona/store/status/NodeStatusDataStore.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/api/src/main/java/org/apache/sirona/store/status/NodeStatusDataStore.java?rev=1711893&r1=1711892&r2=1711893&view=diff
==============================================================================
--- incubator/sirona/trunk/api/src/main/java/org/apache/sirona/store/status/NodeStatusDataStore.java (original)
+++ incubator/sirona/trunk/api/src/main/java/org/apache/sirona/store/status/NodeStatusDataStore.java Mon Nov  2 03:59:47 2015
@@ -16,6 +16,7 @@
  */
 package org.apache.sirona.store.status;
 
+import org.apache.sirona.alert.AlertListener;
 import org.apache.sirona.status.NodeStatus;
 
 import java.util.Map;
@@ -23,4 +24,7 @@ import java.util.Map;
 public interface NodeStatusDataStore {
     Map<String, NodeStatus> statuses();
     void reset();
+
+    void addAlerter(AlertListener listener);
+    void removeAlerter(AlertListener listener);
 }

Modified: incubator/sirona/trunk/api/src/main/java/org/apache/sirona/store/status/PeriodicNodeStatusDataStore.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/api/src/main/java/org/apache/sirona/store/status/PeriodicNodeStatusDataStore.java?rev=1711893&r1=1711892&r2=1711893&view=diff
==============================================================================
--- incubator/sirona/trunk/api/src/main/java/org/apache/sirona/store/status/PeriodicNodeStatusDataStore.java (original)
+++ incubator/sirona/trunk/api/src/main/java/org/apache/sirona/store/status/PeriodicNodeStatusDataStore.java Mon Nov  2 03:59:47 2015
@@ -16,6 +16,8 @@
  */
 package org.apache.sirona.store.status;
 
+import org.apache.sirona.alert.AlertListener;
+import org.apache.sirona.alert.AlerterSupport;
 import org.apache.sirona.configuration.Configuration;
 import org.apache.sirona.configuration.ioc.Created;
 import org.apache.sirona.configuration.ioc.Destroying;
@@ -35,6 +37,8 @@ import java.util.concurrent.atomic.Atomi
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import static java.util.Collections.singletonMap;
+
 public class PeriodicNodeStatusDataStore implements NodeStatusDataStore {
     private static final Logger LOGGER = Logger.getLogger(PeriodicNodeStatusDataStore.class.getName());
 
@@ -42,6 +46,7 @@ public class PeriodicNodeStatusDataStore
     protected final AtomicReference<NodeStatus> status = new AtomicReference<NodeStatus>();
     protected final HashMap<String, NodeStatus> statusAsMap = new HashMap<String, NodeStatus>();
     protected final NodeStatusReporter nodeStatusReporter;
+    protected final AlerterSupport listeners = new AlerterSupport();
 
     public PeriodicNodeStatusDataStore() {
         nodeStatusReporter = newNodeStatusReporter();
@@ -72,12 +77,23 @@ public class PeriodicNodeStatusDataStore
         reload();
     }
 
+    public void addAlerter(final AlertListener listener) {
+        listeners.addAlerter(listener);
+    }
+
+    public void removeAlerter(final AlertListener listener) {
+        listeners.removeAlerter(listener);
+    }
+
     private void reload() {
         final String name = getClass().getSimpleName().toLowerCase(Locale.ENGLISH).replace("nodestatusdatastore", "");
         final long period = getPeriod(name);
+        if (period < 0) {
+            return;
+        }
 
         final ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor(new DaemonThreadFactory(name + "-status-schedule-"));
-        final ScheduledFuture<?> future = ses.scheduleAtFixedRate(new ReportStatusTask(nodeStatusReporter), period, period, TimeUnit.MILLISECONDS);
+        final ScheduledFuture<?> future = ses.scheduleAtFixedRate(new ReportStatusTask(), period, period, TimeUnit.MILLISECONDS);
         scheduledTask.set(new BatchFuture(ses, future));
     }
 
@@ -100,22 +116,21 @@ public class PeriodicNodeStatusDataStore
         return statusAsMap;
     }
 
-    private class ReportStatusTask implements Runnable {
-        private final NodeStatusReporter reporter;
-
-        public ReportStatusTask(final NodeStatusReporter nodeStatusReporter) {
-            reporter = nodeStatusReporter;
+    protected void periodicTask() {
+        final NodeStatus nodeStatus = nodeStatusReporter.computeStatus();
+        try {
+            status.set(nodeStatus);
+            reportStatus(nodeStatus);
+            listeners.notify(singletonMap("local", nodeStatus));
+        } catch (final Exception e) {
+            LOGGER.log(Level.SEVERE, e.getMessage(), e);
         }
+    }
 
+    private class ReportStatusTask implements Runnable {
         @Override
         public void run() {
-            final NodeStatus nodeStatus = reporter.computeStatus();
-            try {
-                status.set(nodeStatus);
-                reportStatus(nodeStatus);
-            } catch (final Exception e) {
-                LOGGER.log(Level.SEVERE, e.getMessage(), e);
-            }
+            PeriodicNodeStatusDataStore.this.periodicTask();
         }
     }
 }

Modified: incubator/sirona/trunk/core/src/main/java/org/apache/sirona/repositories/DefaultRepository.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/core/src/main/java/org/apache/sirona/repositories/DefaultRepository.java?rev=1711893&r1=1711892&r2=1711893&view=diff
==============================================================================
--- incubator/sirona/trunk/core/src/main/java/org/apache/sirona/repositories/DefaultRepository.java (original)
+++ incubator/sirona/trunk/core/src/main/java/org/apache/sirona/repositories/DefaultRepository.java Mon Nov  2 03:59:47 2015
@@ -18,6 +18,7 @@ package org.apache.sirona.repositories;
 
 import org.apache.sirona.Role;
 import org.apache.sirona.SironaException;
+import org.apache.sirona.alert.AlertListener;
 import org.apache.sirona.configuration.Configuration;
 import org.apache.sirona.configuration.ioc.IoCs;
 import org.apache.sirona.counters.Counter;
@@ -40,7 +41,9 @@ import org.apache.sirona.store.gauge.Gau
 import org.apache.sirona.store.status.CollectorNodeStatusDataStore;
 import org.apache.sirona.store.status.NodeStatusDataStore;
 import org.apache.sirona.store.tracking.PathTrackingDataStore;
+import org.apache.sirona.util.ClassLoaders;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Map;
 import java.util.SortedMap;
@@ -53,11 +56,12 @@ public class DefaultRepository implement
     protected final PathTrackingDataStore pathTrackingDataStore;
 
     public DefaultRepository() {
-        this(findCounterDataStore(), findGaugeDataStore(), findStatusDataStore(), findPathTrackingDataStore());
+        this(findCounterDataStore(), findGaugeDataStore(), findStatusDataStore(), findPathTrackingDataStore(), findAlerters());
     }
 
     protected DefaultRepository(final CounterDataStore counter, final CommonGaugeDataStore gauge, //
-                                final NodeStatusDataStore status, final PathTrackingDataStore pathTrackingDataStore) {
+                                final NodeStatusDataStore status, final PathTrackingDataStore pathTrackingDataStore,
+                                final Collection<AlertListener> alertListeners) {
         this.counterDataStore = counter;
         this.gaugeDataStore = gauge;
         this.nodeStatusDataStore = status;
@@ -85,9 +89,36 @@ public class DefaultRepository implement
             addGauge(new UsedNonHeapMemoryGauge());
             addGauge(new ActiveThreadGauge());
         }
+
+        for (final AlertListener listener : alertListeners) {
+            nodeStatusDataStore.addAlerter(listener);
+        }
+    }
+
+    protected static Collection<AlertListener> findAlerters() {
+        final Collection<AlertListener> listeners = new ArrayList<AlertListener>();
+        final String alerters = Configuration.getProperty(Configuration.CONFIG_PROPERTY_PREFIX + "alerters", null);
+        if (alerters != null && alerters.trim().length() > 0) {
+            for (final String alert : alerters.split(" *, *")) {
+                final String classKey = alert + ".class";
+                final String type = Configuration.getProperty(classKey, null);
+                if (type == null) {
+                    throw new IllegalArgumentException("Missing configuration " + classKey);
+                }
+
+                try {
+                    final Class<?> clazz = ClassLoaders.current().loadClass(type);
+                    final AlertListener listener = IoCs.autoSet(alert, AlertListener.class.cast(clazz.newInstance()));
+                    listeners.add(listener);
+                } catch (final Exception e) {
+                    throw new IllegalArgumentException(e);
+                }
+            }
+        }
+        return listeners;
     }
 
-    private static NodeStatusDataStore findStatusDataStore() {
+    protected static NodeStatusDataStore findStatusDataStore() {
         NodeStatusDataStore status = null;
         try {
             status = IoCs.findOrCreateInstance(NodeStatusDataStore.class);
@@ -100,7 +131,7 @@ public class DefaultRepository implement
         return status;
     }
 
-    private static CommonGaugeDataStore findGaugeDataStore() {
+    protected static CommonGaugeDataStore findGaugeDataStore() {
         CommonGaugeDataStore gauge = null;
         try {
             gauge = IoCs.findOrCreateInstance(GaugeDataStore.class);
@@ -120,7 +151,7 @@ public class DefaultRepository implement
         return gauge;
     }
 
-    private static PathTrackingDataStore findPathTrackingDataStore() {
+    protected static PathTrackingDataStore findPathTrackingDataStore() {
         PathTrackingDataStore pathTrackingDataStore = null;
         try {
             pathTrackingDataStore = IoCs.findOrCreateInstance(PathTrackingDataStore.class);
@@ -143,7 +174,7 @@ public class DefaultRepository implement
         return pathTrackingDataStore;
     }
 
-    private static CounterDataStore findCounterDataStore() {
+    protected static CounterDataStore findCounterDataStore() {
         CounterDataStore counter = null;
         try {
             counter = IoCs.findOrCreateInstance(CounterDataStore.class);

Added: incubator/sirona/trunk/core/src/main/java/org/apache/sirona/store/status/CollectorBaseNodeStatusDataStore.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/core/src/main/java/org/apache/sirona/store/status/CollectorBaseNodeStatusDataStore.java?rev=1711893&view=auto
==============================================================================
--- incubator/sirona/trunk/core/src/main/java/org/apache/sirona/store/status/CollectorBaseNodeStatusDataStore.java (added)
+++ incubator/sirona/trunk/core/src/main/java/org/apache/sirona/store/status/CollectorBaseNodeStatusDataStore.java Mon Nov  2 03:59:47 2015
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sirona.store.status;
+
+import org.apache.sirona.alert.AlertListener;
+import org.apache.sirona.alert.AlerterSupport;
+import org.apache.sirona.configuration.Configuration;
+import org.apache.sirona.configuration.ioc.Destroying;
+import org.apache.sirona.store.BatchFuture;
+import org.apache.sirona.util.DaemonThreadFactory;
+
+import java.util.Locale;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public abstract class CollectorBaseNodeStatusDataStore implements CollectorNodeStatusDataStore {
+    private final AlerterSupport listeners = new AlerterSupport();
+    private final AtomicReference<BatchFuture> scheduledTask = new AtomicReference<BatchFuture>();
+
+    @Destroying
+    public void shutdown() {
+        final BatchFuture task = scheduledTask.get();
+        if (task != null) {
+            task.done();
+            scheduledTask.compareAndSet(task, null);
+        }
+    }
+
+    @Override
+    public void reset() {
+        shutdown();
+    }
+
+    @Override
+    public void addAlerter(final AlertListener listener) {
+        listeners.addAlerter(listener);
+        if (scheduledTask.get() == null) {
+            final BatchFuture update = startTask();
+            if (!scheduledTask.compareAndSet(null, update)) {
+                update.done();
+            }
+        }
+    }
+
+    @Override
+    public void removeAlerter(final AlertListener listener) {
+        listeners.removeAlerter(listener);
+        if (!listeners.hasAlerter()) {
+            shutdown();
+        }
+    }
+
+    private BatchFuture startTask() {
+        final String name = getClass().getSimpleName().toLowerCase(Locale.ENGLISH).replace("nodestatusdatastore", "");
+        final long period = getPeriod(name);
+
+        final ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor(new DaemonThreadFactory(name + "-status-alert-schedule-"));
+        final ScheduledFuture<?> future = ses.scheduleAtFixedRate(new CheckStatus(), period, period, TimeUnit.MILLISECONDS);
+        return new BatchFuture(ses, future);
+    }
+
+    protected int getPeriod(final String name) {
+        return Configuration.getInteger(Configuration.CONFIG_PROPERTY_PREFIX + name + ".status.period",
+            Configuration.getInteger(Configuration.CONFIG_PROPERTY_PREFIX + name + ".period", 60000));
+    }
+
+    private class CheckStatus implements Runnable {
+        private final Logger logger = Logger.getLogger(CheckStatus.class.getName());
+
+        public void run() {
+            try {
+                listeners.notify(CollectorBaseNodeStatusDataStore.this.statuses());
+            } catch (final Exception e) {
+                logger.log(Level.SEVERE, e.getMessage(), e);
+            }
+        }
+    }
+}

Modified: incubator/sirona/trunk/core/src/main/java/org/apache/sirona/store/status/InMemoryCollectorNodeStatusDataStore.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/core/src/main/java/org/apache/sirona/store/status/InMemoryCollectorNodeStatusDataStore.java?rev=1711893&r1=1711892&r2=1711893&view=diff
==============================================================================
--- incubator/sirona/trunk/core/src/main/java/org/apache/sirona/store/status/InMemoryCollectorNodeStatusDataStore.java (original)
+++ incubator/sirona/trunk/core/src/main/java/org/apache/sirona/store/status/InMemoryCollectorNodeStatusDataStore.java Mon Nov  2 03:59:47 2015
@@ -17,14 +17,12 @@
 package org.apache.sirona.store.status;
 
 import org.apache.sirona.status.NodeStatus;
-import org.apache.sirona.store.status.CollectorNodeStatusDataStore;
-import org.apache.sirona.store.status.NodeStatusDataStore;
 
 import java.util.Map;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 
-public class InMemoryCollectorNodeStatusDataStore implements CollectorNodeStatusDataStore {
+public class InMemoryCollectorNodeStatusDataStore extends CollectorBaseNodeStatusDataStore {
     private final Map<String, NodeStatus> statuses = new ConcurrentHashMap<String, NodeStatus>();
 
     @Override
@@ -34,6 +32,7 @@ public class InMemoryCollectorNodeStatus
 
     @Override
     public void reset() {
+        super.reset();
         statuses.clear();
     }
 

Added: incubator/sirona/trunk/core/src/test/java/org/apache/sirona/alert/AlerterSupportTest.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/core/src/test/java/org/apache/sirona/alert/AlerterSupportTest.java?rev=1711893&view=auto
==============================================================================
--- incubator/sirona/trunk/core/src/test/java/org/apache/sirona/alert/AlerterSupportTest.java (added)
+++ incubator/sirona/trunk/core/src/test/java/org/apache/sirona/alert/AlerterSupportTest.java Mon Nov  2 03:59:47 2015
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sirona.alert;
+
+import org.apache.sirona.status.NodeStatus;
+import org.apache.sirona.status.Status;
+import org.apache.sirona.status.ValidationResult;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+public class AlerterSupportTest {
+    @Test
+    public void alert() {
+        final Map<String, NodeStatus> nodeStatus = new HashMap<String, NodeStatus>();
+
+        final AlerterSupport support = new AlerterSupport();
+        support.notify(nodeStatus); // all is empty but no exception
+
+        // add a listener
+        final Collection<AlertListener.Alert> alerts = new ArrayList<AlertListener.Alert>();
+        support.addAlerter(new AlertListener() {
+            public void onAlert(final Alert alert) {
+                alerts.add(alert);
+            }
+        });
+
+        support.notify(nodeStatus); // no status so no alert
+        assertEquals(0, alerts.size());
+
+        // now add a OK status
+        nodeStatus.put("host1", new NodeStatus(new ValidationResult[] { new ValidationResult("v1", Status.OK, "")}, new Date()));
+        support.notify(nodeStatus); // only 1 OK status so no alert
+        assertEquals(0, alerts.size());
+
+        // now add another OK status
+        nodeStatus.put("host2", new NodeStatus(new ValidationResult[] { new ValidationResult("v2", Status.OK, "")}, new Date()));
+        support.notify(nodeStatus); // only OK status so no alert
+        assertEquals(0, alerts.size());
+
+        // add a KO status so one alert
+        nodeStatus.put("host1", new NodeStatus(new ValidationResult[] { new ValidationResult("v2", Status.KO, "")}, new Date()));
+        support.notify(nodeStatus); // only OK status so no alert
+        assertEquals(1, alerts.size());
+        alerts.clear();
+
+        // add a DEGRADED status so one alert
+        nodeStatus.put("host1", new NodeStatus(new ValidationResult[] { new ValidationResult("v2", Status.DEGRADED, "")}, new Date()));
+        support.notify(nodeStatus); // only OK status so no alert
+        assertEquals(1, alerts.size());
+        alerts.clear();
+
+        // add a DEGRADED and a KO status so one alert
+        nodeStatus.put("host1", new NodeStatus(new ValidationResult[] { new ValidationResult("v2", Status.DEGRADED, "")}, new Date()));
+        nodeStatus.put("host2", new NodeStatus(new ValidationResult[] { new ValidationResult("v1", Status.KO, "")}, new Date()));
+        support.notify(nodeStatus); // only OK status so no alert
+        assertEquals(2, alerts.size());
+        alerts.clear();
+    }
+}

Added: incubator/sirona/trunk/core/src/test/java/org/apache/sirona/repositories/DefaultRepositoryTest.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/core/src/test/java/org/apache/sirona/repositories/DefaultRepositoryTest.java?rev=1711893&view=auto
==============================================================================
--- incubator/sirona/trunk/core/src/test/java/org/apache/sirona/repositories/DefaultRepositoryTest.java (added)
+++ incubator/sirona/trunk/core/src/test/java/org/apache/sirona/repositories/DefaultRepositoryTest.java Mon Nov  2 03:59:47 2015
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sirona.repositories;
+
+import org.apache.sirona.SironaException;
+import org.apache.sirona.alert.AlertListener;
+import org.apache.sirona.configuration.ioc.IoCs;
+import org.apache.sirona.status.NodeStatus;
+import org.apache.sirona.status.NodeStatusReporter;
+import org.apache.sirona.status.Status;
+import org.apache.sirona.status.ValidationResult;
+import org.apache.sirona.store.status.NodeStatusDataStore;
+import org.apache.sirona.store.status.PeriodicNodeStatusDataStore;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+
+public class DefaultRepositoryTest {
+    @Test
+    public void alerts() {
+        if (Alerter1.instance != null && Alerter1.instance.alerts != null) {
+            Alerter1.instance.alerts.clear();
+        }
+        if (Alerter2.instance != null && Alerter2.instance.alerts != null) {
+            Alerter2.instance.alerts.clear();
+        }
+
+        NodeStatusDataStore original;
+        try {
+            original = IoCs.findOrCreateInstance(NodeStatusDataStore.class);
+        } catch (final SironaException se) {
+            original = null;
+        }
+
+        final ForTestPeriodicNodeStatusDataStore testNodeStatusStore = new ForTestPeriodicNodeStatusDataStore();
+        IoCs.setSingletonInstance(NodeStatusDataStore.class, testNodeStatusStore);
+        try {
+            new DefaultRepository(); // create alerters
+            assertEquals("a-config-value", Alerter1.instance.config);
+            assertNotNull(Alerter1.instance);
+            assertEquals(0, Alerter1.instance.alerts.size());
+            assertNotNull(Alerter2.instance);
+            assertEquals(0, Alerter2.instance.alerts.size());
+
+            testNodeStatusStore.periodicTask();
+            assertEquals(1, Alerter1.instance.alerts.size());
+            assertEquals(1, Alerter2.instance.alerts.size());
+            assertSame(Alerter1.instance.alerts.iterator().next(), Alerter2.instance.alerts.iterator().next());
+        } finally {
+            IoCs.setSingletonInstance(NodeStatusDataStore.class, original);
+        }
+    }
+
+    public static class Alerter1 implements AlertListener {
+        private static volatile Alerter1 instance;
+
+        private final Collection<Alert> alerts = new ArrayList<Alert>();
+
+        private String config;
+
+        public Alerter1() {
+            instance = this;
+        }
+
+        public void onAlert(final Alert alert) {
+            alerts.add(alert);
+        }
+    }
+    public static class Alerter2 implements AlertListener {
+        private static volatile Alerter2 instance;
+        private final Collection<Alert> alerts = new ArrayList<Alert>();
+
+        public Alerter2() {
+            instance = this;
+        }
+
+        public void onAlert(final Alert alert) {
+            alerts.add(alert);
+        }
+    }
+
+    public static class ForTestPeriodicNodeStatusDataStore extends PeriodicNodeStatusDataStore {
+        @Override
+        protected int getPeriod(final String name) {
+            return -1;
+        }
+
+        @Override
+        public void periodicTask() {
+            super.periodicTask();
+        }
+
+        @Override
+        protected NodeStatusReporter newNodeStatusReporter() {
+            return new NodeStatusReporter() {
+                @Override
+                public synchronized NodeStatus computeStatus() {
+                    return new NodeStatus(new ValidationResult[] { new ValidationResult("ko", Status.KO, "")}, new Date());
+                }
+            };
+        }
+    }
+}

Modified: incubator/sirona/trunk/core/src/test/resources/sirona.properties
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/core/src/test/resources/sirona.properties?rev=1711893&r1=1711892&r2=1711893&view=diff
==============================================================================
--- incubator/sirona/trunk/core/src/test/resources/sirona.properties (original)
+++ incubator/sirona/trunk/core/src/test/resources/sirona.properties Mon Nov  2 03:59:47 2015
@@ -23,3 +23,8 @@ org.apache.sirona.goodbeer=true
 org.apache.sirona.configuration.IoCsTest$Field.field = field-value
 org.apache.sirona.configuration.IoCsTest$Method.method = method-value
 org.apache.sirona.configuration.IoCsTest$MethodNotField.methodNotField = method-value-again
+
+org.apache.sirona.alerters = test1,test2
+test1.class = org.apache.sirona.repositories.DefaultRepositoryTest$Alerter1
+test1.config = a-config-value
+test2.class = org.apache.sirona.repositories.DefaultRepositoryTest$Alerter2

Modified: incubator/sirona/trunk/plugins/hazelcast/agent/src/test/java/org/apache/sirona/plugin/hazelcast/BasicHazelcastTest.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/plugins/hazelcast/agent/src/test/java/org/apache/sirona/plugin/hazelcast/BasicHazelcastTest.java?rev=1711893&r1=1711892&r2=1711893&view=diff
==============================================================================
--- incubator/sirona/trunk/plugins/hazelcast/agent/src/test/java/org/apache/sirona/plugin/hazelcast/BasicHazelcastTest.java (original)
+++ incubator/sirona/trunk/plugins/hazelcast/agent/src/test/java/org/apache/sirona/plugin/hazelcast/BasicHazelcastTest.java Mon Nov  2 03:59:47 2015
@@ -16,6 +16,10 @@
  */
 package org.apache.sirona.plugin.hazelcast;
 
+import com.hazelcast.config.Config;
+import com.hazelcast.config.JoinConfig;
+import com.hazelcast.config.NetworkConfig;
+import com.hazelcast.config.TcpIpConfig;
 import com.hazelcast.core.Hazelcast;
 import com.hazelcast.core.HazelcastInstance;
 import com.hazelcast.core.LifecycleEvent;
@@ -33,6 +37,7 @@ import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.concurrent.CountDownLatch;
 
+import static java.util.Collections.singletonList;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
@@ -45,8 +50,18 @@ public class BasicHazelcastTest {
 
     @Test
     public void gauges() throws Throwable {
-        final HazelcastInstance instance1 = Hazelcast.newHazelcastInstance();
-        final HazelcastInstance instance2 = Hazelcast.newHazelcastInstance();
+        final Config config = new Config();
+        final NetworkConfig networkConfig = new NetworkConfig();
+        final JoinConfig join = new JoinConfig();
+        final TcpIpConfig tcpIpConfig = new TcpIpConfig();
+        tcpIpConfig.setEnabled(true);
+        tcpIpConfig.setMembers(singletonList("localhost"));
+        join.setTcpIpConfig(tcpIpConfig);
+        networkConfig.setJoin(join);
+        config.setNetworkConfig(networkConfig);
+
+        final HazelcastInstance instance1 = Hazelcast.newHazelcastInstance(config);
+        final HazelcastInstance instance2 = Hazelcast.newHazelcastInstance(config);
         HazelcastInstance instance3 = null;
 
         Map<Long, Double> members1 = null, partitions;
@@ -54,7 +69,7 @@ public class BasicHazelcastTest {
         final Gauge.LoaderHelper loader = new Gauge.LoaderHelper(false);
         try {
             Thread.sleep(250);
-            instance3 = Hazelcast.newHazelcastInstance();
+            instance3 = Hazelcast.newHazelcastInstance(config);
             Thread.sleep(250);
 
             members1 = gaugeValues("hazelcast-members-cluster");

Modified: incubator/sirona/trunk/server/store/cassandra/src/main/java/org/apache/sirona/cassandra/agent/status/CassandraStatusDataStore.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/server/store/cassandra/src/main/java/org/apache/sirona/cassandra/agent/status/CassandraStatusDataStore.java?rev=1711893&r1=1711892&r2=1711893&view=diff
==============================================================================
--- incubator/sirona/trunk/server/store/cassandra/src/main/java/org/apache/sirona/cassandra/agent/status/CassandraStatusDataStore.java (original)
+++ incubator/sirona/trunk/server/store/cassandra/src/main/java/org/apache/sirona/cassandra/agent/status/CassandraStatusDataStore.java Mon Nov  2 03:59:47 2015
@@ -56,6 +56,7 @@ public class CassandraStatusDataStore ex
                 return statuses;
             }
             statuses.put(marker, localStatus);
+            listeners.notify(statuses);
             return statuses;
         }
         return super.statuses();

Modified: incubator/sirona/trunk/server/store/cassandra/src/main/java/org/apache/sirona/cassandra/collector/status/CassandraCollectorNodeStatusDataStore.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/server/store/cassandra/src/main/java/org/apache/sirona/cassandra/collector/status/CassandraCollectorNodeStatusDataStore.java?rev=1711893&r1=1711892&r2=1711893&view=diff
==============================================================================
--- incubator/sirona/trunk/server/store/cassandra/src/main/java/org/apache/sirona/cassandra/collector/status/CassandraCollectorNodeStatusDataStore.java (original)
+++ incubator/sirona/trunk/server/store/cassandra/src/main/java/org/apache/sirona/cassandra/collector/status/CassandraCollectorNodeStatusDataStore.java Mon Nov  2 03:59:47 2015
@@ -31,7 +31,7 @@ import org.apache.sirona.configuration.i
 import org.apache.sirona.status.NodeStatus;
 import org.apache.sirona.status.Status;
 import org.apache.sirona.status.ValidationResult;
-import org.apache.sirona.store.status.CollectorNodeStatusDataStore;
+import org.apache.sirona.store.status.CollectorBaseNodeStatusDataStore;
 
 import java.util.Collection;
 import java.util.Date;
@@ -41,7 +41,7 @@ import java.util.TreeMap;
 
 import static org.apache.sirona.cassandra.collector.CassandraSirona.column;
 
-public class CassandraCollectorNodeStatusDataStore implements CollectorNodeStatusDataStore {
+public class CassandraCollectorNodeStatusDataStore extends CollectorBaseNodeStatusDataStore {
     private final Keyspace keyspace;
     private final String family;
     private final String markerFamily;
@@ -101,9 +101,9 @@ public class CassandraCollectorNodeStatu
         return statuses;
     }
 
-    @Override // TODO: like clearCounters() see if it should do something or not
-    public void reset() {
-        // no-op
+    @Override
+    public void reset() { // TODO: like clearCounters() see if it should do something or not
+        super.reset();
     }
 
     @Override

Modified: incubator/sirona/trunk/server/websocket-server/src/test/java/org/apache/sirona/websocket/WebSocketTest.java
URL: http://svn.apache.org/viewvc/incubator/sirona/trunk/server/websocket-server/src/test/java/org/apache/sirona/websocket/WebSocketTest.java?rev=1711893&r1=1711892&r2=1711893&view=diff
==============================================================================
--- incubator/sirona/trunk/server/websocket-server/src/test/java/org/apache/sirona/websocket/WebSocketTest.java (original)
+++ incubator/sirona/trunk/server/websocket-server/src/test/java/org/apache/sirona/websocket/WebSocketTest.java Mon Nov  2 03:59:47 2015
@@ -19,6 +19,7 @@ package org.apache.sirona.websocket;
 import org.apache.catalina.startup.Constants;
 import org.apache.johnzon.websocket.mapper.JohnzonTextDecoder;
 import org.apache.sirona.Role;
+import org.apache.sirona.alert.AlertListener;
 import org.apache.sirona.configuration.ioc.IoCs;
 import org.apache.sirona.counters.Counter;
 import org.apache.sirona.counters.Unit;
@@ -50,6 +51,7 @@ import org.junit.runner.RunWith;
 
 import java.net.URL;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Date;
 import java.util.Iterator;
 import java.util.Map;
@@ -122,7 +124,8 @@ public class WebSocketTest {
                     };
                 }
             }),
-            new InMemoryPathTrackingDataStore()
+            new InMemoryPathTrackingDataStore(),
+            Collections.<AlertListener>emptyList()
         ) {};
         clientRepo.addGauge(new Gauge() {
             @Override