SLIDER-587 test for role history on dynamic roles
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 406086a..2b5d0ee 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
@@ -641,33 +641,36 @@
   /**
    * The resource configuration is updated -review and update state.
    * @param resources updated resources specification
+   * @return a list of any dynamically added provider roles
+   * (purely for testing purposes)
    */
-  public synchronized void updateResourceDefinitions(ConfTree resources)
+  public synchronized List<ProviderRole> updateResourceDefinitions(ConfTree resources)
       throws BadConfigException, IOException {
     log.debug("Updating resources to {}", resources);
     
     instanceDefinition.setResources(resources);
     onInstanceDefinitionUpdated();
-    
-    
+ 
     //propagate the role table
-
     Map<String, Map<String, String>> updated = resources.components;
     getClusterStatus().roles = SliderUtils.deepClone(updated);
     getClusterStatus().updateTime = now();
-    buildRoleRequirementsFromResources();
+    return buildRoleRequirementsFromResources();
   }
 
   /**
    * build the role requirements from the cluster specification
+   * @return a list of any dynamically added provider roles
    */
-  private void buildRoleRequirementsFromResources() throws BadConfigException {
+  private List<ProviderRole> buildRoleRequirementsFromResources() throws BadConfigException {
 
+    List<ProviderRole> newRoles = new ArrayList<ProviderRole>(0);
+    
     //now update every role's desired count.
     //if there are no instance values, that role count goes to zero
 
     ConfTreeOperations resources =
-      instanceDefinition.getResourceOperations();
+        instanceDefinition.getResourceOperations();
 
     // Add all the existing roles
     for (RoleStatus roleStatus : getRoleStatusMap().values()) {
@@ -678,33 +681,38 @@
       int currentDesired = roleStatus.getDesired();
       String role = roleStatus.getName();
       MapOperations comp =
-        resources.getComponent(role);
+          resources.getComponent(role);
       int desiredInstanceCount = getDesiredInstanceCount(resources, role);
       if (desiredInstanceCount == 0) {
         log.info("Role {} has 0 instances specified", role);
-      }  
+      }
       if (currentDesired != desiredInstanceCount) {
         log.info("Role {} flexed from {} to {}", role, currentDesired,
-                 desiredInstanceCount);
+            desiredInstanceCount);
         roleStatus.setDesired(desiredInstanceCount);
       }
     }
+
     //now the dynamic ones. Iterate through the the cluster spec and
     //add any role status entries not in the role status
     Set<String> roleNames = resources.getComponentNames();
     for (String name : roleNames) {
       if (!roles.containsKey(name)) {
         // this is a new value
-        MapOperations component = resources.getComponent(name);
         log.info("Adding new role {}", name);
+        MapOperations component = resources.getComponent(name);
         ProviderRole dynamicRole = createDynamicProviderRole(name,
             component);
         RoleStatus roleStatus = buildRole(dynamicRole);
         roleStatus.setDesired(getDesiredInstanceCount(resources, name));
         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/RoleHistory.java b/slider-core/src/main/java/org/apache/slider/server/appmaster/state/RoleHistory.java
index 2b0ee18..33c3442 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
@@ -462,10 +462,6 @@
     }
   }
 
-  public synchronized void onAMRestart() {
-    //TODO once AM restart is implemented and we know what to expect
-  }
-
   /**
    * Find a node for use
    * @param role role
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
new file mode 100644
index 0000000..9a9ad23
--- /dev/null
+++ b/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicHistory.groovy
@@ -0,0 +1,188 @@
+/*
+ * 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.slider.server.appmaster.model.appstate
+
+import groovy.transform.CompileStatic
+import groovy.util.logging.Slf4j
+import org.apache.hadoop.conf.Configuration
+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
+import org.apache.slider.server.appmaster.model.mock.MockRoles
+import org.apache.slider.server.appmaster.model.mock.MockYarnEngine
+import org.apache.slider.server.appmaster.operations.ContainerRequestOperation
+import org.apache.slider.server.appmaster.state.AppState
+import org.apache.slider.server.appmaster.state.NodeInstance
+import org.apache.slider.server.appmaster.state.RoleInstance
+import org.apache.slider.server.appmaster.state.SimpleReleaseSelector
+import org.junit.Test
+
+/**
+ * Test that if you have >1 role, the right roles are chosen for release.
+ */
+@CompileStatic
+@Slf4j
+class TestMockAppStateDynamicHistory extends BaseMockAppStateTest
+    implements MockRoles {
+
+  @Override
+  String getTestName() {
+    return "TestMockAppStateDynamicHistory"
+  }
+
+  /**
+   * Small cluster with multiple containers per node,
+   * to guarantee many container allocations on each node
+   * @return
+   */
+  @Override
+  MockYarnEngine createYarnEngine() {
+    return new MockYarnEngine(8, 1)
+  }
+
+  @Override
+  void initApp() {
+    super.initApp()
+    appState = new MockAppState()
+    appState.setContainerLimits(RM_MAX_RAM, RM_MAX_CORES)
+
+    def instance = factory.newInstanceDefinition(0,0,0)
+
+    appState.buildInstance(
+        instance,
+        new Configuration(),
+        new Configuration(false),
+        factory.ROLES,
+        fs,
+        historyPath,
+        null,
+        null, new SimpleReleaseSelector())
+  }
+
+
+  @Test
+  public void testDynamicRoleHistory() throws Throwable {
+
+    def dynamic = "dynamicRole"
+    int priority_num_8 = 8
+    int desired = 1
+    int placementPolicy = 0
+    // snapshot and patch existing spec
+    def resources = ConfTreeOperations.fromInstance(
+        appState.resourcesSnapshot.confTree)
+    def opts = [
+        (ResourceKeys.COMPONENT_INSTANCES): ""+desired,
+        (ResourceKeys.COMPONENT_PRIORITY) : "" +priority_num_8,
+        (ResourceKeys.COMPONENT_PLACEMENT_POLICY): "" + placementPolicy
+    ]
+
+    resources.components[dynamic] = opts
+
+
+    // write the definitions
+    def updates = appState.updateResourceDefinitions(resources.confTree);
+    assert updates.size() == 1
+    def updatedRole = updates[0]
+    assert updatedRole.placementPolicy == placementPolicy
+
+    // verify the new role was persisted
+    def snapshotDefinition = appState.resourcesSnapshot.getMandatoryComponent(
+        dynamic)
+    assert snapshotDefinition.getMandatoryOptionInt(
+        ResourceKeys.COMPONENT_PRIORITY) == priority_num_8
+
+    // now look at the role map
+    assert appState.roleMap[dynamic] != null
+    def mappedRole = appState.roleMap[dynamic]
+    assert mappedRole.id == priority_num_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 null != appState.roleStatusMap[priority_num_8]
+    def dynamicRoleStatus = appState.roleStatusMap[priority_num_8]
+    assert dynamicRoleStatus.desired == desired
+
+    
+    // before allocating the nodes, fill up the capacity of some of the
+    // hosts
+    engine.allocator.nextIndex()
+
+    def targetNode = 2
+    assert targetNode == engine.allocator.nextIndex()
+    def targetHostname = engine.cluster.nodeAt(targetNode).hostname
+
+    // allocate the nodes
+    def actions = appState.reviewRequestAndReleaseNodes()
+    assert actions.size() == 1
+    def action0 = (ContainerRequestOperation)actions[0]
+
+    def request = action0.request
+    assert !request.nodes
+
+    List<ContainerId> released = []
+    List<RoleInstance> allocations = submitOperations(actions, released)
+    processSubmissionOperations(allocations, [], released)
+    assert allocations.size() == 1
+    RoleInstance ri = allocations[0]
+    
+    assert ri.role == dynamic
+    assert ri.roleId == priority_num_8
+    assert ri.host.host == targetHostname
+
+    // now look at the role history
+
+    def roleHistory = appState.roleHistory
+    def activeNodes = roleHistory.listActiveNodes(priority_num_8)
+    assert activeNodes.size() == 1
+    NodeInstance activeNode = activeNodes[0]
+
+    assert activeNode.hostname == targetHostname
+    
+    // now trigger a termination event on that role
+
+
+    def cid = ri.id
+    // failure
+    AppState.NodeCompletionResult result = appState.onCompletedNode(
+        containerStatus(cid, 1))
+    assert result.roleInstance == ri
+    assert result.containerFailed
+
+    def nodeForNewInstance = roleHistory.findNodeForNewInstance(
+        dynamicRoleStatus)
+    assert nodeForNewInstance
+    
+    // make sure new nodes will default to a different host in the engine
+    assert targetNode < engine.allocator.nextIndex()
+
+    actions = appState.reviewRequestAndReleaseNodes()
+    assert actions.size() == 1
+    def action1 = (ContainerRequestOperation) actions[0]
+    def request1 = action1.request
+    assert request1.nodes
+  }
+}
diff --git a/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicRoles.groovy b/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicRoles.groovy
index 5ef639b..902752c 100644
--- a/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicRoles.groovy
+++ b/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicRoles.groovy
@@ -28,7 +28,6 @@
 import org.apache.slider.server.appmaster.model.mock.MockAppState
 import org.apache.slider.server.appmaster.model.mock.MockRoles
 import org.apache.slider.server.appmaster.model.mock.MockYarnEngine
-import org.apache.slider.server.appmaster.operations.AbstractRMOperation
 import org.apache.slider.server.appmaster.state.RoleInstance
 import org.apache.slider.server.appmaster.state.SimpleReleaseSelector
 import org.junit.Test
@@ -86,55 +85,10 @@
   @Test
   public void testAllocateReleaseRealloc() throws Throwable {
 
-    List<RoleInstance> instances = createAndStartNodes()
-    List<AbstractRMOperation> ops = appState.reviewRequestAndReleaseNodes()
+    createAndStartNodes()
+    appState.reviewRequestAndReleaseNodes()
     appState.getRoleHistory().dump();
     
   }
 
-
-  @Test
-  public void testDynamicRoleHistory() throws Throwable {
-
-    // snapshot and patch existing spec
-    def resources = ConfTreeOperations.fromInstance(
-        appState.resourcesSnapshot.confTree)
-
-    def name = "dynamic"
-    def dynamicComp = resources.getOrAddComponent(name)
-    int priority = 8
-    int placement = 3
-    dynamicComp.put(ResourceKeys.COMPONENT_PRIORITY, "8")
-    dynamicComp.put(ResourceKeys.COMPONENT_INSTANCES, "1")
-    dynamicComp.put(ResourceKeys.COMPONENT_PLACEMENT_POLICY, "3")
-
-    def component = resources.getComponent(name)
-    String priOpt = component.getMandatoryOption(
-        ResourceKeys.COMPONENT_PRIORITY);
-    int parsedPriority = SliderUtils.parseAndValidate(
-        "value of " + name + " " + ResourceKeys.COMPONENT_PRIORITY,
-        priOpt, 0, 1, -1);
-    assert priority == parsedPriority
-
-    def newRole = appState.createDynamicProviderRole(name, component)
-    assert newRole.id == priority
-    
-    appState.updateResourceDefinitions(resources.confTree);
-    assert appState.roleMap[name] != null
-    def mappedRole = appState.roleMap[name]
-    assert mappedRole.id == priority
-
-    def priorityMap = appState.rolePriorityMap
-    assert priorityMap.size() == 4
-    def dynamicProviderRole
-    assert (dynamicProviderRole = priorityMap[priority]) != null
-    assert dynamicProviderRole.id == priority
-
-    // allocate the nodes
-    def allocations = createAndStartNodes()
-    assert allocations.size() == 1
-    RoleInstance ri = allocations[0]
-    assert ri.role == name
-    assert ri.roleId == priority
-  }
 }
diff --git a/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/Allocator.groovy b/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/Allocator.groovy
index 639c632..a027098 100644
--- a/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/Allocator.groovy
+++ b/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/Allocator.groovy
@@ -51,11 +51,11 @@
   MockContainer allocate(AMRMClient.ContainerRequest request) {
     MockYarnCluster.MockYarnClusterNode node = null
     MockYarnCluster.MockYarnClusterContainer allocated = null
-    if (request.nodes != null) {
+    if (request.nodes) {
       for (String host : request.nodes) {
         node = cluster.lookup(host)
         allocated = node.allocate()
-        if (allocated != null) {
+        if (allocated) {
           break
         }
       }
@@ -64,7 +64,7 @@
     if (allocated) {
       return createContainerRecord(request, allocated, node)
     } else {
-      if (request.relaxLocality || request.nodes.isEmpty()) {
+      if (request.relaxLocality || request.nodes.empty) {
         // fallback to anywhere
         return allocateRandom(request)
       } else {
@@ -117,7 +117,7 @@
     return container;
   }
 
-  private int nextIndex() {
+  public int nextIndex() {
     rollingIndex = (rollingIndex + 1) % cluster.clusterSize;
     return rollingIndex;
   }
diff --git a/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/BaseMockAppStateTest.groovy b/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/BaseMockAppStateTest.groovy
index fa54256..c48d7fa 100644
--- a/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/BaseMockAppStateTest.groovy
+++ b/slider-core/src/test/groovy/org/apache/slider/server/appmaster/model/mock/BaseMockAppStateTest.groovy
@@ -136,7 +136,7 @@
     Container target = assigned.container
     RoleInstance ri = new RoleInstance(target)
     ri.roleId = assigned.role.priority
-    ri.role = assigned.role
+    ri.role = assigned.role.name
     return ri
   }