You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ab...@apache.org on 2018/03/29 19:12:14 UTC

[1/2] lucene-solr:jira/solr-12095: First cut, some test are still failing.

Repository: lucene-solr
Updated Branches:
  refs/heads/jira/solr-12095 [created] 059f495e3


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestNodeLostTrigger.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestNodeLostTrigger.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestNodeLostTrigger.java
index 56a8176..33c2efa 100644
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestNodeLostTrigger.java
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestNodeLostTrigger.java
@@ -28,12 +28,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.apache.solr.client.solrj.cloud.SolrCloudManager;
 import org.apache.solr.cloud.autoscaling.ActionContext;
 import org.apache.solr.cloud.autoscaling.AutoScaling;
 import org.apache.solr.cloud.autoscaling.NodeLostTrigger;
 import org.apache.solr.cloud.autoscaling.TriggerAction;
 import org.apache.solr.cloud.autoscaling.TriggerEvent;
+import org.apache.solr.cloud.autoscaling.TriggerValidationException;
 import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.core.SolrResourceLoader;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -75,7 +78,8 @@ public class TestNodeLostTrigger extends SimSolrCloudTestCase {
     long waitForSeconds = 1 + random().nextInt(5);
     Map<String, Object> props = createTriggerProps(waitForSeconds);
 
-    try (NodeLostTrigger trigger = new NodeLostTrigger("node_lost_trigger", props, cluster.getLoader(), cluster)) {
+    try (NodeLostTrigger trigger = new NodeLostTrigger("node_lost_trigger")) {
+      trigger.configure(cluster.getLoader(), cluster, props);
       trigger.setProcessor(noFirstRunProcessor);
       trigger.run();
       Iterator<String> it = cluster.getLiveNodesSet().get().iterator();
@@ -120,7 +124,8 @@ public class TestNodeLostTrigger extends SimSolrCloudTestCase {
 
     // remove a node but add it back before the waitFor period expires
     // and assert that the trigger doesn't fire at all
-    try (NodeLostTrigger trigger = new NodeLostTrigger("node_lost_trigger", props, cluster.getLoader(), cluster)) {
+    try (NodeLostTrigger trigger = new NodeLostTrigger("node_lost_trigger")) {
+      trigger.configure(cluster.getLoader(), cluster, props);
       final long waitTime = 2;
       props.put("waitFor", waitTime);
       trigger.setProcessor(noFirstRunProcessor);
@@ -175,7 +180,8 @@ public class TestNodeLostTrigger extends SimSolrCloudTestCase {
     action.put("name", "testActionInit");
     action.put("class", AssertInitTriggerAction.class.getName());
     actions.add(action);
-    try (NodeLostTrigger trigger = new NodeLostTrigger("node_added_trigger", props, cluster.getLoader(), cluster)) {
+    try (NodeLostTrigger trigger = new NodeLostTrigger("node_added_trigger")) {
+      trigger.configure(cluster.getLoader(), cluster, props);
       assertEquals(true, actionConstructorCalled.get());
       assertEquals(false, actionInitCalled.get());
       assertEquals(false, actionCloseCalled.get());
@@ -192,6 +198,16 @@ public class TestNodeLostTrigger extends SimSolrCloudTestCase {
     }
 
     @Override
+    public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException {
+
+    }
+
+    @Override
+    public void init() {
+      actionInitCalled.compareAndSet(false, true);
+    }
+
+    @Override
     public String getName() {
       return "";
     }
@@ -205,17 +221,13 @@ public class TestNodeLostTrigger extends SimSolrCloudTestCase {
     public void close() throws IOException {
       actionCloseCalled.compareAndSet(false, true);
     }
-
-    @Override
-    public void init(Map<String, String> args) {
-      actionInitCalled.compareAndSet(false, true);
-    }
   }
 
   @Test
   public void testListenerAcceptance() throws Exception {
     Map<String, Object> props = createTriggerProps(0);
-    try (NodeLostTrigger trigger = new NodeLostTrigger("node_added_trigger", props, cluster.getLoader(), cluster)) {
+    try (NodeLostTrigger trigger = new NodeLostTrigger("node_added_trigger")) {
+      trigger.configure(cluster.getLoader(), cluster, props);
       trigger.setProcessor(noFirstRunProcessor);
 
       String newNode = cluster.simAddNode();
@@ -258,7 +270,8 @@ public class TestNodeLostTrigger extends SimSolrCloudTestCase {
     // remove a node but update the trigger before the waitFor period expires
     // and assert that the new trigger still fires
 
-    NodeLostTrigger trigger = new NodeLostTrigger("node_lost_trigger", props, cluster.getLoader(), cluster);
+    NodeLostTrigger trigger = new NodeLostTrigger("node_lost_trigger");
+    trigger.configure(cluster.getLoader(), cluster, props);
     trigger.setProcessor(noFirstRunProcessor);
     trigger.run();
 
@@ -268,7 +281,8 @@ public class TestNodeLostTrigger extends SimSolrCloudTestCase {
     trigger.run(); // this run should detect the lost node
     trigger.close(); // close the old trigger
 
-    try (NodeLostTrigger newTrigger = new NodeLostTrigger("some_different_name", props, cluster.getLoader(), cluster))  {
+    try (NodeLostTrigger newTrigger = new NodeLostTrigger("some_different_name"))  {
+      newTrigger.configure(cluster.getLoader(), cluster, props);
       try {
         newTrigger.restoreState(trigger);
         fail("Trigger should only be able to restore state from an old trigger of the same name");
@@ -277,7 +291,8 @@ public class TestNodeLostTrigger extends SimSolrCloudTestCase {
       }
     }
 
-    try (NodeLostTrigger newTrigger = new NodeLostTrigger("node_lost_trigger", props, cluster.getLoader(), cluster)) {
+    try (NodeLostTrigger newTrigger = new NodeLostTrigger("node_lost_trigger")) {
+      newTrigger.configure(cluster.getLoader(), cluster, props);
       AtomicBoolean fired = new AtomicBoolean(false);
       AtomicReference<TriggerEvent> eventRef = new AtomicReference<>();
       newTrigger.setProcessor(event -> {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestTriggerIntegration.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestTriggerIntegration.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestTriggerIntegration.java
index 974e672..7a4e2f6 100644
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestTriggerIntegration.java
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestTriggerIntegration.java
@@ -51,10 +51,12 @@ import org.apache.solr.cloud.autoscaling.TriggerEvent;
 import org.apache.solr.cloud.autoscaling.TriggerEventQueue;
 import org.apache.solr.cloud.autoscaling.TriggerListenerBase;
 import org.apache.solr.cloud.autoscaling.CapturedEvent;
+import org.apache.solr.cloud.autoscaling.TriggerValidationException;
 import org.apache.solr.common.cloud.LiveNodesListener;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.util.LogLevel;
 import org.apache.solr.util.TimeOut;
 import org.junit.Before;
@@ -569,11 +571,12 @@ public class TestTriggerIntegration extends SimSolrCloudTestCase {
     }
 
     @Override
-    public void init(Map<String, String> args) {
+    public void init() throws Exception {
       log.info("TestTriggerAction init");
+      super.init();
       actionInitCalled.countDown();
-      super.init(args);
     }
+
   }
 
   public static class TestEventQueueAction extends TriggerActionBase {
@@ -598,10 +601,10 @@ public class TestTriggerIntegration extends SimSolrCloudTestCase {
     }
 
     @Override
-    public void init(Map<String, String> args) {
+    public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> args) throws TriggerValidationException {
       log.debug("TestTriggerAction init");
       actionInitCalled.countDown();
-      super.init(args);
+      super.configure(loader, cloudManager, args);
     }
   }
 
@@ -759,10 +762,10 @@ public class TestTriggerIntegration extends SimSolrCloudTestCase {
     }
 
     @Override
-    public void init(Map<String, String> args) {
+    public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> args) throws TriggerValidationException {
       log.info("TestEventMarkerAction init");
       actionInitCalled.countDown();
-      super.init(args);
+      super.configure(loader, cloudManager, args);
     }
   }
 


[2/2] lucene-solr:jira/solr-12095: First cut, some test are still failing.

Posted by ab...@apache.org.
First cut, some test are still failing.


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/059f495e
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/059f495e
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/059f495e

Branch: refs/heads/jira/solr-12095
Commit: 059f495e3765e164cc330dd6ac1cf6dd6a5e6e1f
Parents: 668b817
Author: Andrzej Bialecki <ab...@apache.org>
Authored: Thu Mar 29 21:11:31 2018 +0200
Committer: Andrzej Bialecki <ab...@apache.org>
Committed: Thu Mar 29 21:11:31 2018 +0200

----------------------------------------------------------------------
 .../solr/cloud/autoscaling/AutoScaling.java     | 40 +++++++--
 .../cloud/autoscaling/AutoScalingHandler.java   | 18 ++++
 .../cloud/autoscaling/ComputePlanAction.java    |  8 +-
 .../autoscaling/InactiveShardPlanAction.java    |  7 +-
 .../solr/cloud/autoscaling/MetricTrigger.java   | 22 +++--
 .../cloud/autoscaling/NodeAddedTrigger.java     | 22 ++---
 .../solr/cloud/autoscaling/NodeLostTrigger.java | 20 ++---
 .../autoscaling/OverseerTriggerThread.java      | 12 ++-
 .../cloud/autoscaling/ScheduledTrigger.java     | 37 ++++----
 .../cloud/autoscaling/ScheduledTriggers.java    |  2 +-
 .../cloud/autoscaling/SearchRateTrigger.java    | 28 +++----
 .../solr/cloud/autoscaling/TriggerAction.java   | 24 +++++-
 .../cloud/autoscaling/TriggerActionBase.java    | 41 +++++++--
 .../solr/cloud/autoscaling/TriggerBase.java     | 60 ++++++++-----
 .../solr/cloud/autoscaling/TriggerUtils.java    | 88 ++++++++++++++++++++
 .../autoscaling/TriggerValidationException.java | 68 +++++++++++++++
 .../autoscaling/ComputePlanActionTest.java      | 16 ++--
 .../autoscaling/ExecutePlanActionTest.java      |  9 +-
 .../cloud/autoscaling/MetricTriggerTest.java    |  6 +-
 .../cloud/autoscaling/NodeAddedTriggerTest.java | 45 +++++-----
 .../cloud/autoscaling/NodeLostTriggerTest.java  | 44 ++++++----
 .../cloud/autoscaling/ScheduledTriggerTest.java | 12 +--
 .../autoscaling/SearchRateTriggerTest.java      |  4 +-
 .../autoscaling/TriggerIntegrationTest.java     | 31 ++++---
 .../autoscaling/sim/TestComputePlanAction.java  | 18 ++--
 .../autoscaling/sim/TestExecutePlanAction.java  |  2 +-
 .../autoscaling/sim/TestNodeAddedTrigger.java   | 43 +++++++---
 .../autoscaling/sim/TestNodeLostTrigger.java    | 39 ++++++---
 .../autoscaling/sim/TestTriggerIntegration.java | 15 ++--
 29 files changed, 581 insertions(+), 200 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/java/org/apache/solr/cloud/autoscaling/AutoScaling.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/AutoScaling.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/AutoScaling.java
index a3a9aa8..68282a7 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/AutoScaling.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/AutoScaling.java
@@ -106,10 +106,19 @@ public class AutoScaling {
     void restoreState();
 
     /**
+     * Called when trigger is created but before it's initialized or scheduled for use.
+     * This method should also verify that the trigger configuration parameters are correct. It may
+     * be called multiple times.
+     * @param properties configuration properties
+     * @throws TriggerValidationException contains details of invalid configuration parameters.
+     */
+    void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException;
+
+    /**
      * Called before a trigger is scheduled. Any heavy object creation or initialisation should
      * be done in this method instead of the Trigger's constructor.
      */
-    void init();
+    void init() throws Exception;
   }
 
   /**
@@ -118,7 +127,7 @@ public class AutoScaling {
   public static abstract class TriggerFactory implements Closeable {
     protected boolean isClosed = false;
 
-    public abstract Trigger create(TriggerEventType type, String name, Map<String, Object> props);
+    public abstract Trigger create(TriggerEventType type, String name, Map<String, Object> props) throws TriggerValidationException;
 
     @Override
     public void close() throws IOException {
@@ -144,24 +153,38 @@ public class AutoScaling {
     }
 
     @Override
-    public synchronized Trigger create(TriggerEventType type, String name, Map<String, Object> props) {
+    public synchronized Trigger create(TriggerEventType type, String name, Map<String, Object> props) throws TriggerValidationException {
       if (isClosed) {
         throw new AlreadyClosedException("TriggerFactory has already been closed, cannot create new triggers");
       }
+      if (type == null) {
+        throw new IllegalArgumentException("Trigger type must not be null");
+      }
+      if (name == null || name.isEmpty()) {
+        throw new IllegalArgumentException("Trigger name must not be empty");
+      }
+      Trigger t;
       switch (type) {
         case NODEADDED:
-          return new NodeAddedTrigger(name, props, loader, cloudManager);
+          t = new NodeAddedTrigger(name);
+          break;
         case NODELOST:
-          return new NodeLostTrigger(name, props, loader, cloudManager);
+          t = new NodeLostTrigger(name);
+        break;
         case SEARCHRATE:
-          return new SearchRateTrigger(name, props, loader, cloudManager);
+          t = new SearchRateTrigger(name);
+        break;
         case METRIC:
-          return new MetricTrigger(name, props, loader, cloudManager);
+          t = new MetricTrigger(name);
+        break;
         case SCHEDULED:
-          return new ScheduledTrigger(name, props, loader, cloudManager);
+          t = new ScheduledTrigger(name);
+        break;
         default:
           throw new IllegalArgumentException("Unknown event type: " + type + " in trigger: " + name);
       }
+      t.configure(loader, cloudManager, props);
+      return t;
     }
 
   }
@@ -210,4 +233,5 @@ public class AutoScaling {
           "    }";
 
   public static final Map<String, Object> SCHEDULED_MAINTENANCE_TRIGGER_PROPS = (Map) Utils.fromJSONString(SCHEDULED_MAINTENANCE_TRIGGER_DSL);
+
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/java/org/apache/solr/cloud/autoscaling/AutoScalingHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/AutoScalingHandler.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/AutoScalingHandler.java
index 5126e2e..f93a828 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/AutoScalingHandler.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/AutoScalingHandler.java
@@ -50,6 +50,7 @@ import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.AutoScalingParams;
 import org.apache.solr.common.params.CollectionAdminParams;
 import org.apache.solr.common.util.CommandOperation;
+import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.SolrResourceLoader;
@@ -79,6 +80,7 @@ public class AutoScalingHandler extends RequestHandlerBase implements Permission
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
   protected final SolrCloudManager cloudManager;
   protected final SolrResourceLoader loader;
+  protected final AutoScaling.TriggerFactory triggerFactory;
   private final List<Map<String, String>> DEFAULT_ACTIONS = new ArrayList<>(3);
   private static Set<String> singletonCommands = Stream.of("set-cluster-preferences", "set-cluster-policy")
       .collect(collectingAndThen(toSet(), Collections::unmodifiableSet));
@@ -88,6 +90,7 @@ public class AutoScalingHandler extends RequestHandlerBase implements Permission
   public AutoScalingHandler(SolrCloudManager cloudManager, SolrResourceLoader loader) {
     this.cloudManager = cloudManager;
     this.loader = loader;
+    this.triggerFactory = new AutoScaling.TriggerFactoryImpl(loader, cloudManager);
     this.timeSource = cloudManager.getTimeSource();
     Map<String, String> map = new HashMap<>(2);
     map.put(NAME, "compute_plan");
@@ -531,6 +534,21 @@ public class AutoScalingHandler extends RequestHandlerBase implements Permission
       }
     }
     AutoScalingConfig.TriggerConfig trigger = new AutoScalingConfig.TriggerConfig(triggerName, opCopy.getValuesExcluding("name"));
+    // validate trigger config
+    AutoScaling.Trigger t = null;
+    try {
+      t = triggerFactory.create(trigger.event, trigger.name, trigger.properties);
+    } catch (TriggerValidationException e) {
+      op.addError("Error validating trigger config " + trigger.name + ": " + e.getDetails());
+      return currentConfig;
+    } catch (Exception e) {
+      op.addError("Error validating trigger config " + trigger.name + ": " + e.toString());
+      return currentConfig;
+    } finally {
+      if (t != null) {
+        IOUtils.closeQuietly(t);
+      }
+    }
     currentConfig = currentConfig.withTriggerConfig(trigger);
     // check that there's a default SystemLogListener, unless user specified another one
     return withSystemLogListener(currentConfig, triggerName);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/java/org/apache/solr/cloud/autoscaling/ComputePlanAction.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/ComputePlanAction.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/ComputePlanAction.java
index dc3cfd5..55f432c 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/ComputePlanAction.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/ComputePlanAction.java
@@ -40,6 +40,7 @@ import org.apache.solr.common.params.AutoScalingParams;
 import org.apache.solr.common.params.CollectionParams;
 import org.apache.solr.common.params.CoreAdminParams;
 import org.apache.solr.common.util.StrUtils;
+import org.apache.solr.core.SolrResourceLoader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -55,10 +56,11 @@ public class ComputePlanAction extends TriggerActionBase {
 
   Set<String> collections = new HashSet<>();
 
+
   @Override
-  public void init(Map<String, String> args) {
-    super.init(args);
-    String colString = args.get("collections");
+  public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException {
+    super.configure(loader, cloudManager, properties);
+    String colString = (String) properties.get("collections");
     if (colString != null && !colString.isEmpty()) {
       collections.addAll(StrUtils.splitSmart(colString, ','));
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/java/org/apache/solr/cloud/autoscaling/InactiveShardPlanAction.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/InactiveShardPlanAction.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/InactiveShardPlanAction.java
index 5e962a8..2cbe487 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/InactiveShardPlanAction.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/InactiveShardPlanAction.java
@@ -29,6 +29,7 @@ import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.common.cloud.ClusterState;
 import org.apache.solr.common.cloud.Slice;
 import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.core.SolrResourceLoader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -49,9 +50,9 @@ public class InactiveShardPlanAction extends TriggerActionBase {
   private int cleanupTTL;
 
   @Override
-  public void init(Map<String, String> args) {
-    super.init(args);
-    String cleanupStr = args.getOrDefault(TTL_PROP, String.valueOf(DEFAULT_TTL_SECONDS));
+  public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException {
+    super.configure(loader, cloudManager, properties);
+    String cleanupStr = String.valueOf(properties.getOrDefault(TTL_PROP, String.valueOf(DEFAULT_TTL_SECONDS)));
     try {
       cleanupTTL = Integer.parseInt(cleanupStr);
     } catch (Exception e) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/java/org/apache/solr/cloud/autoscaling/MetricTrigger.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/MetricTrigger.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/MetricTrigger.java
index e0dd253..9fdf8dc 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/MetricTrigger.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/MetricTrigger.java
@@ -53,21 +53,31 @@ import static org.apache.solr.common.params.AutoScalingParams.PREFERRED_OP;
 public class MetricTrigger extends TriggerBase {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
-  private final String metric;
-  private final Number above, below;
-  private final String collection, shard, node, preferredOp;
+  private String metric;
+  private Number above, below;
+  private String collection, shard, node, preferredOp;
 
   private final Map<String, Long> lastNodeEvent = new ConcurrentHashMap<>();
 
-  public MetricTrigger(String name, Map<String, Object> properties, SolrResourceLoader loader, SolrCloudManager cloudManager) {
-    super(TriggerEventType.METRIC, name, properties, loader, cloudManager);
+  public MetricTrigger(String name) {
+    super(TriggerEventType.METRIC, name);
+    TriggerUtils.requiredProperties(requiredProperties, validProperties, METRIC);
+    TriggerUtils.validProperties(validProperties, ABOVE, BELOW, PREFERRED_OP,
+        AutoScalingParams.COLLECTION,
+        AutoScalingParams.SHARD,
+        AutoScalingParams.NODE);
+  }
+
+  @Override
+  public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException {
+    super.configure(loader, cloudManager, properties);
     this.metric = (String) properties.get(METRIC);
     this.above = (Number) properties.get(ABOVE);
     this.below = (Number) properties.get(BELOW);
     this.collection = (String) properties.getOrDefault(AutoScalingParams.COLLECTION, Policy.ANY);
     shard = (String) properties.getOrDefault(AutoScalingParams.SHARD, Policy.ANY);
     if (collection.equals(Policy.ANY) && !shard.equals(Policy.ANY)) {
-      throw new IllegalArgumentException("When 'shard' is other than #ANY then collection name must be also other than #ANY");
+      throw new TriggerValidationException("shard", "When 'shard' is other than #ANY then collection name must be also other than #ANY");
     }
     node = (String) properties.getOrDefault(AutoScalingParams.NODE, Policy.ANY);
     preferredOp = (String) properties.getOrDefault(PREFERRED_OP, CollectionParams.CollectionAction.MOVEREPLICA.toLower());

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/java/org/apache/solr/cloud/autoscaling/NodeAddedTrigger.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/NodeAddedTrigger.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/NodeAddedTrigger.java
index ad89f2a..d83f8b9 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/NodeAddedTrigger.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/NodeAddedTrigger.java
@@ -44,22 +44,20 @@ import org.slf4j.LoggerFactory;
 public class NodeAddedTrigger extends TriggerBase {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
-  private Set<String> lastLiveNodes;
+  private Set<String> lastLiveNodes = new HashSet<>();
 
   private Map<String, Long> nodeNameVsTimeAdded = new HashMap<>();
 
-  public NodeAddedTrigger(String name, Map<String, Object> properties,
-                          SolrResourceLoader loader,
-                          SolrCloudManager cloudManager) {
-    super(TriggerEventType.NODEADDED, name, properties, loader, cloudManager);
-    lastLiveNodes = new HashSet<>(cloudManager.getClusterStateProvider().getLiveNodes());
-    log.debug("Initial livenodes: {}", lastLiveNodes);
-    log.debug("NodeAddedTrigger {} instantiated with properties: {}", name, properties);
+  public NodeAddedTrigger(String name) {
+    super(TriggerEventType.NODEADDED, name);
   }
 
   @Override
-  public void init() {
+  public void init() throws Exception {
     super.init();
+    lastLiveNodes = new HashSet<>(cloudManager.getClusterStateProvider().getLiveNodes());
+    log.debug("Initial livenodes: {}", lastLiveNodes);
+    log.debug("NodeAddedTrigger {} instantiated with properties: {}", name, properties);
     // pick up added nodes for which marker paths were created
     try {
       List<String> added = stateManager.listData(ZkStateReader.SOLR_AUTOSCALING_NODE_ADDED_PATH);
@@ -85,8 +83,10 @@ public class NodeAddedTrigger extends TriggerBase {
     if (old instanceof NodeAddedTrigger) {
       NodeAddedTrigger that = (NodeAddedTrigger) old;
       assert this.name.equals(that.name);
-      this.lastLiveNodes = new HashSet<>(that.lastLiveNodes);
-      this.nodeNameVsTimeAdded = new HashMap<>(that.nodeNameVsTimeAdded);
+      this.lastLiveNodes.clear();
+      this.lastLiveNodes.addAll(that.lastLiveNodes);
+      this.nodeNameVsTimeAdded.clear();
+      this.nodeNameVsTimeAdded.putAll(that.nodeNameVsTimeAdded);
     } else  {
       throw new SolrException(SolrException.ErrorCode.INVALID_STATE,
           "Unable to restore state from an unknown type of trigger");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/java/org/apache/solr/cloud/autoscaling/NodeLostTrigger.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/NodeLostTrigger.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/NodeLostTrigger.java
index 1e7aec5..5bf243f 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/NodeLostTrigger.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/NodeLostTrigger.java
@@ -43,21 +43,19 @@ import org.slf4j.LoggerFactory;
 public class NodeLostTrigger extends TriggerBase {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
-  private Set<String> lastLiveNodes;
+  private Set<String> lastLiveNodes = new HashSet<>();
 
   private Map<String, Long> nodeNameVsTimeRemoved = new HashMap<>();
 
-  public NodeLostTrigger(String name, Map<String, Object> properties,
-                         SolrResourceLoader loader,
-                         SolrCloudManager dataProvider) {
-    super(TriggerEventType.NODELOST, name, properties, loader, dataProvider);
-    lastLiveNodes = new HashSet<>(dataProvider.getClusterStateProvider().getLiveNodes());
-    log.debug("Initial livenodes: {}", lastLiveNodes);
+  public NodeLostTrigger(String name) {
+    super(TriggerEventType.NODELOST, name);
   }
 
   @Override
-  public void init() {
+  public void init() throws Exception {
     super.init();
+    lastLiveNodes = new HashSet<>(cloudManager.getClusterStateProvider().getLiveNodes());
+    log.debug("Initial livenodes: {}", lastLiveNodes);
     // pick up lost nodes for which marker paths were created
     try {
       List<String> lost = stateManager.listData(ZkStateReader.SOLR_AUTOSCALING_NODE_LOST_PATH);
@@ -82,8 +80,10 @@ public class NodeLostTrigger extends TriggerBase {
     if (old instanceof NodeLostTrigger) {
       NodeLostTrigger that = (NodeLostTrigger) old;
       assert this.name.equals(that.name);
-      this.lastLiveNodes = new HashSet<>(that.lastLiveNodes);
-      this.nodeNameVsTimeRemoved = new HashMap<>(that.nodeNameVsTimeRemoved);
+      this.lastLiveNodes.clear();
+      this.lastLiveNodes.addAll(that.lastLiveNodes);
+      this.nodeNameVsTimeRemoved.clear();
+      this.nodeNameVsTimeRemoved.putAll(that.nodeNameVsTimeRemoved);
     } else  {
       throw new SolrException(SolrException.ErrorCode.INVALID_STATE,
           "Unable to restore state from an unknown type of trigger");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/java/org/apache/solr/cloud/autoscaling/OverseerTriggerThread.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/OverseerTriggerThread.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/OverseerTriggerThread.java
index f97372d..d3fc5b6 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/OverseerTriggerThread.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/OverseerTriggerThread.java
@@ -226,7 +226,11 @@ public class OverseerTriggerThread implements Runnable, SolrCloseable {
           if (entry.getValue().getEventType().equals(TriggerEventType.NODEADDED)) {
             cleanOldNodeAddedMarkers = false;
           }
-          scheduledTriggers.add(entry.getValue());
+          try {
+            scheduledTriggers.add(entry.getValue());
+          } catch (Exception e) {
+            log.warn("Exception initializing trigger " + entry.getKey() + ", configuration ignored", e);
+          }
         }
       } catch (AlreadyClosedException e) {
         // this _should_ mean that we're closing, complain loudly if that's not the case
@@ -378,7 +382,11 @@ public class OverseerTriggerThread implements Runnable, SolrCloseable {
       AutoScalingConfig.TriggerConfig cfg = entry.getValue();
       TriggerEventType eventType = cfg.event;
       String triggerName = entry.getKey();
-      triggerMap.put(triggerName, triggerFactory.create(eventType, triggerName, cfg.properties));
+      try {
+        triggerMap.put(triggerName, triggerFactory.create(eventType, triggerName, cfg.properties));
+      } catch (TriggerValidationException e) {
+        log.warn("Error in trigger '" + triggerName + "' configuration, trigger config ignored: " + cfg, e);
+      }
     }
     return triggerMap;
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/java/org/apache/solr/cloud/autoscaling/ScheduledTrigger.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/ScheduledTrigger.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/ScheduledTrigger.java
index bbc9c30..5e25542 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/ScheduledTrigger.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/ScheduledTrigger.java
@@ -33,6 +33,7 @@ import java.util.concurrent.TimeUnit;
 import org.apache.solr.client.solrj.cloud.SolrCloudManager;
 import org.apache.solr.client.solrj.cloud.autoscaling.TriggerEventType;
 import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.AutoScalingParams;
 import org.apache.solr.common.util.TimeSource;
 import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.util.DateMathParser;
@@ -52,20 +53,25 @@ public class ScheduledTrigger extends TriggerBase {
   private static final String LAST_RUN_AT = "lastRunAt";
   static final String ACTUAL_EVENT_TIME = "actualEventTime";
 
-  private final String everyStr;
+  private String everyStr;
 
-  private final String graceDurationStr;
+  private String graceDurationStr;
 
-  private final String preferredOp;
+  private String preferredOp;
 
-  private final TimeZone timeZone;
+  private TimeZone timeZone;
 
   private Instant lastRunAt;
 
-  public ScheduledTrigger(String name, Map<String, Object> properties,
-                          SolrResourceLoader loader, SolrCloudManager cloudManager) {
-    super(TriggerEventType.SCHEDULED, name, properties, loader, cloudManager);
+  public ScheduledTrigger(String name) {
+    super(TriggerEventType.SCHEDULED, name);
+    TriggerUtils.requiredProperties(requiredProperties, validProperties, "startTime");
+    TriggerUtils.validProperties(validProperties, "timeZone", "every", "graceDuration", AutoScalingParams.PREFERRED_OP);
+  }
 
+  @Override
+  public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException {
+    super.configure(loader, cloudManager, properties);
     String timeZoneStr = (String) properties.get("timeZone");
     this.timeZone = TimeZoneUtils.parseTimezone(timeZoneStr); // defaults to UTC
 
@@ -90,18 +96,17 @@ public class ScheduledTrigger extends TriggerBase {
     this.lastRunAt = startTime;
   }
 
-  private Instant parseStartTime(Date now, String startTimeStr, String timeZoneStr) {
-    if (startTimeStr == null) {
-      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Parameter 'startTime' cannot be null");
-    }
+  private Instant parseStartTime(Date now, String startTimeStr, String timeZoneStr) throws TriggerValidationException {
     try {
       // try parsing startTime as an ISO-8601 date time string
       return DateMathParser.parseMath(now, startTimeStr).toInstant();
     } catch (SolrException e) {
-      if (e.code() != SolrException.ErrorCode.BAD_REQUEST.code)  throw e;
+      if (e.code() != SolrException.ErrorCode.BAD_REQUEST.code) {
+        throw new TriggerValidationException("startTime", "error parsing value '" + startTimeStr + "': " + e.toString());
+      }
     }
     if (timeZoneStr == null)  {
-      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+      throw new TriggerValidationException("timeZone",
           "Either 'startTime' should be an ISO-8601 date time string or 'timeZone' must be not be null");
     }
     TimeZone timeZone = TimeZone.getTimeZone(timeZoneStr);
@@ -111,7 +116,11 @@ public class ScheduledTrigger extends TriggerBase {
         .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
         .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
         .toFormatter(Locale.ROOT).withZone(timeZone.toZoneId());
-    return Instant.from(dateTimeFormatter.parse(startTimeStr));
+    try {
+      return Instant.from(dateTimeFormatter.parse(startTimeStr));
+    } catch (Exception e) {
+      throw new TriggerValidationException("startTime", "error parsing startTime '" + startTimeStr + "': " + e.toString());
+    }
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/java/org/apache/solr/cloud/autoscaling/ScheduledTriggers.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/ScheduledTriggers.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/ScheduledTriggers.java
index 0e21b04..983b8a5 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/ScheduledTriggers.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/ScheduledTriggers.java
@@ -196,7 +196,7 @@ public class ScheduledTriggers implements Closeable {
    * @param newTrigger the trigger to be managed
    * @throws AlreadyClosedException if this class has already been closed
    */
-  public synchronized void add(AutoScaling.Trigger newTrigger) {
+  public synchronized void add(AutoScaling.Trigger newTrigger) throws Exception {
     if (isClosed) {
       throw new AlreadyClosedException("ScheduledTriggers has been closed and cannot be used anymore");
     }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/java/org/apache/solr/cloud/autoscaling/SearchRateTrigger.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/SearchRateTrigger.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/SearchRateTrigger.java
index 814c676..ffe7ba1 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/SearchRateTrigger.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/SearchRateTrigger.java
@@ -49,43 +49,43 @@ import org.slf4j.LoggerFactory;
 public class SearchRateTrigger extends TriggerBase {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
-  private final String handler;
-  private final String collection;
-  private final String shard;
-  private final String node;
-  private final double rate;
+  private String handler;
+  private String collection;
+  private String shard;
+  private String node;
+  private double rate;
   private final Map<String, Long> lastCollectionEvent = new ConcurrentHashMap<>();
   private final Map<String, Long> lastNodeEvent = new ConcurrentHashMap<>();
   private final Map<String, Long> lastShardEvent = new ConcurrentHashMap<>();
   private final Map<String, Long> lastReplicaEvent = new ConcurrentHashMap<>();
   private final Map<String, Object> state = new HashMap<>();
 
-  public SearchRateTrigger(String name, Map<String, Object> properties,
-                           SolrResourceLoader loader,
-                           SolrCloudManager cloudManager) {
-    super(TriggerEventType.SEARCHRATE, name, properties, loader, cloudManager);
+  public SearchRateTrigger(String name) {
+    super(TriggerEventType.SEARCHRATE, name);
     this.state.put("lastCollectionEvent", lastCollectionEvent);
     this.state.put("lastNodeEvent", lastNodeEvent);
     this.state.put("lastShardEvent", lastShardEvent);
     this.state.put("lastReplicaEvent", lastReplicaEvent);
+    TriggerUtils.requiredProperties(requiredProperties, validProperties, "rate");
+  }
 
+  @Override
+  public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException {
+    super.configure(loader, cloudManager, properties);
     // parse config options
     collection = (String)properties.getOrDefault(AutoScalingParams.COLLECTION, Policy.ANY);
     shard = (String)properties.getOrDefault(AutoScalingParams.SHARD, Policy.ANY);
     if (collection.equals(Policy.ANY) && !shard.equals(Policy.ANY)) {
-      throw new IllegalArgumentException("When 'shard' is other than #ANY then collection name must be also other than #ANY");
+      throw new TriggerValidationException("shard", "When 'shard' is other than #ANY then collection name must be also other than #ANY");
     }
     node = (String)properties.getOrDefault(AutoScalingParams.NODE, Policy.ANY);
     handler = (String)properties.getOrDefault(AutoScalingParams.HANDLER, "/select");
 
-    if (properties.get("rate") == null) {
-      throw new IllegalArgumentException("No 'rate' specified in configuration");
-    }
     String rateString = String.valueOf(properties.get("rate"));
     try {
       rate = Double.parseDouble(rateString);
     } catch (Exception e) {
-      throw new IllegalArgumentException("Invalid 'rate' configuration value: '" + rateString + "'", e);
+      throw new TriggerValidationException("rate", "Invalid 'rate' configuration value: '" + rateString + "': " + e.toString());
     }
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerAction.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerAction.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerAction.java
index df2022e..b873ee6 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerAction.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerAction.java
@@ -18,13 +18,33 @@
 package org.apache.solr.cloud.autoscaling;
 
 import java.io.Closeable;
+import java.util.Map;
 
-import org.apache.solr.util.plugin.MapInitializedPlugin;
+import org.apache.solr.client.solrj.cloud.SolrCloudManager;
+import org.apache.solr.core.SolrResourceLoader;
 
 /**
  * Interface for actions performed in response to a trigger being activated
  */
-public interface TriggerAction extends MapInitializedPlugin, Closeable {
+public interface TriggerAction extends Closeable {
+
+  /**
+   * Called when action is created but before it's initialized and used.
+   * This method should also verify that the configuration parameters are correct.
+   * It may be called multiple times.
+   * @param loader loader to use for instantiating sub-components
+   * @param cloudManager current instance of SolrCloudManager
+   * @param properties configuration properties
+   * @throws TriggerValidationException contains details of invalid configuration parameters.
+   */
+  void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException;
+
+  /**
+   * Called before an action is first used. Any heavy object creation or initialization should
+   * be done in this method instead of the constructor or {@link #configure(SolrResourceLoader, SolrCloudManager, Map)} method.
+   */
+  void init() throws Exception;
+
   String getName();
 
   void process(TriggerEvent event, ActionContext context) throws Exception;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerActionBase.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerActionBase.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerActionBase.java
index 75c4a87..ee2ce3c 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerActionBase.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerActionBase.java
@@ -17,19 +17,35 @@
 package org.apache.solr.cloud.autoscaling;
 
 import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
+
+import org.apache.solr.client.solrj.cloud.SolrCloudManager;
+import org.apache.solr.core.SolrResourceLoader;
 
 /**
  * Base class for {@link TriggerAction} implementations.
  */
 public abstract class TriggerActionBase implements TriggerAction {
 
-  protected Map<String, String> initArgs;
+  protected Map<String, Object> properties = new HashMap<>();
+  protected SolrResourceLoader loader;
+  protected SolrCloudManager cloudManager;
+  protected final Set<String> validProperties = new HashSet<>();
+  protected final Set<String> requiredProperties = new HashSet<>();
+
+  protected TriggerActionBase() {
+    TriggerUtils.validProperties(validProperties, "name");
+    TriggerUtils.requiredProperties(requiredProperties, validProperties, "class");
+  }
 
   @Override
   public String getName() {
-    if (initArgs != null) {
-      return initArgs.get("name");
+    String name = (String) properties.get("name");
+    if (name != null) {
+      return name;
     } else {
       return getClass().getSimpleName();
     }
@@ -41,7 +57,22 @@ public abstract class TriggerActionBase implements TriggerAction {
   }
 
   @Override
-  public void init(Map<String, String> args) {
-    this.initArgs = args;
+  public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException {
+    this.loader = loader;
+    this.cloudManager = cloudManager;
+    if (properties != null) {
+      this.properties.putAll(properties);
+    }
+    // validate the config
+    Map<String, String> results = new HashMap<>();
+    TriggerUtils.checkProperties(this.properties, results, requiredProperties, validProperties);
+    if (!results.isEmpty()) {
+      throw new TriggerValidationException(results);
+    }
+  }
+
+  @Override
+  public void init() throws Exception {
+
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerBase.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerBase.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerBase.java
index 04fa37e..c03557f 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerBase.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerBase.java
@@ -21,9 +21,11 @@ import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.lucene.util.IOUtils;
@@ -50,39 +52,66 @@ public abstract class TriggerBase implements AutoScaling.Trigger {
   private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   protected final String name;
-  protected final SolrCloudManager cloudManager;
-  protected final DistribStateManager stateManager;
+  protected SolrCloudManager cloudManager;
+  protected SolrResourceLoader loader;
+  protected DistribStateManager stateManager;
   protected final Map<String, Object> properties = new HashMap<>();
+  protected final Set<String> validProperties = new HashSet<>();
+  protected final Set<String> requiredProperties = new HashSet<>();
   protected final TriggerEventType eventType;
-  protected final int waitForSecond;
+  protected int waitForSecond;
   protected Map<String,Object> lastState;
   protected final AtomicReference<AutoScaling.TriggerEventProcessor> processorRef = new AtomicReference<>();
-  protected final List<TriggerAction> actions;
-  protected final boolean enabled;
+  protected List<TriggerAction> actions;
+  protected boolean enabled;
   protected boolean isClosed;
 
 
-  protected TriggerBase(TriggerEventType eventType, String name, Map<String, Object> properties, SolrResourceLoader loader, SolrCloudManager cloudManager) {
+  protected TriggerBase(TriggerEventType eventType, String name) {
     this.eventType = eventType;
     this.name = name;
+
+    // subclasses may modify this set to include other supported properties
+    TriggerUtils.validProperties(validProperties, "name", "event", "enabled", "waitFor", "actions");
+  }
+
+  @Override
+  public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException {
     this.cloudManager = cloudManager;
+    this.loader = loader;
     this.stateManager = cloudManager.getDistribStateManager();
     if (properties != null) {
       this.properties.putAll(properties);
     }
     this.enabled = Boolean.parseBoolean(String.valueOf(this.properties.getOrDefault("enabled", "true")));
     this.waitForSecond = ((Number) this.properties.getOrDefault("waitFor", -1L)).intValue();
-    List<Map<String, String>> o = (List<Map<String, String>>) properties.get("actions");
+    List<Map<String, Object>> o = (List<Map<String, Object>>) properties.get("actions");
     if (o != null && !o.isEmpty()) {
       actions = new ArrayList<>(3);
-      for (Map<String, String> map : o) {
-        TriggerAction action = loader.newInstance(map.get("class"), TriggerAction.class);
+      for (Map<String, Object> map : o) {
+        TriggerAction action = null;
+        try {
+          action = loader.newInstance((String)map.get("class"), TriggerAction.class);
+        } catch (Exception e) {
+          throw new TriggerValidationException("action", "exception creating action " + map + ": " + e.toString());
+        }
+        action.configure(loader, cloudManager, map);
         actions.add(action);
       }
     } else {
       actions = Collections.emptyList();
     }
 
+
+    Map<String, String> results = new HashMap<>();
+    TriggerUtils.checkProperties(this.properties, results, requiredProperties, validProperties);
+    if (!results.isEmpty()) {
+      throw new TriggerValidationException(results);
+    }
+  }
+
+  @Override
+  public void init() throws Exception {
     try {
       if (!stateManager.hasData(ZkStateReader.SOLR_AUTOSCALING_TRIGGER_STATE_PATH)) {
         stateManager.makePath(ZkStateReader.SOLR_AUTOSCALING_TRIGGER_STATE_PATH);
@@ -91,17 +120,10 @@ public abstract class TriggerBase implements AutoScaling.Trigger {
       // ignore
     } catch (InterruptedException | KeeperException | IOException e) {
       LOG.warn("Exception checking ZK path " + ZkStateReader.SOLR_AUTOSCALING_TRIGGER_STATE_PATH, e);
+      throw e;
     }
-  }
-
-  @Override
-  public void init() {
-    List<Map<String, String>> o = (List<Map<String, String>>) properties.get("actions");
-    if (o != null && !o.isEmpty()) {
-      for (int i = 0; i < o.size(); i++) {
-        Map<String, String> map = o.get(i);
-        actions.get(i).init(map);
-      }
+    for (TriggerAction action : actions) {
+      action.init();
     }
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerUtils.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerUtils.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerUtils.java
new file mode 100644
index 0000000..bdc0c11
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerUtils.java
@@ -0,0 +1,88 @@
+/*
+ * 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.solr.cloud.autoscaling;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ *
+ */
+public class TriggerUtils {
+  // validation helper methods
+
+  public static void requiredProperties(Set<String> required, Set<String> valid, String... propertyNames) {
+    required.addAll(Arrays.asList(propertyNames));
+    valid.addAll(Arrays.asList(propertyNames));
+  }
+
+  public static void validProperties(Set<String> valid, String... propertyNames) {
+    valid.addAll(Arrays.asList(propertyNames));
+  }
+
+  public static void checkProperties(Map<String, Object> properties, Map<String, String> results, Set<String> required, Set<String> valid) {
+    checkValidPropertyNames(properties, results, valid);
+    checkRequiredPropertyNames(properties, results, required);
+  }
+
+  public static void checkValidPropertyNames(Map<String, Object> properties, Map<String, String> results, Set<String> valid) {
+    Set<String> currentNames = new HashSet<>(properties.keySet());
+    currentNames.removeAll(valid);
+    if (!currentNames.isEmpty()) {
+      for (String name : currentNames) {
+        results.put(name, "unknown property");
+      }
+    }
+  }
+
+  public static void checkRequiredPropertyNames(Map<String, Object> properties, Map<String, String> results, Set<String> required) {
+    Set<String> requiredNames = new HashSet<>(required);
+    requiredNames.removeAll(properties.keySet());
+    if (!requiredNames.isEmpty()) {
+      for (String name : requiredNames) {
+        results.put(name, "missing required property");
+      }
+    }
+  }
+
+  public static void checkProperty(Map<String, Object> properties, Map<String, String> results, String name, boolean required, Class... acceptClasses) {
+    Object value = properties.get(name);
+    if (value == null) {
+      if (required) {
+        results.put(name, "missing required value");
+      } else {
+        return;
+      }
+    }
+    if (acceptClasses == null || acceptClasses.length == 0) {
+      return;
+    }
+    boolean accepted = false;
+    for (Class clz : acceptClasses) {
+      if (clz.isAssignableFrom(value.getClass())) {
+        accepted = true;
+        break;
+      }
+    }
+    if (!accepted) {
+      results.put(name, "value is not an expected type");
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerValidationException.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerValidationException.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerValidationException.java
new file mode 100644
index 0000000..ac2542b
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/TriggerValidationException.java
@@ -0,0 +1,68 @@
+/*
+ * 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.solr.cloud.autoscaling;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class represents errors found when validating trigger configuration.
+ */
+public class TriggerValidationException extends Exception {
+  private Map<String, String> details = new HashMap<>();
+
+  /**
+   * Create an exception.
+   * @param details details of invalid configuration - key is a property name,
+   *                value is an error message.
+   */
+  public TriggerValidationException(Map<String, String> details) {
+    super();
+    if (details != null) {
+      this.details.putAll(details);
+    }
+  }
+
+  /**
+   * Create an exception.
+   * @param keyValues zero or even number of arguments representing symbolic key
+   *                  (eg. property name) and the corresponding validation error message.
+   */
+  public TriggerValidationException(String... keyValues) {
+    super();
+    if (keyValues == null || keyValues.length == 0) {
+      return;
+    }
+    if (keyValues.length % 2 != 0) {
+      throw new IllegalArgumentException("number of arguments representing key & value pairs must be even");
+    }
+    for (int i = 0; i < keyValues.length; i += 2) {
+      details.put(keyValues[i], keyValues[i + 1]);
+    }
+  }
+
+  public Map<String, String> getDetails() {
+    return details;
+  }
+
+  @Override
+  public String toString() {
+    return "TriggerValidationException{" +
+        "details=" + details +
+        '}';
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/test/org/apache/solr/cloud/autoscaling/ComputePlanActionTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/ComputePlanActionTest.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/ComputePlanActionTest.java
index 943e8fc..5952e40 100644
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/ComputePlanActionTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/ComputePlanActionTest.java
@@ -47,6 +47,7 @@ import org.apache.solr.common.params.CollectionParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.Utils;
+import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.util.LogLevel;
 import org.junit.After;
 import org.junit.Before;
@@ -368,6 +369,16 @@ public class ComputePlanActionTest extends SolrCloudTestCase {
     static String expectedNode;
 
     @Override
+    public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException {
+
+    }
+
+    @Override
+    public void init() {
+
+    }
+
+    @Override
     public String getName() {
       return null;
     }
@@ -389,11 +400,6 @@ public class ComputePlanActionTest extends SolrCloudTestCase {
     public void close() throws IOException {
 
     }
-
-    @Override
-    public void init(Map<String, String> args) {
-
-    }
   }
 
   @Test

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/test/org/apache/solr/cloud/autoscaling/ExecutePlanActionTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/ExecutePlanActionTest.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/ExecutePlanActionTest.java
index 6e4463a..b26da52 100644
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/ExecutePlanActionTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/ExecutePlanActionTest.java
@@ -27,6 +27,7 @@ import java.util.stream.Collectors;
 
 import com.google.common.collect.Lists;
 import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.cloud.SolrCloudManager;
 import org.apache.solr.client.solrj.cloud.autoscaling.TriggerEventType;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
@@ -40,6 +41,7 @@ import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.params.CollectionParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.Utils;
+import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.util.LogLevel;
 import org.apache.solr.common.util.TimeSource;
 import org.apache.zookeeper.data.Stat;
@@ -60,6 +62,9 @@ public class ExecutePlanActionTest extends SolrCloudTestCase {
 
   private static final int NODE_COUNT = 2;
 
+  private SolrResourceLoader loader;
+  private SolrCloudManager cloudManager;
+
   @BeforeClass
   public static void setupCluster() throws Exception {
     configureCluster(NODE_COUNT)
@@ -81,6 +86,8 @@ public class ExecutePlanActionTest extends SolrCloudTestCase {
       }
     }
     cluster.waitForAllNodes(30);
+    loader = cluster.getJettySolrRunner(0).getCoreContainer().getResourceLoader();
+    cloudManager = cluster.getJettySolrRunner(0).getCoreContainer().getZkController().getSolrCloudManager();
   }
 
   @Test
@@ -109,7 +116,7 @@ public class ExecutePlanActionTest extends SolrCloudTestCase {
     JettySolrRunner survivor = otherJetties.get(0);
 
     try (ExecutePlanAction action = new ExecutePlanAction()) {
-      action.init(Collections.singletonMap("name", "execute_plan"));
+      action.configure(loader, cloudManager, Collections.singletonMap("name", "execute_plan"));
 
       // used to signal if we found that ExecutePlanAction did in fact create the right znode before executing the operation
       AtomicBoolean znodeCreated = new AtomicBoolean(false);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/test/org/apache/solr/cloud/autoscaling/MetricTriggerTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/MetricTriggerTest.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/MetricTriggerTest.java
index 93c91c5..f0f9f07 100644
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/MetricTriggerTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/MetricTriggerTest.java
@@ -72,7 +72,8 @@ public class MetricTriggerTest extends SolrCloudTestCase {
     SolrResourceLoader loader = cluster.getJettySolrRunner(0).getCoreContainer().getResourceLoader();
     SolrCloudManager cloudManager = new SolrClientCloudManager(new ZkDistributedQueueFactory(zkClient), cluster.getSolrClient());
 
-    try (MetricTrigger metricTrigger = new MetricTrigger("metricTrigger", props, loader, cloudManager)) {
+    try (MetricTrigger metricTrigger = new MetricTrigger("metricTrigger")) {
+      metricTrigger.configure(loader, cloudManager, props);
       metricTrigger.setProcessor(noFirstRunProcessor);
       metricTrigger.run();
       metricTrigger.setProcessor(event -> events.add(event));
@@ -85,7 +86,8 @@ public class MetricTriggerTest extends SolrCloudTestCase {
     events.clear();
     tag = "metrics:" + registry + ":ADMIN./admin/file.handlerStart";
     props = createTriggerProps(waitForSeconds, tag, null, 100.0d, DEFAULT_TEST_COLLECTION_NAME, null, null);
-    try (MetricTrigger metricTrigger = new MetricTrigger("metricTrigger", props, loader, cloudManager)) {
+    try (MetricTrigger metricTrigger = new MetricTrigger("metricTrigger")) {
+      metricTrigger.configure(loader, cloudManager, props);
       metricTrigger.setProcessor(noFirstRunProcessor);
       metricTrigger.run();
       metricTrigger.setProcessor(event -> events.add(event));

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/test/org/apache/solr/cloud/autoscaling/NodeAddedTriggerTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/NodeAddedTriggerTest.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/NodeAddedTriggerTest.java
index d82cec2..0485d4a 100644
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/NodeAddedTriggerTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/NodeAddedTriggerTest.java
@@ -27,10 +27,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.apache.solr.client.solrj.cloud.SolrCloudManager;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.cloud.SolrCloudTestCase;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.core.SolrResourceLoader;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -72,8 +74,8 @@ public class NodeAddedTriggerTest extends SolrCloudTestCase {
     long waitForSeconds = 1 + random().nextInt(5);
     Map<String, Object> props = createTriggerProps(waitForSeconds);
 
-    try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger", props, container.getResourceLoader(),
-        container.getZkController().getSolrCloudManager())) {
+    try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger")) {
+      trigger.configure(container.getResourceLoader(), container.getZkController().getSolrCloudManager(), props);
       trigger.setProcessor(noFirstRunProcessor);
       trigger.run();
 
@@ -113,8 +115,8 @@ public class NodeAddedTriggerTest extends SolrCloudTestCase {
 
     // add a new node but remove it before the waitFor period expires
     // and assert that the trigger doesn't fire at all
-    try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger", props, container.getResourceLoader(),
-        container.getZkController().getSolrCloudManager())) {
+    try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger")) {
+      trigger.configure(container.getResourceLoader(), container.getZkController().getSolrCloudManager(), props);
       final long waitTime = 2;
       props.put("waitFor", waitTime);
       trigger.setProcessor(noFirstRunProcessor);
@@ -159,8 +161,8 @@ public class NodeAddedTriggerTest extends SolrCloudTestCase {
     action.put("name", "testActionInit");
     action.put("class", NodeAddedTriggerTest.AssertInitTriggerAction.class.getName());
     actions.add(action);
-    try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger", props, container.getResourceLoader(),
-        container.getZkController().getSolrCloudManager())) {
+    try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger")) {
+      trigger.configure(container.getResourceLoader(), container.getZkController().getSolrCloudManager(), props);
       assertEquals(true, actionConstructorCalled.get());
       assertEquals(false, actionInitCalled.get());
       assertEquals(false, actionCloseCalled.get());
@@ -177,6 +179,16 @@ public class NodeAddedTriggerTest extends SolrCloudTestCase {
     }
 
     @Override
+    public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException {
+
+    }
+
+    @Override
+    public void init() {
+      actionInitCalled.compareAndSet(false, true);
+    }
+
+    @Override
     public String getName() {
       return "";
     }
@@ -190,19 +202,14 @@ public class NodeAddedTriggerTest extends SolrCloudTestCase {
     public void close() throws IOException {
       actionCloseCalled.compareAndSet(false, true);
     }
-
-    @Override
-    public void init(Map<String, String> args) {
-      actionInitCalled.compareAndSet(false, true);
-    }
   }
 
   @Test
   public void testListenerAcceptance() throws Exception {
     CoreContainer container = cluster.getJettySolrRunners().get(0).getCoreContainer();
     Map<String, Object> props = createTriggerProps(0);
-    try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger", props, container.getResourceLoader(),
-        container.getZkController().getSolrCloudManager())) {
+    try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger")) {
+      trigger.configure(container.getResourceLoader(), container.getZkController().getSolrCloudManager(), props);
       trigger.setProcessor(noFirstRunProcessor);
       trigger.run(); // starts tracking live nodes
 
@@ -238,8 +245,8 @@ public class NodeAddedTriggerTest extends SolrCloudTestCase {
 
     // add a new node but update the trigger before the waitFor period expires
     // and assert that the new trigger still fires
-    NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger", props, container.getResourceLoader(),
-        container.getZkController().getSolrCloudManager());
+    NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger");
+    trigger.configure(container.getResourceLoader(), container.getZkController().getSolrCloudManager(), props);
     trigger.setProcessor(noFirstRunProcessor);
     trigger.run();
 
@@ -247,8 +254,8 @@ public class NodeAddedTriggerTest extends SolrCloudTestCase {
     trigger.run(); // this run should detect the new node
     trigger.close(); // close the old trigger
 
-    try (NodeAddedTrigger newTrigger = new NodeAddedTrigger("some_different_name", props, container.getResourceLoader(),
-        container.getZkController().getSolrCloudManager()))  {
+    try (NodeAddedTrigger newTrigger = new NodeAddedTrigger("some_different_name"))  {
+      newTrigger.configure(container.getResourceLoader(), container.getZkController().getSolrCloudManager(), props);
       try {
         newTrigger.restoreState(trigger);
         fail("Trigger should only be able to restore state from an old trigger of the same name");
@@ -257,8 +264,8 @@ public class NodeAddedTriggerTest extends SolrCloudTestCase {
       }
     }
 
-    try (NodeAddedTrigger newTrigger = new NodeAddedTrigger("node_added_trigger", props, container.getResourceLoader(),
-        container.getZkController().getSolrCloudManager()))  {
+    try (NodeAddedTrigger newTrigger = new NodeAddedTrigger("node_added_trigger"))  {
+      newTrigger.configure(container.getResourceLoader(), container.getZkController().getSolrCloudManager(), props);
       AtomicBoolean fired = new AtomicBoolean(false);
       AtomicReference<TriggerEvent> eventRef = new AtomicReference<>();
       newTrigger.setProcessor(event -> {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/test/org/apache/solr/cloud/autoscaling/NodeLostTriggerTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/NodeLostTriggerTest.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/NodeLostTriggerTest.java
index d9c9571..07988e1 100644
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/NodeLostTriggerTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/NodeLostTriggerTest.java
@@ -27,10 +27,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.apache.solr.client.solrj.cloud.SolrCloudManager;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.cloud.SolrCloudTestCase;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.core.SolrResourceLoader;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -73,8 +75,8 @@ public class NodeLostTriggerTest extends SolrCloudTestCase {
     long waitForSeconds = 1 + random().nextInt(5);
     Map<String, Object> props = createTriggerProps(waitForSeconds);
 
-    try (NodeLostTrigger trigger = new NodeLostTrigger("node_lost_trigger", props, container.getResourceLoader(),
-        container.getZkController().getSolrCloudManager())) {
+    try (NodeLostTrigger trigger = new NodeLostTrigger("node_lost_trigger")) {
+      trigger.configure(container.getResourceLoader(), container.getZkController().getSolrCloudManager(), props);
       trigger.setProcessor(noFirstRunProcessor);
       trigger.run();
       String lostNodeName1 = cluster.getJettySolrRunner(1).getNodeName();
@@ -118,8 +120,8 @@ public class NodeLostTriggerTest extends SolrCloudTestCase {
 
     // remove a node but add it back before the waitFor period expires
     // and assert that the trigger doesn't fire at all
-    try (NodeLostTrigger trigger = new NodeLostTrigger("node_lost_trigger", props, container.getResourceLoader(),
-        container.getZkController().getSolrCloudManager())) {
+    try (NodeLostTrigger trigger = new NodeLostTrigger("node_lost_trigger")) {
+      trigger.configure(container.getResourceLoader(), container.getZkController().getSolrCloudManager(), props);
       final long waitTime = 2;
       props.put("waitFor", waitTime);
       trigger.setProcessor(noFirstRunProcessor);
@@ -175,8 +177,8 @@ public class NodeLostTriggerTest extends SolrCloudTestCase {
     action.put("name", "testActionInit");
     action.put("class", AssertInitTriggerAction.class.getName());
     actions.add(action);
-    try (NodeLostTrigger trigger = new NodeLostTrigger("node_added_trigger", props, container.getResourceLoader(),
-        container.getZkController().getSolrCloudManager())) {
+    try (NodeLostTrigger trigger = new NodeLostTrigger("node_added_trigger")) {
+      trigger.configure(container.getResourceLoader(), container.getZkController().getSolrCloudManager(), props);
       assertEquals(true, actionConstructorCalled.get());
       assertEquals(false, actionInitCalled.get());
       assertEquals(false, actionCloseCalled.get());
@@ -193,6 +195,16 @@ public class NodeLostTriggerTest extends SolrCloudTestCase {
     }
 
     @Override
+    public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException {
+
+    }
+
+    @Override
+    public void init() {
+      actionInitCalled.compareAndSet(false, true);
+    }
+
+    @Override
     public String getName() {
       return "";
     }
@@ -207,18 +219,14 @@ public class NodeLostTriggerTest extends SolrCloudTestCase {
       actionCloseCalled.compareAndSet(false, true);
     }
 
-    @Override
-    public void init(Map<String, String> args) {
-      actionInitCalled.compareAndSet(false, true);
-    }
   }
 
   @Test
   public void testListenerAcceptance() throws Exception {
     CoreContainer container = cluster.getJettySolrRunners().get(0).getCoreContainer();
     Map<String, Object> props = createTriggerProps(0);
-    try (NodeLostTrigger trigger = new NodeLostTrigger("node_added_trigger", props, container.getResourceLoader(),
-        container.getZkController().getSolrCloudManager())) {
+    try (NodeLostTrigger trigger = new NodeLostTrigger("node_added_trigger")) {
+      trigger.configure(container.getResourceLoader(), container.getZkController().getSolrCloudManager(), props);
       trigger.setProcessor(noFirstRunProcessor);
 
       JettySolrRunner newNode = cluster.startJettySolrRunner();
@@ -272,8 +280,8 @@ public class NodeLostTriggerTest extends SolrCloudTestCase {
     // remove a node but update the trigger before the waitFor period expires
     // and assert that the new trigger still fires
 
-    NodeLostTrigger trigger = new NodeLostTrigger("node_lost_trigger", props, container.getResourceLoader(),
-        container.getZkController().getSolrCloudManager());
+    NodeLostTrigger trigger = new NodeLostTrigger("node_lost_trigger");
+    trigger.configure(container.getResourceLoader(), container.getZkController().getSolrCloudManager(), props);
     trigger.setProcessor(noFirstRunProcessor);
     trigger.run();
 
@@ -290,8 +298,8 @@ public class NodeLostTriggerTest extends SolrCloudTestCase {
     trigger.run(); // this run should detect the lost node
     trigger.close(); // close the old trigger
 
-    try (NodeLostTrigger newTrigger = new NodeLostTrigger("some_different_name", props, container.getResourceLoader(),
-        container.getZkController().getSolrCloudManager()))  {
+    try (NodeLostTrigger newTrigger = new NodeLostTrigger("some_different_name"))  {
+      newTrigger.configure(container.getResourceLoader(), container.getZkController().getSolrCloudManager(), props);
       try {
         newTrigger.restoreState(trigger);
         fail("Trigger should only be able to restore state from an old trigger of the same name");
@@ -300,8 +308,8 @@ public class NodeLostTriggerTest extends SolrCloudTestCase {
       }
     }
 
-    try (NodeLostTrigger newTrigger = new NodeLostTrigger("node_lost_trigger", props, container.getResourceLoader(),
-        container.getZkController().getSolrCloudManager())) {
+    try (NodeLostTrigger newTrigger = new NodeLostTrigger("node_lost_trigger")) {
+      newTrigger.configure(container.getResourceLoader(), container.getZkController().getSolrCloudManager(), props);
       AtomicBoolean fired = new AtomicBoolean(false);
       AtomicReference<TriggerEvent> eventRef = new AtomicReference<>();
       newTrigger.setProcessor(event -> {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/test/org/apache/solr/cloud/autoscaling/ScheduledTriggerTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/ScheduledTriggerTest.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/ScheduledTriggerTest.java
index 6fedd95..012dd94 100644
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/ScheduledTriggerTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/ScheduledTriggerTest.java
@@ -81,8 +81,8 @@ public class ScheduledTriggerTest extends SolrCloudTestCase {
     Map<String, Object> properties = createTriggerProperties(new Date(threeDaysAgo).toInstant().toString(),
         TimeZone.getDefault().getID(),
         "+2DAYS", "+1HOUR");
-    try (ScheduledTrigger scheduledTrigger = new ScheduledTrigger("sched1", properties,
-        container.getResourceLoader(), container.getZkController().getSolrCloudManager())) {
+    try (ScheduledTrigger scheduledTrigger = new ScheduledTrigger("sched1")) {
+      scheduledTrigger.configure(container.getResourceLoader(), container.getZkController().getSolrCloudManager(), properties);
       scheduledTrigger.init();
       AtomicReference<TriggerEvent> eventRef = new AtomicReference<>();
       scheduledTrigger.setProcessor(event -> {
@@ -94,9 +94,9 @@ public class ScheduledTriggerTest extends SolrCloudTestCase {
     }
   }
 
-  private void scheduledTriggerTest(CoreContainer container, Map<String, Object> properties) throws IOException, InterruptedException {
-    try (ScheduledTrigger scheduledTrigger = new ScheduledTrigger("sched1", properties,
-        container.getResourceLoader(), container.getZkController().getSolrCloudManager())) {
+  private void scheduledTriggerTest(CoreContainer container, Map<String, Object> properties) throws Exception {
+    try (ScheduledTrigger scheduledTrigger = new ScheduledTrigger("sched1")) {
+      scheduledTrigger.configure(container.getResourceLoader(), container.getZkController().getSolrCloudManager(), properties);
       scheduledTrigger.init();
       scheduledTrigger.setProcessor(noFirstRunProcessor);
       scheduledTrigger.run();
@@ -119,7 +119,7 @@ public class ScheduledTriggerTest extends SolrCloudTestCase {
 
   private Map<String, Object> createTriggerProperties(String startTime, String timeZone, String every, String graceTime) {
     Map<String, Object> properties = new HashMap<>();
-    properties.put("graceTime", graceTime);
+    properties.put("graceDuration", graceTime);
     properties.put("startTime", startTime);
     properties.put("timeZone", timeZone);
     properties.put("every", every);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/test/org/apache/solr/cloud/autoscaling/SearchRateTriggerTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/SearchRateTriggerTest.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/SearchRateTriggerTest.java
index 081cd90..1c72649 100644
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/SearchRateTriggerTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/SearchRateTriggerTest.java
@@ -83,7 +83,9 @@ public class SearchRateTriggerTest extends SolrCloudTestCase {
     final List<TriggerEvent> events = new ArrayList<>();
     CloudSolrClient solrClient = cluster.getSolrClient();
 
-    try (SearchRateTrigger trigger = new SearchRateTrigger("search_rate_trigger", props, loader, cloudManager)) {
+    try (SearchRateTrigger trigger = new SearchRateTrigger("search_rate_trigger")) {
+      trigger.configure(loader, cloudManager, props);
+      trigger.init();
       trigger.setProcessor(noFirstRunProcessor);
       trigger.run();
       trigger.setProcessor(event -> events.add(event));

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/test/org/apache/solr/cloud/autoscaling/TriggerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/TriggerIntegrationTest.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/TriggerIntegrationTest.java
index f29280b..5f97a50 100644
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/TriggerIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/TriggerIntegrationTest.java
@@ -663,10 +663,10 @@ public class TriggerIntegrationTest extends SolrCloudTestCase {
     }
 
     @Override
-    public void init(Map<String, String> args) {
+    public void init() throws Exception {
       log.info("TestTriggerAction init");
       actionInitCalled.countDown();
-      super.init(args);
+      super.init();
     }
   }
 
@@ -692,10 +692,10 @@ public class TriggerIntegrationTest extends SolrCloudTestCase {
     }
 
     @Override
-    public void init(Map<String, String> args) {
-      log.debug("TestTriggerAction init");
+    public void init() throws Exception {
+      log.info("TestEventQueueAction init");
       actionInitCalled.countDown();
-      super.init(args);
+      super.init();
     }
   }
 
@@ -871,10 +871,10 @@ public class TriggerIntegrationTest extends SolrCloudTestCase {
     }
 
     @Override
-    public void init(Map<String, String> args) {
+    public void init() throws Exception {
       log.info("TestEventMarkerAction init");
       actionInitCalled.countDown();
-      super.init(args);
+      super.init();
     }
   }
 
@@ -1295,7 +1295,7 @@ public class TriggerIntegrationTest extends SolrCloudTestCase {
     try (ScheduledTriggers scheduledTriggers = new ScheduledTriggers(resourceLoader, solrCloudManager)) {
       AutoScalingConfig config = new AutoScalingConfig(Collections.emptyMap());
       scheduledTriggers.setAutoScalingConfig(config);
-      scheduledTriggers.add(new TriggerBase(TriggerEventType.NODELOST, "x", Collections.emptyMap(), resourceLoader, solrCloudManager) {
+      AutoScaling.Trigger t = new TriggerBase(TriggerEventType.NODELOST, "x") {
         @Override
         protected Map<String, Object> getState() {
           return Collections.singletonMap("x","y");
@@ -1318,7 +1318,10 @@ public class TriggerIntegrationTest extends SolrCloudTestCase {
           diff.set(timeSource.getTimeNs() - l);
           getTriggerFiredLatch().countDown();
         }
-      });
+      };
+      t.configure(runner.getCoreContainer().getResourceLoader(), runner.getCoreContainer().getZkController().getSolrCloudManager(), Collections.emptyMap());
+      scheduledTriggers.add(t);
+
       assertTrue(getTriggerFiredLatch().await(4, TimeUnit.SECONDS));
       assertTrue(diff.get() - TimeUnit.SECONDS.toNanos(ScheduledTriggers.DEFAULT_SCHEDULED_TRIGGER_DELAY_SECONDS) >= 0);
 
@@ -1340,7 +1343,7 @@ public class TriggerIntegrationTest extends SolrCloudTestCase {
       final Set<String> triggerNames = Collections.synchronizedSet(new HashSet<>());
       triggerFiredLatch = new CountDownLatch(8);
       for (int i = 0; i < 8; i++) {
-        triggerList.add(new MockTrigger(TriggerEventType.NODELOST, "x" + i, Collections.emptyMap(), resourceLoader, solrCloudManager)  {
+        AutoScaling.Trigger trigger = new MockTrigger(TriggerEventType.NODELOST, "x" + i)  {
           @Override
           public void run() {
             try {
@@ -1355,7 +1358,9 @@ public class TriggerIntegrationTest extends SolrCloudTestCase {
               threadNames.add(Thread.currentThread().getName());
             }
           }
-        });
+        };
+        trigger.configure(resourceLoader, solrCloudManager, Collections.emptyMap());
+        triggerList.add(trigger);
         scheduledTriggers.add(triggerList.get(i));
       }
       assertTrue("Timed out waiting for latch to fire", getTriggerFiredLatch().await(20, TimeUnit.SECONDS));
@@ -1383,8 +1388,8 @@ public class TriggerIntegrationTest extends SolrCloudTestCase {
 
   public static class MockTrigger extends TriggerBase {
 
-    public MockTrigger(TriggerEventType eventType, String name, Map<String, Object> properties, SolrResourceLoader loader, SolrCloudManager cloudManager) {
-      super(eventType, name, properties, loader, cloudManager);
+    public MockTrigger(TriggerEventType eventType, String name) {
+      super(eventType, name);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestComputePlanAction.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestComputePlanAction.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestComputePlanAction.java
index 4dde7b6..cdc4173 100644
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestComputePlanAction.java
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestComputePlanAction.java
@@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicReference;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrResponse;
+import org.apache.solr.client.solrj.cloud.SolrCloudManager;
 import org.apache.solr.client.solrj.cloud.autoscaling.TriggerEventType;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.cloud.CloudTestUtils;
@@ -38,6 +39,7 @@ import org.apache.solr.cloud.autoscaling.ComputePlanAction;
 import org.apache.solr.cloud.autoscaling.ScheduledTriggers;
 import org.apache.solr.cloud.autoscaling.TriggerAction;
 import org.apache.solr.cloud.autoscaling.TriggerEvent;
+import org.apache.solr.cloud.autoscaling.TriggerValidationException;
 import org.apache.solr.common.cloud.ClusterState;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.Replica;
@@ -46,6 +48,7 @@ import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.TimeSource;
 import org.apache.solr.common.util.Utils;
+import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.util.LogLevel;
 import org.junit.After;
 import org.junit.Before;
@@ -319,6 +322,16 @@ public class TestComputePlanAction extends SimSolrCloudTestCase {
     static String expectedNode;
 
     @Override
+    public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException {
+
+    }
+
+    @Override
+    public void init() {
+
+    }
+
+    @Override
     public String getName() {
       return null;
     }
@@ -340,10 +353,5 @@ public class TestComputePlanAction extends SimSolrCloudTestCase {
     public void close() throws IOException {
 
     }
-
-    @Override
-    public void init(Map<String, String> args) {
-
-    }
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestExecutePlanAction.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestExecutePlanAction.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestExecutePlanAction.java
index a030d8a..9117dcd 100644
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestExecutePlanAction.java
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestExecutePlanAction.java
@@ -103,7 +103,7 @@ public class TestExecutePlanAction extends SimSolrCloudTestCase {
     String survivor = otherNodes.get(0);
 
     try (ExecutePlanAction action = new ExecutePlanAction()) {
-      action.init(Collections.singletonMap("name", "execute_plan"));
+      action.configure(cluster.getLoader(), cluster, Collections.singletonMap("name", "execute_plan"));
 
       // used to signal if we found that ExecutePlanAction did in fact create the right znode before executing the operation
       AtomicBoolean znodeCreated = new AtomicBoolean(false);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/059f495e/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestNodeAddedTrigger.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestNodeAddedTrigger.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestNodeAddedTrigger.java
index eafc300..fd816ca 100644
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestNodeAddedTrigger.java
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestNodeAddedTrigger.java
@@ -27,12 +27,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.apache.solr.client.solrj.cloud.SolrCloudManager;
 import org.apache.solr.cloud.autoscaling.ActionContext;
 import org.apache.solr.cloud.autoscaling.AutoScaling;
 import org.apache.solr.cloud.autoscaling.NodeAddedTrigger;
 import org.apache.solr.cloud.autoscaling.TriggerAction;
 import org.apache.solr.cloud.autoscaling.TriggerEvent;
+import org.apache.solr.cloud.autoscaling.TriggerValidationException;
 import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.core.SolrResourceLoader;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -75,7 +78,8 @@ public class TestNodeAddedTrigger extends SimSolrCloudTestCase {
     long waitForSeconds = 1 + random().nextInt(5);
     Map<String, Object> props = createTriggerProps(waitForSeconds);
 
-    try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger", props, cluster.getLoader(), cluster)) {
+    try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger")) {
+      trigger.configure(cluster.getLoader(), cluster, props);
       trigger.setProcessor(noFirstRunProcessor);
       trigger.run();
 
@@ -115,7 +119,9 @@ public class TestNodeAddedTrigger extends SimSolrCloudTestCase {
 
     // add a new node but remove it before the waitFor period expires
     // and assert that the trigger doesn't fire at all
-    try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger", props, cluster.getLoader(), cluster)) {
+    try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger")) {
+      trigger.configure(cluster.getLoader(), cluster, props);
+      trigger.init();
       final long waitTime = 2;
       props.put("waitFor", waitTime);
       trigger.setProcessor(noFirstRunProcessor);
@@ -159,7 +165,8 @@ public class TestNodeAddedTrigger extends SimSolrCloudTestCase {
     action.put("name", "testActionInit");
     action.put("class", TestNodeAddedTrigger.AssertInitTriggerAction.class.getName());
     actions.add(action);
-    try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger", props, cluster.getLoader(), cluster)) {
+    try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger")) {
+      trigger.configure(cluster.getLoader(), cluster, props);
       assertEquals(true, actionConstructorCalled.get());
       assertEquals(false, actionInitCalled.get());
       assertEquals(false, actionCloseCalled.get());
@@ -176,6 +183,16 @@ public class TestNodeAddedTrigger extends SimSolrCloudTestCase {
     }
 
     @Override
+    public void configure(SolrResourceLoader loader, SolrCloudManager cloudManager, Map<String, Object> properties) throws TriggerValidationException {
+
+    }
+
+    @Override
+    public void init() {
+      actionInitCalled.compareAndSet(false, true);
+    }
+
+    @Override
     public String getName() {
       return "";
     }
@@ -189,17 +206,14 @@ public class TestNodeAddedTrigger extends SimSolrCloudTestCase {
     public void close() throws IOException {
       actionCloseCalled.compareAndSet(false, true);
     }
-
-    @Override
-    public void init(Map<String, String> args) {
-      actionInitCalled.compareAndSet(false, true);
-    }
-  }
+ }
 
   @Test
   public void testListenerAcceptance() throws Exception {
     Map<String, Object> props = createTriggerProps(0);
-    try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger", props, cluster.getLoader(), cluster)) {
+    try (NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger")) {
+      trigger.configure(cluster.getLoader(), cluster, props);
+      trigger.init();
       trigger.setProcessor(noFirstRunProcessor);
       trigger.run(); // starts tracking live nodes
 
@@ -234,7 +248,8 @@ public class TestNodeAddedTrigger extends SimSolrCloudTestCase {
 
     // add a new node but update the trigger before the waitFor period expires
     // and assert that the new trigger still fires
-    NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger", props, cluster.getLoader(), cluster);
+    NodeAddedTrigger trigger = new NodeAddedTrigger("node_added_trigger");
+    trigger.configure(cluster.getLoader(), cluster, props);
     trigger.setProcessor(noFirstRunProcessor);
     trigger.run();
 
@@ -242,7 +257,8 @@ public class TestNodeAddedTrigger extends SimSolrCloudTestCase {
     trigger.run(); // this run should detect the new node
     trigger.close(); // close the old trigger
 
-    try (NodeAddedTrigger newTrigger = new NodeAddedTrigger("some_different_name", props, cluster.getLoader(), cluster))  {
+    try (NodeAddedTrigger newTrigger = new NodeAddedTrigger("some_different_name"))  {
+      newTrigger.configure(cluster.getLoader(), cluster, props);
       try {
         newTrigger.restoreState(trigger);
         fail("Trigger should only be able to restore state from an old trigger of the same name");
@@ -251,7 +267,8 @@ public class TestNodeAddedTrigger extends SimSolrCloudTestCase {
       }
     }
 
-    try (NodeAddedTrigger newTrigger = new NodeAddedTrigger("node_added_trigger", props, cluster.getLoader(), cluster))  {
+    try (NodeAddedTrigger newTrigger = new NodeAddedTrigger("node_added_trigger"))  {
+      newTrigger.configure(cluster.getLoader(), cluster, props);
       AtomicBoolean fired = new AtomicBoolean(false);
       AtomicReference<TriggerEvent> eventRef = new AtomicReference<>();
       newTrigger.setProcessor(event -> {