SLIDER-587 test for dynamic role replacement passing
diff --git a/slider-core/src/main/java/org/apache/slider/server/appmaster/state/AppState.java b/slider-core/src/main/java/org/apache/slider/server/appmaster/state/AppState.java
index 2b5d0ee..0848173 100644
--- a/slider-core/src/main/java/org/apache/slider/server/appmaster/state/AppState.java
+++ b/slider-core/src/main/java/org/apache/slider/server/appmaster/state/AppState.java
@@ -708,8 +708,6 @@
         log.info("New role {}", roleStatus);
         roleHistory.addNewProviderRole(dynamicRole);
         newRoles.add(dynamicRole);
-      } else {
-        log.debug("known role: {}", name);
       }
     }
     return newRoles;
diff --git a/slider-core/src/main/java/org/apache/slider/server/appmaster/state/NodeEntry.java b/slider-core/src/main/java/org/apache/slider/server/appmaster/state/NodeEntry.java
index 83c590b..ebddaf9 100644
--- a/slider-core/src/main/java/org/apache/slider/server/appmaster/state/NodeEntry.java
+++ b/slider-core/src/main/java/org/apache/slider/server/appmaster/state/NodeEntry.java
@@ -37,10 +37,10 @@
  */
 public class NodeEntry {
   
-  public final int index;
+  public final int rolePriority;
 
-  public NodeEntry(int index) {
-    this.index = index;
+  public NodeEntry(int rolePriority) {
+    this.rolePriority = rolePriority;
   }
 
   /**
@@ -132,8 +132,7 @@
   public synchronized boolean onStartFailed() {
     decStarting();
     ++startFailed;
-    ++failed;
-    return isAvailable();
+    return containerCompleted(false);
   }
   
   /**
@@ -211,7 +210,8 @@
   @Override
   public String toString() {
     final StringBuilder sb = new StringBuilder("NodeEntry{");
-    sb.append("requested=").append(requested);
+    sb.append("priority=").append(rolePriority);
+    sb.append(", requested=").append(requested);
     sb.append(", starting=").append(starting);
     sb.append(", live=").append(live);
     sb.append(", failed=").append(failed);
diff --git a/slider-core/src/main/java/org/apache/slider/server/appmaster/state/NodeInstance.java b/slider-core/src/main/java/org/apache/slider/server/appmaster/state/NodeInstance.java
index 1ba2282..bc79b71 100644
--- a/slider-core/src/main/java/org/apache/slider/server/appmaster/state/NodeInstance.java
+++ b/slider-core/src/main/java/org/apache/slider/server/appmaster/state/NodeInstance.java
@@ -52,7 +52,7 @@
    */
   public synchronized NodeEntry get(int role) {
     for (NodeEntry nodeEntry : nodeEntries) {
-      if (nodeEntry.index == role) {
+      if (nodeEntry.rolePriority == role) {
         return nodeEntry;
       }
     }
@@ -146,7 +146,7 @@
       new StringBuilder(toString());
     int i = 0;
     for (NodeEntry entry : nodeEntries) {
-      sb.append(String.format("\n  [%02d]  ", i++));
+      sb.append(String.format("\n  [%02d]  ", entry.rolePriority));
         sb.append(entry.toString());
     }
     return sb.toString();
diff --git a/slider-core/src/main/java/org/apache/slider/server/appmaster/state/RoleHistory.java b/slider-core/src/main/java/org/apache/slider/server/appmaster/state/RoleHistory.java
index 33c3442..f1c0af5 100644
--- a/slider-core/src/main/java/org/apache/slider/server/appmaster/state/RoleHistory.java
+++ b/slider-core/src/main/java/org/apache/slider/server/appmaster/state/RoleHistory.java
@@ -63,8 +63,6 @@
   protected static final Logger log =
     LoggerFactory.getLogger(RoleHistory.class);
   private final List<ProviderRole> providerRoles;
-  private final Map<String, ProviderRole> providerRoleMap =
-    new HashMap<String, ProviderRole>();
   private long startTime;
   /**
    * Time when saved
@@ -97,6 +95,7 @@
    * ask with/without locality. Has other potential uses as well.
    */
   private Set<String> failedNodes = new HashSet<String>();
+  
   // dummy to be used in maps for faster lookup where we don't care about values
   private final Object DUMMY_VALUE = new Object(); 
 
@@ -104,9 +103,6 @@
                                                        BadConfigException {
     this.providerRoles = providerRoles;
     roleSize = providerRoles.size();
-    for (ProviderRole providerRole : providerRoles) {
-      providerRoleMap.put(providerRole.name, providerRole);
-    }
     reset();
   }
 
@@ -120,18 +116,24 @@
     resetAvailableNodeLists();
 
     outstandingRequests = new OutstandingRequestTracker();
+    
     Map<Integer, RoleStatus> roleStats = new HashMap<Integer, RoleStatus>();
-
-
     for (ProviderRole providerRole : providerRoles) {
-      addProviderRole(roleStats, providerRole);
+      checkProviderRole(roleStats, providerRole);
     }
   }
 
-  
-  private void addProviderRole(Map<Integer, RoleStatus> roleStats,
-                               ProviderRole providerRole)
-    throws ArrayIndexOutOfBoundsException, BadConfigException {
+  /**
+   * safety check: make sure the provider role is unique amongst
+   * the role stats...which is extended with the new role
+   * @param roleStats role stats
+   * @param providerRole role
+   * @throws ArrayIndexOutOfBoundsException
+   * @throws BadConfigException
+   */
+  private void checkProviderRole(Map<Integer, RoleStatus> roleStats,
+      ProviderRole providerRole)
+    throws BadConfigException {
     int index = providerRole.id;
     if (index < 0) {
       throw new BadConfigException("Provider " + providerRole
@@ -154,12 +156,12 @@
     throws BadConfigException {
     Map<Integer, RoleStatus> roleStats = new HashMap<Integer, RoleStatus>();
 
-
     for (ProviderRole role : providerRoles) {
       roleStats.put(role.id, new RoleStatus(role));
     }
 
-    addProviderRole(roleStats, providerRole);
+    checkProviderRole(roleStats, providerRole);
+    this.providerRoles.add(providerRole);
   }
 
   /**
@@ -432,7 +434,8 @@
    * @param id role ID
    * @return potenially null list
    */
-  private List<NodeInstance> getNodesForRoleId(int id) {
+  @VisibleForTesting
+  public List<NodeInstance> getNodesForRoleId(int id) {
     return availableNodes.get(id);
   }
   
@@ -610,6 +613,8 @@
   public synchronized boolean onContainerAllocated(Container container, int desiredCount, int actualCount) {
     int role = ContainerPriority.extractRole(container);
     String hostname = RoleHistoryUtils.hostnameOf(container);
+    LinkedList<NodeInstance> nodeInstances =
+        getOrCreateNodesForRoleId(role);
     boolean requestFound =
       outstandingRequests.onContainerAllocated(role, hostname);
     if (desiredCount <= actualCount) {
@@ -619,7 +624,7 @@
       if (!hosts.isEmpty()) {
         //add the list
         log.debug("Adding {} hosts for role {}", hosts.size(), role);
-        getOrCreateNodesForRoleId(role).addAll(hosts);
+        nodeInstances.addAll(hosts);
         sortAvailableNodeList(role);
       }
     }
@@ -734,6 +739,8 @@
                                                        boolean wasReleased,
                                                        boolean shortLived) {
     NodeEntry nodeEntry = getOrCreateNodeEntry(container);
+    log.debug("Finished container for node {}, released={}, shortlived={}",
+        nodeEntry.rolePriority, wasReleased, shortLived);
     boolean available;
     if (shortLived) {
       nodeEntry.onStartFailed();
@@ -781,13 +788,16 @@
       List<NodeInstance> instances =
         getOrCreateNodesForRoleId(role.id);
       log.info("  available: " + instances.size()
-               + " " + SliderUtils.joinWithInnerSeparator(", ", instances));
+               + " " + SliderUtils.joinWithInnerSeparator(" ", instances));
     }
 
     log.info("Nodes in Cluster: {}", getClusterSize());
     for (NodeInstance node : nodemap.values()) {
       log.info(node.toFullString());
     }
+
+    log.info("Failed nodes: {}",
+        SliderUtils.joinWithInnerSeparator(" ", failedNodes));
   }
 
   /**
diff --git a/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicHistory.groovy b/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicHistory.groovy
index 9a9ad23..7d41012 100644
--- a/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicHistory.groovy
+++ b/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicHistory.groovy
@@ -24,7 +24,6 @@
 import org.apache.hadoop.yarn.api.records.ContainerId
 import org.apache.slider.api.ResourceKeys
 import org.apache.slider.core.conf.ConfTreeOperations
-import org.apache.slider.providers.PlacementPolicy
 import org.apache.slider.providers.ProviderRole
 import org.apache.slider.server.appmaster.model.mock.BaseMockAppStateTest
 import org.apache.slider.server.appmaster.model.mock.MockAppState
@@ -84,7 +83,7 @@
   public void testDynamicRoleHistory() throws Throwable {
 
     def dynamic = "dynamicRole"
-    int priority_num_8 = 8
+    int role_priority_8 = 8
     int desired = 1
     int placementPolicy = 0
     // snapshot and patch existing spec
@@ -92,7 +91,7 @@
         appState.resourcesSnapshot.confTree)
     def opts = [
         (ResourceKeys.COMPONENT_INSTANCES): ""+desired,
-        (ResourceKeys.COMPONENT_PRIORITY) : "" +priority_num_8,
+        (ResourceKeys.COMPONENT_PRIORITY) : "" +role_priority_8,
         (ResourceKeys.COMPONENT_PLACEMENT_POLICY): "" + placementPolicy
     ]
 
@@ -109,21 +108,21 @@
     def snapshotDefinition = appState.resourcesSnapshot.getMandatoryComponent(
         dynamic)
     assert snapshotDefinition.getMandatoryOptionInt(
-        ResourceKeys.COMPONENT_PRIORITY) == priority_num_8
+        ResourceKeys.COMPONENT_PRIORITY) == role_priority_8
 
     // now look at the role map
     assert appState.roleMap[dynamic] != null
     def mappedRole = appState.roleMap[dynamic]
-    assert mappedRole.id == priority_num_8
+    assert mappedRole.id == role_priority_8
 
     def priorityMap = appState.rolePriorityMap
     assert priorityMap.size() == 4
     ProviderRole dynamicProviderRole
-    assert (dynamicProviderRole = priorityMap[priority_num_8]) != null
-    assert dynamicProviderRole.id == priority_num_8
+    assert (dynamicProviderRole = priorityMap[role_priority_8]) != null
+    assert dynamicProviderRole.id == role_priority_8
 
-    assert null != appState.roleStatusMap[priority_num_8]
-    def dynamicRoleStatus = appState.roleStatusMap[priority_num_8]
+    assert null != appState.roleStatusMap[role_priority_8]
+    def dynamicRoleStatus = appState.roleStatusMap[role_priority_8]
     assert dynamicRoleStatus.desired == desired
 
     
@@ -135,6 +134,9 @@
     assert targetNode == engine.allocator.nextIndex()
     def targetHostname = engine.cluster.nodeAt(targetNode).hostname
 
+    // clock is set to a small value
+    appState.time = 100000
+    
     // allocate the nodes
     def actions = appState.reviewRequestAndReleaseNodes()
     assert actions.size() == 1
@@ -150,31 +152,52 @@
     RoleInstance ri = allocations[0]
     
     assert ri.role == dynamic
-    assert ri.roleId == priority_num_8
+    assert ri.roleId == role_priority_8
     assert ri.host.host == targetHostname
 
     // now look at the role history
 
     def roleHistory = appState.roleHistory
-    def activeNodes = roleHistory.listActiveNodes(priority_num_8)
+    def activeNodes = roleHistory.listActiveNodes(role_priority_8)
     assert activeNodes.size() == 1
     NodeInstance activeNode = activeNodes[0]
+    assert activeNode.get(role_priority_8)
+    def entry8 = activeNode.get(role_priority_8)
+    assert entry8.active == 1
 
     assert activeNode.hostname == targetHostname
-    
+
+    def activeNodeInstance = roleHistory.getOrCreateNodeInstance(ri.container)
+
+    assert activeNode == activeNodeInstance
+    def entry
+    assert (entry = activeNodeInstance.get(role_priority_8)) != null
+    assert entry.active
+    assert entry.live
+
+
     // now trigger a termination event on that role
+    
+    // increment time for a long-lived failure event
+    appState.time = appState.time + 100000
 
-
+    log.debug("Triggering failure")
     def cid = ri.id
-    // failure
     AppState.NodeCompletionResult result = appState.onCompletedNode(
         containerStatus(cid, 1))
     assert result.roleInstance == ri
     assert result.containerFailed
+    
+    roleHistory.dump();
+    // values should have changed
+    assert entry.failed == 1
+    assert !entry.startFailed
+    assert !entry.active
+    assert !entry.live
 
-    def nodeForNewInstance = roleHistory.findNodeForNewInstance(
-        dynamicRoleStatus)
-    assert nodeForNewInstance
+
+    def nodesForRoleId = roleHistory.getNodesForRoleId(role_priority_8)
+    assert nodesForRoleId
     
     // make sure new nodes will default to a different host in the engine
     assert targetNode < engine.allocator.nextIndex()
diff --git a/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/MockAppState.groovy b/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/MockAppState.groovy
index ad85b89..e683587 100644
--- a/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/MockAppState.groovy
+++ b/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/MockAppState.groovy
@@ -30,6 +30,8 @@
     super(recordFactory);
   }
 
+  long time = 0;
+  
   /**
    * Instance with a mock record factory
    */
@@ -40,4 +42,13 @@
   public Map<String, ProviderRole> getRoleMap() {
     return super.roleMap;
   }
+
+  /**
+   * Current time. if the <code>time</code> field
+   * is set, that value is returned
+   * @return the current time.
+   */
+  protected long now() {
+    return time ?: System.currentTimeMillis();
+  }
 }