You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by su...@apache.org on 2017/05/30 17:10:48 UTC

[28/50] [abbrv] hadoop git commit: YARN-6335. Port slider's groovy unit tests to yarn native services. Contributed by Billie Rinaldi

http://git-wip-us.apache.org/repos/asf/hadoop/blob/1eb168b3/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateAAOvercapacity.java
----------------------------------------------------------------------
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateAAOvercapacity.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateAAOvercapacity.java
new file mode 100644
index 0000000..e339a0a
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateAAOvercapacity.java
@@ -0,0 +1,112 @@
+/*
+ * 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 org.apache.hadoop.yarn.api.records.Container;
+import org.apache.hadoop.yarn.api.records.ContainerId;
+import org.apache.slider.core.main.LauncherExitCodes;
+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.AppState;
+import org.apache.slider.server.appmaster.state.NodeInstance;
+import org.apache.slider.server.appmaster.state.NodeMap;
+import org.apache.slider.server.appmaster.state.RoleInstance;
+import org.apache.slider.server.appmaster.state.RoleStatus;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test Anti-affine placement with a cluster of size 1.
+ */
+public class TestMockAppStateAAOvercapacity extends BaseMockAppStateAATest
+    implements MockRoles {
+
+  private static final int NODES = 1;
+
+  @Override
+  public MockYarnEngine createYarnEngine() {
+    return new MockYarnEngine(NODES, 1);
+  }
+
+  void assertAllContainersAA() {
+    assertAllContainersAA(getAaRole().getKey());
+  }
+
+  /**
+   *
+   * @throws Throwable
+   */
+  @Test
+  public void testOvercapacityRecovery() throws Throwable {
+    RoleStatus aaRole = getAaRole();
+
+    describe("Ask for 1 more than the no of available nodes;" +
+             "verify the state. kill the allocated container and review");
+    //more than expected
+    int desired = 3;
+    aaRole.setDesired(desired);
+    assertTrue(appState.getRoleHistory().canPlaceAANodes());
+
+    //first request
+    List<AbstractRMOperation> operations =
+        appState.reviewRequestAndReleaseNodes();
+    assertTrue(aaRole.isAARequestOutstanding());
+    assertEquals(1, aaRole.getRequested());
+    assertEquals(desired - 1, aaRole.getAAPending());
+    List<AbstractRMOperation> operationsOut = new ArrayList<>();
+    // allocate and re-submit
+    List<RoleInstance> instances = submitOperations(operations,
+        EMPTY_ID_LIST, operationsOut);
+    assertEquals(1, instances.size());
+    assertAllContainersAA();
+
+    // expect an outstanding AA request to be unsatisfied
+    assertTrue(aaRole.getRunning() < aaRole.getDesired());
+    assertEquals(0, aaRole.getRequested());
+    assertFalse(aaRole.isAARequestOutstanding());
+    assertEquals(desired - 1, aaRole.getAAPending());
+    List<Container> allocatedContainers = engine.execute(operations,
+        EMPTY_ID_LIST);
+    assertEquals(0, allocatedContainers.size());
+
+    // now lets trigger a failure
+    NodeMap nodemap = cloneNodemap();
+    assertEquals(1, nodemap.size());
+
+    RoleInstance instance = instances.get(0);
+    ContainerId cid = instance.getContainerId();
+
+    AppState.NodeCompletionResult result = appState.onCompletedContainer(
+        containerStatus(cid, LauncherExitCodes.EXIT_TASK_LAUNCH_FAILURE));
+    assertTrue(result.containerFailed);
+
+    assertEquals(1, aaRole.getFailed());
+    assertEquals(0, aaRole.getRunning());
+    List<NodeInstance> availablePlacements = appState.getRoleHistory()
+        .findNodeForNewAAInstance(aaRole);
+    assertEquals(1, availablePlacements.size());
+    describe("expecting a successful review with available placements of " +
+            availablePlacements);
+    operations = appState.reviewRequestAndReleaseNodes();
+    assertEquals(1, operations.size());
+  }
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/1eb168b3/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateAAPlacement.java
----------------------------------------------------------------------
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateAAPlacement.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateAAPlacement.java
new file mode 100644
index 0000000..eb25b40
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateAAPlacement.java
@@ -0,0 +1,380 @@
+/*
+ * 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 org.apache.hadoop.yarn.api.records.Container;
+import org.apache.hadoop.yarn.api.records.NodeState;
+import org.apache.hadoop.yarn.client.api.AMRMClient;
+import org.apache.hadoop.yarn.client.api.AMRMClient.ContainerRequest;
+import org.apache.slider.api.types.NodeInformation;
+import org.apache.slider.common.tools.SliderUtils;
+import org.apache.slider.providers.PlacementPolicy;
+import org.apache.slider.server.appmaster.model.mock.MockAppState;
+import org.apache.slider.server.appmaster.model.mock.MockFactory;
+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.AppState;
+import org.apache.slider.server.appmaster.state.AppState.NodeUpdatedOutcome;
+import org.apache.slider.server.appmaster.state.AppStateBindingInfo;
+import org.apache.slider.server.appmaster.state.ContainerAssignment;
+import org.apache.slider.server.appmaster.state.NodeInstance;
+import org.apache.slider.server.appmaster.state.RoleInstance;
+import org.apache.slider.server.appmaster.state.RoleStatus;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.slider.api.ResourceKeys.COMPONENT_PLACEMENT_POLICY;
+import static org.apache.slider.server.appmaster.model.mock.MockFactory.AAROLE_2;
+
+/**
+ * Test Anti-affine placement.
+ */
+public class TestMockAppStateAAPlacement extends BaseMockAppStateAATest
+    implements MockRoles {
+  private static final Logger LOG =
+      LoggerFactory.getLogger(TestMockAppStateAAPlacement.class);
+
+  private static final int NODES = 3;
+
+  /**
+   * The YARN engine has a cluster with very few nodes (3) and lots of
+   * containers, so if AA placement isn't working, there will be affine
+   * placements surfacing.
+   * @return
+   */
+  @Override
+  public MockYarnEngine createYarnEngine() {
+    return new MockYarnEngine(NODES, 8);
+  }
+
+  /**
+   * This is the simplest AA allocation: no labels, so allocate anywhere.
+   * @throws Throwable
+   */
+  @Test
+  public void testAllocateAANoLabel() throws Throwable {
+    RoleStatus aaRole = getAaRole();
+
+    assertTrue(cloneNodemap().size() > 0);
+
+    // want multiple instances, so there will be iterations
+    aaRole.setDesired(2);
+
+    List<AbstractRMOperation> ops = appState.reviewRequestAndReleaseNodes();
+    AMRMClient.ContainerRequest request = getSingleRequest(ops);
+    assertFalse(request.getRelaxLocality());
+    assertEquals(request.getNodes().size(), engine.getCluster()
+        .getClusterSize());
+    assertNull(request.getRacks());
+    assertNotNull(request.getCapability());
+
+    Container allocated = engine.allocateContainer(request);
+
+    // notify the container ane expect
+    List<ContainerAssignment> assignments = new ArrayList<>();
+    List<AbstractRMOperation> operations = new ArrayList<>();
+    appState.onContainersAllocated(Arrays.asList(allocated), assignments,
+        operations);
+
+    String host = allocated.getNodeId().getHost();
+    NodeInstance hostInstance = cloneNodemap().get(host);
+    assertEquals(1, hostInstance.get(aaRole.getKey()).getStarting());
+    assertFalse(hostInstance.canHost(aaRole.getKey(), ""));
+    assertFalse(hostInstance.canHost(aaRole.getKey(), null));
+
+    // assignment
+    assertEquals(1, assignments.size());
+
+    // verify the release matches the allocation
+    assertEquals(2, operations.size());
+    assertNotNull(getCancel(operations, 0).getCapability().equals(allocated
+            .getResource()));
+
+    // we also expect a new allocation request to have been issued
+
+    ContainerRequest req2 = getRequest(operations, 1);
+    assertEquals(req2.getNodes().size(), engine.getCluster()
+        .getClusterSize() - 1);
+
+    assertFalse(req2.getNodes().contains(host));
+    assertFalse(request.getRelaxLocality());
+
+    // verify the pending couner is down
+    assertEquals(0L, aaRole.getAAPending());
+    Container allocated2 = engine.allocateContainer(req2);
+
+    // placement must be on a different host
+    assertNotEquals(allocated2.getNodeId(), allocated.getNodeId());
+
+    ContainerAssignment assigned = assignments.get(0);
+    Container container = assigned.container;
+    RoleInstance ri = roleInstance(assigned);
+    //tell the app it arrived
+    appState.containerStartSubmitted(container, ri);
+    assertNotNull(appState.onNodeManagerContainerStarted(container.getId()));
+    ops = appState.reviewRequestAndReleaseNodes();
+    assertEquals(0, ops.size());
+    assertAllContainersAA();
+
+    // identify those hosts with an aa role on
+    Map<Integer, String> naming = appState.buildNamingMap();
+    assertEquals(3, naming.size());
+
+    String name = aaRole.getName();
+    assertEquals(name, naming.get(aaRole.getKey()));
+    Map<String, NodeInformation> info =
+        appState.getRoleHistory().getNodeInformationSnapshot(naming);
+    assertTrue(SliderUtils.isNotEmpty(info));
+
+    NodeInformation nodeInformation = info.get(host);
+    assertNotNull(nodeInformation);
+    assertTrue(SliderUtils.isNotEmpty(nodeInformation.entries));
+    assertNotNull(nodeInformation.entries.get(name));
+    assertEquals(1, nodeInformation.entries.get(name).live);
+  }
+
+  @Test
+  public void testAllocateFlexUp() throws Throwable {
+    RoleStatus aaRole = getAaRole();
+
+    // want multiple instances, so there will be iterations
+    aaRole.setDesired(2);
+    List<AbstractRMOperation> ops = appState.reviewRequestAndReleaseNodes();
+    getSingleRequest(ops);
+    assertEquals(1, aaRole.getRequested());
+    assertEquals(1, aaRole.getAAPending());
+    assertEquals(aaRole.getActualAndRequested() + aaRole
+            .getAAPending(), aaRole.getDesired());
+
+    // now trigger that flex up
+    aaRole.setDesired(3);
+
+    // expect: no new reqests, pending count ++
+    List<AbstractRMOperation> ops2 = appState.reviewRequestAndReleaseNodes();
+    assertTrue(ops2.isEmpty());
+    assertEquals(aaRole.getRunning() + aaRole.getAAPending() +
+            aaRole.getOutstandingAARequestCount(), aaRole.getDesired());
+
+    // 1 outstanding
+    assertEquals(0, aaRole.getRunning());
+    assertTrue(aaRole.isAARequestOutstanding());
+    // and one AA
+    assertEquals(2, aaRole.getAAPending());
+    assertAllContainersAA();
+
+    // next iter
+    assertEquals(1, submitOperations(ops, EMPTY_ID_LIST, ops2).size());
+    assertEquals(2, ops2.size());
+    assertEquals(1, aaRole.getAAPending());
+    assertAllContainersAA();
+
+    assertEquals(0, appState.reviewRequestAndReleaseNodes().size());
+    // now trigger the next execution cycle
+    List<AbstractRMOperation> ops3 = new ArrayList<>();
+    assertEquals(1, submitOperations(ops2, EMPTY_ID_LIST, ops3).size());
+    assertEquals(2, ops3.size());
+    assertEquals(0, aaRole.getAAPending());
+    assertAllContainersAA();
+
+  }
+
+  @Test
+  public void testAllocateFlexDownDecrementsPending() throws Throwable {
+    RoleStatus aaRole = getAaRole();
+
+    // want multiple instances, so there will be iterations
+    aaRole.setDesired(2);
+    List<AbstractRMOperation> ops = appState.reviewRequestAndReleaseNodes();
+    getSingleRequest(ops);
+    assertEquals(1, aaRole.getAAPending());
+    assertTrue(aaRole.isAARequestOutstanding());
+
+    // flex down so that the next request should be cancelled
+    aaRole.setDesired(1);
+
+    // expect: no new requests, pending count --
+    List<AbstractRMOperation> ops2 = appState.reviewRequestAndReleaseNodes();
+    assertTrue(ops2.isEmpty());
+    assertTrue(aaRole.isAARequestOutstanding());
+    assertEquals(0, aaRole.getAAPending());
+    assertAllContainersAA();
+
+    // next iter
+    submitOperations(ops, EMPTY_ID_LIST, ops2).size();
+    assertEquals(1, ops2.size());
+    assertAllContainersAA();
+  }
+
+  /**
+   * Here flex down while there is only one outstanding request.
+   * The outstanding flex should be cancelled
+   * @throws Throwable
+   */
+  @Test
+  public void testAllocateFlexDownForcesCancel() throws Throwable {
+    RoleStatus aaRole = getAaRole();
+
+    // want multiple instances, so there will be iterations
+    aaRole.setDesired(1);
+    List<AbstractRMOperation> ops = appState.reviewRequestAndReleaseNodes();
+    getSingleRequest(ops);
+    assertEquals(1, aaRole.getRequested());
+    assertEquals(0, aaRole.getAAPending());
+    assertTrue(aaRole.isAARequestOutstanding());
+
+    // flex down so that the next request should be cancelled
+    aaRole.setDesired(0);
+    // expect: no new requests, pending count --
+    List<AbstractRMOperation> ops2 = appState.reviewRequestAndReleaseNodes();
+    assertEquals(0, aaRole.getRequested());
+    assertEquals(0, aaRole.getAAPending());
+    assertFalse(aaRole.isAARequestOutstanding());
+    assertEquals(1, ops2.size());
+    getSingleCancel(ops2);
+
+    // next iter
+    submitOperations(ops, EMPTY_ID_LIST, ops2).size();
+    getSingleRelease(ops2);
+  }
+
+  void assertAllContainersAA() {
+    assertAllContainersAA(getAaRole().getKey());
+  }
+
+  /**
+   *
+   * @throws Throwable
+   */
+  @Test
+  public void testAskForTooMany() throws Throwable {
+    RoleStatus aaRole = getAaRole();
+
+    describe("Ask for 1 more than the no of available nodes;" +
+        " expect the final request to be unsatisfied until the cluster " +
+        "changes size");
+    //more than expected
+    aaRole.setDesired(NODES + 1);
+    List<AbstractRMOperation > operations = appState
+        .reviewRequestAndReleaseNodes();
+    assertTrue(aaRole.isAARequestOutstanding());
+    assertEquals(NODES, aaRole.getAAPending());
+    for (int i = 0; i < NODES; i++) {
+      String iter = "Iteration " + i + " role = " + aaRole;
+      LOG.info(iter);
+      List<AbstractRMOperation > operationsOut = new ArrayList<>();
+      assertEquals(1, submitOperations(operations, EMPTY_ID_LIST,
+          operationsOut).size());
+      operations = operationsOut;
+      if (i + 1 < NODES) {
+        assertEquals(2, operations.size());
+      } else {
+        assertEquals(1, operations.size());
+      }
+      assertAllContainersAA();
+    }
+    // expect an outstanding AA request to be unsatisfied
+    assertTrue(aaRole.getRunning() < aaRole.getDesired());
+    assertEquals(0, aaRole.getRequested());
+    assertFalse(aaRole.isAARequestOutstanding());
+    List<Container> allocatedContainers = engine.execute(operations,
+        EMPTY_ID_LIST);
+    assertEquals(0, allocatedContainers.size());
+    // in a review now, no more requests can be generated, as there is no
+    // space for AA placements, even though there is cluster capacity
+    assertEquals(0, appState.reviewRequestAndReleaseNodes().size());
+
+    // now do a node update (this doesn't touch the YARN engine; the node
+    // isn't really there)
+    NodeUpdatedOutcome outcome = addNewNode();
+    assertEquals(cloneNodemap().size(), NODES + 1);
+    assertTrue(outcome.clusterChanged);
+    // no active calls to empty
+    assertTrue(outcome.operations.isEmpty());
+    assertEquals(1, appState.reviewRequestAndReleaseNodes().size());
+  }
+
+  protected AppState.NodeUpdatedOutcome addNewNode() {
+    return updateNodes(MockFactory.INSTANCE.newNodeReport("4", NodeState
+        .RUNNING, "gpu"));
+  }
+
+  @Test
+  public void testClusterSizeChangesDuringRequestSequence() throws Throwable {
+    RoleStatus aaRole = getAaRole();
+    describe("Change the cluster size where the cluster size changes during " +
+        "a test sequence.");
+    aaRole.setDesired(NODES + 1);
+    appState.reviewRequestAndReleaseNodes();
+    assertTrue(aaRole.isAARequestOutstanding());
+    assertEquals(NODES, aaRole.getAAPending());
+    NodeUpdatedOutcome outcome = addNewNode();
+    assertTrue(outcome.clusterChanged);
+    // one call to cancel
+    assertEquals(1, outcome.operations.size());
+    // and on a review, one more to rebuild
+    assertEquals(1, appState.reviewRequestAndReleaseNodes().size());
+  }
+
+  @Test
+  public void testBindingInfoMustHaveNodeMap() throws Throwable {
+    AppStateBindingInfo bindingInfo = buildBindingInfo();
+    bindingInfo.nodeReports = null;
+    try {
+      MockAppState state = new MockAppState(bindingInfo);
+      fail("Expected an exception, got " + state);
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+
+  @Test
+  public void testAMRestart() throws Throwable {
+    int desiredAA = 3;
+    getAaRole().setDesired(desiredAA);
+    List<RoleInstance> instances = createAndStartNodes();
+    List<Container> containers = new ArrayList<>();
+    for (RoleInstance instance : instances) {
+      containers.add(instance.container);
+    }
+
+    // now destroy the app state
+    AppStateBindingInfo bindingInfo = buildBindingInfo();
+    bindingInfo.application = factory.newApplication(0, 0, desiredAA).name(
+        getTestName());
+    bindingInfo.application.getComponent(ROLE2)
+        .getConfiguration().setProperty(COMPONENT_PLACEMENT_POLICY,
+        Integer.toString(PlacementPolicy.ANTI_AFFINITY_REQUIRED));
+    bindingInfo.liveContainers = containers;
+    appState = new MockAppState(bindingInfo);
+
+    RoleStatus aaRole = lookupRole(AAROLE_2.name);
+    RoleStatus gpuRole = lookupRole(MockFactory.AAROLE_1_GPU.name);
+    appState.reviewRequestAndReleaseNodes();
+    assertTrue(aaRole.isAntiAffinePlacement());
+    assertTrue(aaRole.isAARequestOutstanding());
+
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/1eb168b3/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateContainerFailure.java
----------------------------------------------------------------------
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateContainerFailure.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateContainerFailure.java
new file mode 100644
index 0000000..ea0dcf4
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateContainerFailure.java
@@ -0,0 +1,387 @@
+/*
+ * 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 org.apache.hadoop.yarn.api.records.ContainerId;
+import org.apache.slider.api.ResourceKeys;
+import org.apache.slider.api.resource.Application;
+import org.apache.slider.core.exceptions.SliderException;
+import org.apache.slider.core.exceptions.TriggerClusterTeardownException;
+import org.apache.slider.server.appmaster.actions.ResetFailureWindow;
+import org.apache.slider.server.appmaster.model.mock.BaseMockAppStateTest;
+import org.apache.slider.server.appmaster.model.mock.MockAM;
+import org.apache.slider.server.appmaster.model.mock.MockAppState;
+import org.apache.slider.server.appmaster.model.mock.MockRMOperationHandler;
+import org.apache.slider.server.appmaster.model.mock.MockRoles;
+import org.apache.slider.server.appmaster.model.mock.MockYarnEngine;
+import org.apache.slider.server.appmaster.state.AppState;
+import org.apache.slider.server.appmaster.state.AppStateBindingInfo;
+import org.apache.slider.server.appmaster.state.ContainerOutcome;
+import org.apache.slider.server.appmaster.state.NodeEntry;
+import org.apache.slider.server.appmaster.state.NodeInstance;
+import org.apache.slider.server.appmaster.state.RoleHistory;
+import org.apache.slider.server.appmaster.state.RoleInstance;
+import org.apache.slider.server.appmaster.state.RoleStatus;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+/**
+ * Test that if you have >1 role, the right roles are chosen for release.
+ */
+public class TestMockAppStateContainerFailure extends BaseMockAppStateTest
+    implements MockRoles {
+  private static final Logger LOG =
+      LoggerFactory.getLogger(TestMockAppStateContainerFailure.class);
+
+  private MockRMOperationHandler operationHandler = new
+      MockRMOperationHandler();
+  private MockAM mockAM = new MockAM();
+
+  @Override
+  public String getTestName() {
+    return "TestMockAppStateContainerFailure";
+  }
+
+  /**
+   * Small cluster with multiple containers per node,
+   * to guarantee many container allocations on each node.
+   * @return
+   */
+  @Override
+  public MockYarnEngine createYarnEngine() {
+    return new MockYarnEngine(4, 8000);
+  }
+
+  @Override
+  public Application buildApplication() {
+    Application application = super.buildApplication();
+    application.getConfiguration().setProperty(
+        ResourceKeys.CONTAINER_FAILURE_THRESHOLD, "10");
+    return application;
+  }
+
+  @Test
+  public void testShortLivedFail() throws Throwable {
+
+    getRole0Status().setDesired(1);
+    List<RoleInstance> instances = createAndStartNodes();
+    assertEquals(1, instances.size());
+
+    RoleInstance instance = instances.get(0);
+    long created = instance.createTime;
+    long started = instance.startTime;
+    assertTrue(created > 0);
+    assertTrue(started >= created);
+    List<ContainerId> ids = extractContainerIds(instances, ROLE0);
+
+    ContainerId cid = ids.get(0);
+    assertTrue(appState.isShortLived(instance));
+    AppState.NodeCompletionResult result = appState.onCompletedContainer(
+        containerStatus(cid, 1));
+    assertNotNull(result.roleInstance);
+    assertTrue(result.containerFailed);
+    RoleStatus status = getRole0Status();
+    assertEquals(1, status.getFailed());
+//    assertEquals(1, status.getStartFailed());
+
+    //view the world
+    appState.getRoleHistory().dump();
+    List<NodeInstance> queue = appState.getRoleHistory().cloneRecentNodeList(
+        getRole0Status().getKey());
+    assertEquals(0, queue.size());
+
+  }
+
+  @Test
+  public void testLongLivedFail() throws Throwable {
+
+    getRole0Status().setDesired(1);
+    List<RoleInstance> instances = createAndStartNodes();
+    assertEquals(1, instances.size());
+
+    RoleInstance instance = instances.get(0);
+    instance.startTime = System.currentTimeMillis() - 60 * 60 * 1000;
+    assertFalse(appState.isShortLived(instance));
+    List<ContainerId> ids = extractContainerIds(instances, ROLE0);
+
+    ContainerId cid = ids.get(0);
+    AppState.NodeCompletionResult result = appState.onCompletedContainer(
+        containerStatus(cid, 1));
+    assertNotNull(result.roleInstance);
+    assertTrue(result.containerFailed);
+    RoleStatus status = getRole0Status();
+    assertEquals(1, status.getFailed());
+//    assertEquals(0, status.getStartFailed());
+
+    //view the world
+    appState.getRoleHistory().dump();
+    List<NodeInstance> queue = appState.getRoleHistory().cloneRecentNodeList(
+        getRole0Status().getKey());
+    assertEquals(1, queue.size());
+
+  }
+
+  @Test
+  public void testNodeStartFailure() throws Throwable {
+
+    getRole0Status().setDesired(1);
+    List<RoleInstance> instances = createAndSubmitNodes();
+    assertEquals(1, instances.size());
+
+    RoleInstance instance = instances.get(0);
+
+    List<ContainerId> ids = extractContainerIds(instances, ROLE0);
+
+    ContainerId cid = ids.get(0);
+    appState.onNodeManagerContainerStartFailed(cid, new SliderException(
+        "oops"));
+    RoleStatus status = getRole0Status();
+    assertEquals(1, status.getFailed());
+//    assertEquals(1, status.getStartFailed());
+
+
+    RoleHistory history = appState.getRoleHistory();
+    history.dump();
+    List<NodeInstance> queue = history.cloneRecentNodeList(getRole0Status()
+        .getKey());
+    assertEquals(0, queue.size());
+
+    NodeInstance ni = history.getOrCreateNodeInstance(instance.container);
+    NodeEntry re = ni.get(getRole0Status().getKey());
+    assertEquals(1, re.getFailed());
+    assertEquals(1, re.getStartFailed());
+  }
+
+  @Test
+  public void testRecurrentStartupFailure() throws Throwable {
+
+    getRole0Status().setDesired(1);
+    try {
+      for (int i = 0; i< 100; i++) {
+        List<RoleInstance> instances = createAndSubmitNodes();
+        assertEquals(1, instances.size());
+
+        List<ContainerId> ids = extractContainerIds(instances, ROLE0);
+
+        ContainerId cid = ids.get(0);
+        LOG.info("{} instance {} {}", i, instances.get(0), cid);
+        assertNotNull(cid);
+        appState.onNodeManagerContainerStartFailed(cid,
+            new SliderException("failure #" + i));
+        AppState.NodeCompletionResult result = appState.onCompletedContainer(
+            containerStatus(cid));
+        assertTrue(result.containerFailed);
+      }
+      fail("Cluster did not fail from too many startup failures");
+    } catch (TriggerClusterTeardownException teardown) {
+      LOG.info("Exception {} : {}", teardown.getExitCode(), teardown);
+    }
+  }
+
+  @Test
+  public void testRecurrentStartupFailureWithUnlimitedFailures() throws
+      Throwable {
+    // Update instance definition to allow containers to fail any number of
+    // times
+    AppStateBindingInfo bindingInfo = buildBindingInfo();
+    bindingInfo.application.getConfiguration().setProperty(
+        ResourceKeys.CONTAINER_FAILURE_THRESHOLD, "0");
+    appState = new MockAppState(bindingInfo);
+
+    getRole0Status().setDesired(1);
+    try {
+      for (int i = 0; i < 100; i++) {
+        List<RoleInstance> instances = createAndSubmitNodes();
+        assertEquals(1, instances.size());
+
+        List<ContainerId> ids = extractContainerIds(instances, ROLE0);
+
+        ContainerId cid = ids.get(0);
+        LOG.info("{} instance {} {}", i, instances.get(0), cid);
+        assertNotNull(cid);
+        appState.onNodeManagerContainerStartFailed(cid,
+            new SliderException("failure #" + i));
+        AppState.NodeCompletionResult result = appState.onCompletedContainer(
+            containerStatus(cid));
+        assertTrue(result.containerFailed);
+      }
+    } catch (TriggerClusterTeardownException teardown) {
+      LOG.info("Exception {} : {}", teardown.getExitCode(), teardown);
+      fail("Cluster failed despite " + ResourceKeys
+          .CONTAINER_FAILURE_THRESHOLD + " = 0");
+    }
+  }
+
+  @Test
+  public void testRoleStatusFailureWindow() throws Throwable {
+
+    ResetFailureWindow resetter = new ResetFailureWindow(operationHandler);
+
+    // initial reset
+    resetter.execute(mockAM, null, appState);
+
+    getRole0Status().setDesired(1);
+    for (int i = 0; i < 100; i++) {
+      resetter.execute(mockAM, null, appState);
+      List<RoleInstance> instances = createAndSubmitNodes();
+      assertEquals(1, instances.size());
+
+      List<ContainerId> ids = extractContainerIds(instances, ROLE0);
+
+      ContainerId cid = ids.get(0);
+      LOG.info("{} instance {} {}", i, instances.get(0), cid);
+      assertNotNull(cid);
+      appState.onNodeManagerContainerStartFailed(
+          cid,
+          new SliderException("failure #" + i));
+      AppState.NodeCompletionResult result = appState.onCompletedContainer(
+          containerStatus(cid));
+      assertTrue(result.containerFailed);
+    }
+  }
+
+  @Test
+  public void testRoleStatusFailed() throws Throwable {
+    RoleStatus status = getRole0Status();
+    // limits exceeded
+    appState.incFailedContainers(status, ContainerOutcome.Failed);
+    assertEquals(1, status.getFailed());
+    assertEquals(1L, status.getFailedRecently());
+    assertEquals(0L, status.getLimitsExceeded());
+    assertEquals(0L, status.getPreempted());
+    assertEquals(0L, status.getDiskFailed());
+
+    ResetFailureWindow resetter = new ResetFailureWindow(operationHandler);
+    resetter.execute(mockAM, null, appState);
+    assertEquals(1, status.getFailed());
+    assertEquals(0L, status.getFailedRecently());
+  }
+
+  @Test
+  public void testRoleStatusFailedLimitsExceeded() throws Throwable {
+    RoleStatus status = getRole0Status();
+    // limits exceeded
+    appState.incFailedContainers(status, ContainerOutcome
+        .Failed_limits_exceeded);
+    assertEquals(1, status.getFailed());
+    assertEquals(1L, status.getFailedRecently());
+    assertEquals(1L, status.getLimitsExceeded());
+    assertEquals(0L, status.getPreempted());
+    assertEquals(0L, status.getDiskFailed());
+
+    ResetFailureWindow resetter = new ResetFailureWindow(operationHandler);
+    resetter.execute(mockAM, null, appState);
+    assertEquals(1, status.getFailed());
+    assertEquals(0L, status.getFailedRecently());
+    assertEquals(1L, status.getLimitsExceeded());
+  }
+
+
+  @Test
+  public void testRoleStatusFailedPrempted() throws Throwable {
+    RoleStatus status = getRole0Status();
+    // limits exceeded
+    appState.incFailedContainers(status, ContainerOutcome.Preempted);
+    assertEquals(0, status.getFailed());
+    assertEquals(1L, status.getPreempted());
+    assertEquals(0L, status.getFailedRecently());
+    assertEquals(0L, status.getDiskFailed());
+
+    ResetFailureWindow resetter = new ResetFailureWindow(operationHandler);
+    resetter.execute(mockAM, null, appState);
+    assertEquals(1L, status.getPreempted());
+  }
+
+
+  @Test
+  public void testRoleStatusFailedNode() throws Throwable {
+    RoleStatus status = getRole0Status();
+    // limits exceeded
+    appState.incFailedContainers(status, ContainerOutcome.Disk_failure);
+    assertEquals(1, status.getFailed());
+    assertEquals(0L, status.getFailedRecently());
+    assertEquals(0L, status.getLimitsExceeded());
+    assertEquals(0L, status.getPreempted());
+    assertEquals(1L, status.getDiskFailed());
+  }
+
+  @Test
+  public void testNodeEntryCompleted() throws Throwable {
+    NodeEntry nodeEntry = new NodeEntry(1);
+    nodeEntry.containerCompleted(true, ContainerOutcome.Completed);
+    assertEquals(0, nodeEntry.getFailed());
+    assertEquals(0, nodeEntry.getFailedRecently());
+    assertEquals(0, nodeEntry.getStartFailed());
+    assertEquals(0, nodeEntry.getPreempted());
+    assertEquals(0, nodeEntry.getActive());
+    assertTrue(nodeEntry.isAvailable());
+  }
+
+  @Test
+  public void testNodeEntryFailed() throws Throwable {
+    NodeEntry nodeEntry = new NodeEntry(1);
+    nodeEntry.containerCompleted(false, ContainerOutcome.Failed);
+    assertEquals(1, nodeEntry.getFailed());
+    assertEquals(1, nodeEntry.getFailedRecently());
+    assertEquals(0, nodeEntry.getStartFailed());
+    assertEquals(0, nodeEntry.getPreempted());
+    assertEquals(0, nodeEntry.getActive());
+    assertTrue(nodeEntry.isAvailable());
+    nodeEntry.resetFailedRecently();
+    assertEquals(1, nodeEntry.getFailed());
+    assertEquals(0, nodeEntry.getFailedRecently());
+  }
+
+  @Test
+  public void testNodeEntryLimitsExceeded() throws Throwable {
+    NodeEntry nodeEntry = new NodeEntry(1);
+    nodeEntry.containerCompleted(false, ContainerOutcome
+        .Failed_limits_exceeded);
+    assertEquals(0, nodeEntry.getFailed());
+    assertEquals(0, nodeEntry.getFailedRecently());
+    assertEquals(0, nodeEntry.getStartFailed());
+    assertEquals(0, nodeEntry.getPreempted());
+  }
+
+  @Test
+  public void testNodeEntryPreempted() throws Throwable {
+    NodeEntry nodeEntry = new NodeEntry(1);
+    nodeEntry.containerCompleted(false, ContainerOutcome.Preempted);
+    assertEquals(0, nodeEntry.getFailed());
+    assertEquals(0, nodeEntry.getFailedRecently());
+    assertEquals(0, nodeEntry.getStartFailed());
+    assertEquals(1, nodeEntry.getPreempted());
+  }
+
+  @Test
+  public void testNodeEntryNodeFailure() throws Throwable {
+    NodeEntry nodeEntry = new NodeEntry(1);
+    nodeEntry.containerCompleted(false, ContainerOutcome.Disk_failure);
+    assertEquals(1, nodeEntry.getFailed());
+    assertEquals(1, nodeEntry.getFailedRecently());
+    assertEquals(0, nodeEntry.getStartFailed());
+    assertEquals(0, nodeEntry.getPreempted());
+  }
+
+
+
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/1eb168b3/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicHistory.java
----------------------------------------------------------------------
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicHistory.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicHistory.java
new file mode 100644
index 0000000..da2ed0d
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicHistory.java
@@ -0,0 +1,212 @@
+/*
+ * 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 org.apache.hadoop.yarn.api.records.ContainerId;
+import org.apache.hadoop.yarn.client.api.AMRMClient.ContainerRequest;
+import org.apache.slider.api.ResourceKeys;
+import org.apache.slider.api.resource.Application;
+import org.apache.slider.api.resource.Component;
+import org.apache.slider.common.tools.SliderUtils;
+import org.apache.slider.core.exceptions.BadConfigException;
+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.MockRoleHistory;
+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.operations.ContainerRequestOperation;
+import org.apache.slider.server.appmaster.state.AppState;
+import org.apache.slider.server.appmaster.state.NodeEntry;
+import org.apache.slider.server.appmaster.state.NodeInstance;
+import org.apache.slider.server.appmaster.state.RoleHistory;
+import org.apache.slider.server.appmaster.state.RoleInstance;
+import org.apache.slider.server.appmaster.state.RoleStatus;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Test that if you have >1 role, the right roles are chosen for release.
+ */
+public class TestMockAppStateDynamicHistory extends BaseMockAppStateTest
+    implements MockRoles {
+  private static final Logger LOG =
+      LoggerFactory.getLogger(TestMockAppStateDynamicHistory.class);
+
+  /**
+   * Small cluster with multiple containers per node,
+   * to guarantee many container allocations on each node.
+   * @return
+   */
+  @Override
+  public MockYarnEngine createYarnEngine() {
+    return new MockYarnEngine(8, 1);
+  }
+
+  @Test
+  public void testDynamicRoleHistory() throws Throwable {
+
+    String dynamic = "dynamicRole";
+    long desired = 1;
+    int placementPolicy = PlacementPolicy.DEFAULT;
+    // snapshot and patch existing spec
+    Application application = appState.getClusterStatus();
+    Component component = new Component().name(dynamic).numberOfContainers(
+        desired);
+    component.getConfiguration().setProperty(ResourceKeys
+        .COMPONENT_PLACEMENT_POLICY, "" + placementPolicy);
+    application.getComponents().add(component);
+
+    // write the definitions
+    List<ProviderRole> updates = appState.updateComponents(
+        Collections.singletonMap(dynamic, desired));
+    assertEquals(1, updates.size());
+    ProviderRole updatedRole = updates.get(0);
+    assertEquals(updatedRole.placementPolicy, placementPolicy);
+
+    // now look at the role map
+    assertNotNull(appState.getRoleMap().get(dynamic));
+    ProviderRole mappedRole = appState.getRoleMap().get(dynamic);
+    int rolePriority = mappedRole.id;
+
+    Map<Integer, ProviderRole> priorityMap = appState.getRolePriorityMap();
+    assertEquals(priorityMap.size(), 4);
+    ProviderRole dynamicProviderRole = priorityMap.get(rolePriority);
+    assertNotNull(dynamicProviderRole);
+    assertEquals(dynamicProviderRole.id, rolePriority);
+
+    assertNotNull(appState.getRoleStatusMap().get(rolePriority));
+    RoleStatus dynamicRoleStatus =
+        appState.getRoleStatusMap().get(rolePriority);
+    assertEquals(dynamicRoleStatus.getDesired(), desired);
+
+
+    // before allocating the nodes, fill up the capacity of some of the
+    // hosts
+    engine.getAllocator().nextIndex();
+
+    int targetNode = 2;
+    assertEquals(targetNode, engine.getAllocator().nextIndex());
+    String targetHostname = engine.getCluster().nodeAt(targetNode)
+        .getHostname();
+
+    // clock is set to a small value
+    appState.setTime(100000);
+
+    // allocate the nodes
+    List<AbstractRMOperation> actions = appState.reviewRequestAndReleaseNodes();
+    assertEquals(1, actions.size());
+    ContainerRequestOperation action0 = (ContainerRequestOperation)actions
+        .get(0);
+
+    ContainerRequest request = action0.getRequest();
+    assertTrue(SliderUtils.isEmpty(request.getNodes()));
+
+    List<ContainerId> released = new ArrayList<>();
+    List<RoleInstance> allocations = submitOperations(actions, released);
+    processSubmissionOperations(allocations, new ArrayList<>(), released);
+    assertEquals(1, allocations.size());
+    RoleInstance ri = allocations.get(0);
+
+    assertEquals(ri.role, dynamic);
+    assertEquals(ri.roleId, rolePriority);
+    assertEquals(ri.host, targetHostname);
+
+    // now look at the role history
+
+    RoleHistory roleHistory = appState.getRoleHistory();
+    List<NodeInstance> activeNodes = roleHistory.listActiveNodes(
+        rolePriority);
+    assertEquals(activeNodes.size(), 1);
+    NodeInstance activeNode = activeNodes.get(0);
+    assertNotNull(activeNode.get(rolePriority));
+    NodeEntry entry8 = activeNode.get(rolePriority);
+    assertEquals(entry8.getActive(), 1);
+
+    assertEquals(activeNode.hostname, targetHostname);
+
+    NodeInstance activeNodeInstance =
+        roleHistory.getOrCreateNodeInstance(ri.container);
+
+    assertEquals(activeNode, activeNodeInstance);
+    NodeEntry entry = activeNodeInstance.get(rolePriority);
+    assertNotNull(entry);
+    assertTrue(entry.getActive() > 0);
+    assertTrue(entry.getLive() > 0);
+
+
+    // now trigger a termination event on that role
+
+    // increment time for a long-lived failure event
+    appState.incTime(100000);
+
+    LOG.debug("Triggering failure");
+    ContainerId cid = ri.getContainerId();
+    AppState.NodeCompletionResult result = appState.onCompletedContainer(
+        containerStatus(cid, 1));
+    assertEquals(result.roleInstance, ri);
+    assertTrue(result.containerFailed);
+
+    roleHistory.dump();
+    // values should have changed
+    assertEquals(1, entry.getFailed());
+    assertEquals(0, entry.getStartFailed());
+    assertEquals(0, entry.getActive());
+    assertEquals(0, entry.getLive());
+
+
+    List<NodeInstance> nodesForRoleId =
+        roleHistory.getRecentNodesForRoleId(rolePriority);
+    assertNotNull(nodesForRoleId);
+
+    // make sure new nodes will default to a different host in the engine
+    assertTrue(targetNode < engine.getAllocator().nextIndex());
+
+    actions = appState.reviewRequestAndReleaseNodes();
+    assertEquals(1, actions.size());
+    ContainerRequestOperation action1 = (ContainerRequestOperation) actions
+        .get(0);
+    ContainerRequest request1 = action1.getRequest();
+    assertTrue(SliderUtils.isNotEmpty(request1.getNodes()));
+  }
+
+  @Test(expected = BadConfigException.class)
+  public void testRoleHistoryRoleAdditions() throws Throwable {
+    MockRoleHistory roleHistory = new MockRoleHistory(new ArrayList<>());
+    roleHistory.addNewRole(new RoleStatus(new ProviderRole("one", 1)));
+    roleHistory.addNewRole(new RoleStatus(new ProviderRole("two", 1)));
+    roleHistory.dump();
+  }
+
+  @Test(expected = BadConfigException.class)
+  public void testRoleHistoryRoleStartupConflict() throws Throwable {
+    MockRoleHistory roleHistory = new MockRoleHistory(Arrays.asList(
+        new ProviderRole("one", 1), new ProviderRole("two", 1)
+    ));
+    roleHistory.dump();
+  }
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/1eb168b3/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicRoles.java
----------------------------------------------------------------------
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicRoles.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicRoles.java
new file mode 100644
index 0000000..2c695fd
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateDynamicRoles.java
@@ -0,0 +1,243 @@
+/*
+ * 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 org.apache.slider.api.ResourceKeys;
+import org.apache.slider.api.resource.Application;
+import org.apache.slider.api.resource.Component;
+import org.apache.slider.providers.PlacementPolicy;
+import org.apache.slider.server.appmaster.model.mock.BaseMockAppStateTest;
+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.operations.ContainerRequestOperation;
+import org.apache.slider.server.appmaster.state.AppState.NodeCompletionResult;
+import org.apache.slider.server.appmaster.state.ContainerPriority;
+import org.apache.slider.server.appmaster.state.RoleHistoryUtils;
+import org.apache.slider.server.appmaster.state.RoleInstance;
+import org.apache.slider.server.appmaster.state.RoleStatus;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.apache.slider.server.appmaster.model.mock.MockFactory.NODE_FAILURE_THRESHOLD;
+
+/**
+ * Test that if you have >1 role, the right roles are chosen for release.
+ */
+public class TestMockAppStateDynamicRoles extends BaseMockAppStateTest
+    implements MockRoles {
+  private static final Logger LOG =
+      LoggerFactory.getLogger(TestMockAppStateDynamicRoles.class);
+  private static final String ROLE4 = "4";
+  private static final String ROLE5 = "5";
+
+  @Override
+  public String getTestName() {
+    return "TestMockAppStateDynamicRoles";
+  }
+
+  /**
+   * Small cluster with multiple containers per node,
+   * to guarantee many container allocations on each node.
+   * @return
+   */
+  @Override
+  public MockYarnEngine createYarnEngine() {
+    return new MockYarnEngine(8, 2);
+  }
+
+  @Override
+  public Application buildApplication() {
+    Application application = super.buildApplication();
+
+    Component component = new Component().name(ROLE4).numberOfContainers(1L);
+    component.getConfiguration().setProperty(ResourceKeys
+        .NODE_FAILURE_THRESHOLD, Integer.toString(3));
+    application.getComponents().add(component);
+
+    component = new Component().name(ROLE5).numberOfContainers(1L);
+    component.getConfiguration().setProperty(ResourceKeys
+        .COMPONENT_PLACEMENT_POLICY, Integer.toString(PlacementPolicy.STRICT));
+    application.getComponents().add(component);
+
+    return application;
+  }
+
+  @Test
+  public void testAllocateReleaseRealloc() throws Throwable {
+
+    createAndStartNodes();
+    appState.reviewRequestAndReleaseNodes();
+    appState.getRoleHistory().dump();
+  }
+
+  /**
+   * Find all allocations for a specific role.
+   * @param role role Id/priority
+   * @param actions source list
+   * @return found list
+   */
+  List<ContainerRequestOperation> findAllocationsForRole(int role,
+      List<AbstractRMOperation> actions) {
+    List<ContainerRequestOperation> ops = new ArrayList<>();
+    for (AbstractRMOperation op : actions) {
+      if (op instanceof ContainerRequestOperation && role ==
+          ContainerPriority.extractRole(((ContainerRequestOperation) op)
+              .getRequest().getPriority())) {
+        ops.add((ContainerRequestOperation) op);
+      }
+    }
+    return ops;
+  }
+
+  @Test
+  public void testStrictPlacementInitialRequest() throws Throwable {
+    LOG.info("Initial engine state = {}", engine);
+    List<AbstractRMOperation> actions = appState.reviewRequestAndReleaseNodes();
+    assertEquals(2, actions.size());
+
+    // neither have locality at this point
+    assertRelaxLocalityFlag(appState.lookupRoleStatus(ROLE4).getKey(), null,
+        true, actions);
+    assertRelaxLocalityFlag(appState.lookupRoleStatus(ROLE5).getKey(), null,
+        true, actions);
+  }
+
+  @Test
+  public void testPolicyPropagation() throws Throwable {
+    assertEquals(0, (appState.lookupRoleStatus(ROLE4).getPlacementPolicy() &
+        PlacementPolicy.STRICT));
+    assertNotEquals(0, (appState.lookupRoleStatus(ROLE5).getPlacementPolicy() &
+        PlacementPolicy.STRICT));
+
+  }
+
+  @Test
+  public void testNodeFailureThresholdPropagation() throws Throwable {
+    assertEquals(3, appState.lookupRoleStatus(ROLE4).getNodeFailureThreshold());
+    assertEquals(NODE_FAILURE_THRESHOLD, appState.lookupRoleStatus(ROLE5)
+        .getNodeFailureThreshold());
+  }
+
+  @Test
+  public void testLaxPlacementSecondRequestRole4() throws Throwable {
+    LOG.info("Initial engine state = {}", engine);
+    RoleStatus role4 = appState.lookupRoleStatus(ROLE4);
+    RoleStatus role5 = appState.lookupRoleStatus(ROLE5);
+    role4.setDesired(1);
+    role5.setDesired(0);
+
+    List<RoleInstance> instances = createStartAndStopNodes(new ArrayList<>());
+    assertEquals(1, instances.size());
+
+    int id = appState.lookupRoleStatus(ROLE4).getKey();
+    RoleInstance instanceA = null;
+    for (RoleInstance instance : instances) {
+      if (instance.roleId == id) {
+        instanceA = instance;
+      }
+    }
+    assertNotNull(instanceA);
+    String hostname = RoleHistoryUtils.hostnameOf(instanceA.container);
+
+    LOG.info("Allocated engine state = {}", engine);
+    assertEquals(1, engine.containerCount());
+
+    assertEquals(1, role4.getRunning());
+    // shrinking cluster
+
+    role4.setDesired(0);
+    appState.lookupRoleStatus(ROLE4).setDesired(0);
+    List<NodeCompletionResult> completionResults = new ArrayList<>();
+    createStartAndStopNodes(completionResults);
+    assertEquals(0, engine.containerCount());
+    assertEquals(1, completionResults.size());
+
+    // expanding: expect hostnames  now
+    role4.setDesired(1);
+    List<AbstractRMOperation> actions = appState.reviewRequestAndReleaseNodes();
+    assertEquals(1, actions.size());
+
+    ContainerRequestOperation cro = (ContainerRequestOperation) actions.get(0);
+    List<String> nodes = cro.getRequest().getNodes();
+    assertEquals(1, nodes.size());
+    assertEquals(hostname, nodes.get(0));
+  }
+
+  @Test
+  public void testStrictPlacementSecondRequestRole5() throws Throwable {
+    LOG.info("Initial engine state = {}", engine);
+    RoleStatus role4 = appState.lookupRoleStatus(ROLE4);
+    RoleStatus role5 = appState.lookupRoleStatus(ROLE5);
+    role4.setDesired(0);
+    role5.setDesired(1);
+
+    List<RoleInstance> instances = createStartAndStopNodes(new ArrayList<>());
+    assertEquals(1, instances.size());
+
+    int id = appState.lookupRoleStatus(ROLE5).getKey();
+    RoleInstance instanceA = null;
+    for (RoleInstance instance : instances) {
+      if (instance.roleId == id) {
+        instanceA = instance;
+      }
+    }
+    assertNotNull(instanceA);
+    String hostname = RoleHistoryUtils.hostnameOf(instanceA.container);
+
+    LOG.info("Allocated engine state = {}", engine);
+    assertEquals(1, engine.containerCount());
+
+    assertEquals(1, role5.getRunning());
+
+    // shrinking cluster
+    role5.setDesired(0);
+    List<NodeCompletionResult> completionResults = new ArrayList<>();
+    createStartAndStopNodes(completionResults);
+    assertEquals(0, engine.containerCount());
+    assertEquals(1, completionResults.size());
+    assertEquals(0, role5.getRunning());
+
+    role5.setDesired(1);
+    List<AbstractRMOperation> actions = appState.reviewRequestAndReleaseNodes();
+    assertEquals(1, actions.size());
+    assertRelaxLocalityFlag(id, "", false, actions);
+    ContainerRequestOperation cro = (ContainerRequestOperation) actions.get(0);
+    List<String> nodes = cro.getRequest().getNodes();
+    assertEquals(1, nodes.size());
+    assertEquals(hostname, nodes.get(0));
+  }
+
+  public void assertRelaxLocalityFlag(
+      int role,
+      String expectedHost,
+      boolean expectedRelaxFlag,
+      List<AbstractRMOperation> actions) {
+    List<ContainerRequestOperation> requests = findAllocationsForRole(
+        role, actions);
+    assertEquals(1, requests.size());
+    ContainerRequestOperation req = requests.get(0);
+    assertEquals(expectedRelaxFlag, req.getRequest().getRelaxLocality());
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/1eb168b3/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateFlexDynamicRoles.java
----------------------------------------------------------------------
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateFlexDynamicRoles.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateFlexDynamicRoles.java
new file mode 100644
index 0000000..01bf9bd
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateFlexDynamicRoles.java
@@ -0,0 +1,160 @@
+/*
+ * 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 org.apache.hadoop.fs.Path;
+import org.apache.slider.api.resource.Application;
+import org.apache.slider.api.resource.Component;
+import org.apache.slider.core.exceptions.SliderInternalStateException;
+import org.apache.slider.core.exceptions.TriggerClusterTeardownException;
+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.state.AppStateBindingInfo;
+import org.apache.slider.server.appmaster.state.MostRecentContainerReleaseSelector;
+import org.apache.slider.server.appmaster.state.RoleHistory;
+import org.apache.slider.server.avro.LoadedRoleHistory;
+import org.apache.slider.server.avro.RoleHistoryWriter;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.Collections;
+
+/**
+ * Test that if you have more than one role, the right roles are chosen for
+ * release.
+ */
+public class TestMockAppStateFlexDynamicRoles extends BaseMockAppStateTest
+    implements MockRoles {
+  private static final Logger LOG =
+      LoggerFactory.getLogger(TestMockAppStateFlexDynamicRoles.class);
+
+  @Override
+  public String getTestName() {
+    return "TestMockAppStateFlexDynamicRoles";
+  }
+
+  /**
+   * Small cluster with multiple containers per node,
+   * to guarantee many container allocations on each node.
+   * @return
+   */
+  @Override
+  public MockYarnEngine createYarnEngine() {
+    return new MockYarnEngine(4, 4);
+  }
+
+  @Override
+  public AppStateBindingInfo buildBindingInfo() {
+    AppStateBindingInfo bindingInfo = super.buildBindingInfo();
+    bindingInfo.releaseSelector = new MostRecentContainerReleaseSelector();
+    return bindingInfo;
+  }
+
+  @Override
+  public Application buildApplication() {
+    Application application = super.buildApplication();
+    Component component = new Component().name("dynamic-6")
+        .numberOfContainers(1L);
+    application.getComponents().add(component);
+
+    return application;
+  }
+
+  @Before
+  public void init()
+      throws TriggerClusterTeardownException, SliderInternalStateException {
+    createAndStartNodes();
+  }
+
+  @Test
+  public void testDynamicFlexAddRole() throws Throwable {
+    Application application = appState.getClusterStatus();
+    Component component = new Component().name("dynamicAdd7")
+        .numberOfContainers(1L);
+    application.getComponents().add(component);
+    appState.updateComponents(Collections.singletonMap(component.getName(),
+        component.getNumberOfContainers()));
+    createAndStartNodes();
+    dumpClusterDescription("updated CD", appState.getClusterStatus());
+    appState.lookupRoleStatus("dynamicAdd7");
+  }
+
+  @Test
+  public void testDynamicFlexDropRole() throws Throwable {
+    appState.updateComponents(Collections.singletonMap("dynamic-6", 0L));
+
+    Application getCD = appState.getClusterStatus();
+    dumpClusterDescription("updated CD", getCD);
+    //status is retained for future
+    appState.lookupRoleStatus("dynamic-6");
+  }
+
+
+  @Test
+  public void testHistorySaveFlexLoad() throws Throwable {
+    Application application = appState.getClusterStatus();
+    RoleHistory roleHistory = appState.getRoleHistory();
+    Path history = roleHistory.saveHistory(0x0001);
+    RoleHistoryWriter historyWriter = new RoleHistoryWriter();
+    Component component = new Component().name("HistorySaveFlexLoad")
+        .numberOfContainers(1L);
+    application.getComponents().add(component);
+
+    appState.updateComponents(Collections.singletonMap(component.getName(),
+        component.getNumberOfContainers()));
+    createAndStartNodes();
+    LoadedRoleHistory loadedRoleHistory =
+        historyWriter.read(fs, history);
+    assertEquals(0, appState.getRoleHistory().rebuild(loadedRoleHistory));
+  }
+
+  @Test
+  public void testHistoryFlexSaveResetLoad() throws Throwable {
+    Application application = appState.getClusterStatus();
+    Component component = new Component().name("HistoryFlexSaveLoad")
+        .numberOfContainers(1L);
+    application.getComponents().add(component);
+
+    appState.updateComponents(Collections.singletonMap(component.getName(),
+        component.getNumberOfContainers()));
+    createAndStartNodes();
+    RoleHistoryWriter historyWriter = new RoleHistoryWriter();
+    RoleHistory roleHistory = appState.getRoleHistory();
+    Path history = roleHistory.saveHistory(0x0002);
+    //now reset the app state
+    File historyWorkDir2 = new File("target/history" + getTestName() +
+        "-0002");
+    Path historyPath2 = new Path(historyWorkDir2.toURI());
+    appState = new MockAppState();
+    AppStateBindingInfo binding2 = buildBindingInfo();
+    binding2.application = factory.newApplication(0, 0, 0)
+        .name(getTestName());
+    binding2.historyPath = historyPath2;
+    appState.buildInstance(binding2);
+    // on this read there won't be the right number of roles
+    LoadedRoleHistory loadedRoleHistory = historyWriter.read(fs, history);
+    assertEquals(0, appState.getRoleHistory().rebuild(loadedRoleHistory));
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/1eb168b3/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateFlexing.java
----------------------------------------------------------------------
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateFlexing.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateFlexing.java
new file mode 100644
index 0000000..9b5e532
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateFlexing.java
@@ -0,0 +1,201 @@
+/*
+ * 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 org.apache.hadoop.yarn.api.records.Container;
+import org.apache.slider.api.resource.Application;
+import org.apache.slider.api.types.ApplicationLivenessInformation;
+import org.apache.slider.core.exceptions.TriggerClusterTeardownException;
+import org.apache.slider.server.appmaster.model.mock.BaseMockAppStateTest;
+import org.apache.slider.server.appmaster.model.mock.MockRoles;
+import org.apache.slider.server.appmaster.operations.AbstractRMOperation;
+import org.apache.slider.server.appmaster.operations.CancelSingleRequest;
+import org.apache.slider.server.appmaster.state.AppState;
+import org.apache.slider.server.appmaster.state.ContainerAssignment;
+import org.apache.slider.server.appmaster.state.RoleInstance;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test app state flexing.
+ */
+public class TestMockAppStateFlexing extends BaseMockAppStateTest implements
+    MockRoles {
+  private static final Logger LOG =
+      LoggerFactory.getLogger(BaseMockAppStateTest.class);
+
+  @Override
+  public String getTestName() {
+    return "TestMockAppStateFlexing";
+  }
+
+  @Test
+  public void testFlexDuringLaunchPhase() throws Throwable {
+
+    // ask for one instance of role0
+    getRole0Status().setDesired(1);
+
+    List<AbstractRMOperation> ops = appState.reviewRequestAndReleaseNodes();
+
+    // at this point there's now one request in the list
+    assertEquals(1, ops.size());
+    // and in a liveness check, one outstanding
+    ApplicationLivenessInformation liveness =
+        appState.getApplicationLivenessInformation();
+    assertEquals(1, liveness.requestsOutstanding);
+    assertFalse(liveness.allRequestsSatisfied);
+
+    List<Container> allocations = engine.execute(ops);
+    List<ContainerAssignment> assignments = new ArrayList<>();
+    List<AbstractRMOperation> releases = new ArrayList<>();
+    appState.onContainersAllocated(allocations, assignments, releases);
+    assertEquals(1, assignments.size());
+    ContainerAssignment assigned = assignments.get(0);
+    Container target = assigned.container;
+    RoleInstance ri = roleInstance(assigned);
+
+    ops = appState.reviewRequestAndReleaseNodes();
+    assertTrue(ops.isEmpty());
+
+    liveness = appState.getApplicationLivenessInformation();
+    assertEquals(0, liveness.requestsOutstanding);
+    assertTrue(liveness.allRequestsSatisfied);
+
+    //now this is the start point.
+    appState.containerStartSubmitted(target, ri);
+
+    ops = appState.reviewRequestAndReleaseNodes();
+    assertTrue(ops.isEmpty());
+
+    appState.innerOnNodeManagerContainerStarted(target.getId());
+  }
+
+  @Test
+  public void testFlexBeforeAllocationPhase() throws Throwable {
+    getRole0Status().setDesired(1);
+
+    List<AbstractRMOperation> ops = appState.reviewRequestAndReleaseNodes();
+    assertFalse(ops.isEmpty());
+
+    // second scan will find the first run outstanding, so not re-issue
+    // any more container requests
+    List<AbstractRMOperation> ops2 = appState.reviewRequestAndReleaseNodes();
+    assertTrue(ops2.isEmpty());
+
+    // and in a liveness check, one outstanding
+    ApplicationLivenessInformation liveness = appState
+        .getApplicationLivenessInformation();
+    assertEquals(1, liveness.requestsOutstanding);
+    assertFalse(liveness.allRequestsSatisfied);
+
+    appState.refreshClusterStatus();
+    Application application = appState.getClusterStatus();
+    // TODO cluster status returns liveness info
+//    assertEquals(1, cd.liveness.requestsOutstanding);
+
+  }
+
+
+  @Test
+  public void testFlexDownTwice() throws Throwable {
+    int r0 = 6;
+    int r1 = 0;
+    int r2 = 0;
+    getRole0Status().setDesired(r0);
+    getRole1Status().setDesired(r1);
+    getRole2Status().setDesired(r2);
+    List<RoleInstance> instances = createAndStartNodes();
+
+    int clusterSize = r0 + r1 + r2;
+    assertEquals(instances.size(), clusterSize);
+    LOG.info("shrinking cluster");
+    r0 = 4;
+    getRole0Status().setDesired(r0);
+    List<AppState.NodeCompletionResult> completionResults = new ArrayList<>();
+    instances = createStartAndStopNodes(completionResults);
+    assertEquals(0, instances.size());
+    // assert two nodes were released
+    assertEquals(2, completionResults.size());
+
+    // no-op review
+    completionResults = new ArrayList<>();
+    instances = createStartAndStopNodes(completionResults);
+    assertEquals(0, instances.size());
+    // assert two nodes were released
+    assertEquals(0, completionResults.size());
+
+
+    // now shrink again
+    getRole0Status().setDesired(1);
+    completionResults = new ArrayList<>();
+    instances = createStartAndStopNodes(completionResults);
+    assertEquals(0, instances.size());
+    // assert two nodes were released
+    assertEquals(3, completionResults.size());
+
+  }
+
+  @Test
+  public void testFlexNegative() throws Throwable {
+    int r0 = 6;
+    int r1 = 0;
+    int r2 = 0;
+    getRole0Status().setDesired(r0);
+    getRole1Status().setDesired(r1);
+    getRole2Status().setDesired(r2);
+    List<RoleInstance> instances = createAndStartNodes();
+
+    int clusterSize = r0 + r1 + r2;
+    assertEquals(instances.size(), clusterSize);
+    LOG.info("shrinking cluster");
+    getRole0Status().setDesired(-2);
+    List<AppState.NodeCompletionResult> completionResults = new ArrayList<>();
+    try {
+      createStartAndStopNodes(completionResults);
+      fail("expected an exception");
+    } catch (TriggerClusterTeardownException e) {
+    }
+
+  }
+
+  @Test
+  public void testCancelWithRequestsOutstanding() throws Throwable {
+    // flex cluster size before the original set were allocated
+
+
+    getRole0Status().setDesired(6);
+    // build the ops
+    List<AbstractRMOperation> ops = appState.reviewRequestAndReleaseNodes();
+    // here the data structures exist
+
+    // go down
+    getRole0Status().setDesired(3);
+    List<AbstractRMOperation> ops2 = appState.reviewRequestAndReleaseNodes();
+    assertEquals(3, ops2.size());
+    for (AbstractRMOperation op : ops2) {
+      assertTrue(op instanceof CancelSingleRequest);
+    }
+
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/hadoop/blob/1eb168b3/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateRMOperations.java
----------------------------------------------------------------------
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateRMOperations.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateRMOperations.java
new file mode 100644
index 0000000..2d87be6
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-slider/hadoop-yarn-slider-core/src/test/java/org/apache/slider/server/appmaster/model/appstate/TestMockAppStateRMOperations.java
@@ -0,0 +1,382 @@
+/*
+ * 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 org.apache.hadoop.yarn.api.records.Container;
+import org.apache.hadoop.yarn.api.records.ContainerId;
+import org.apache.hadoop.yarn.client.api.AMRMClient;
+import org.apache.slider.server.appmaster.model.mock.BaseMockAppStateTest;
+import org.apache.slider.server.appmaster.model.mock.MockRMOperationHandler;
+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.operations.CancelSingleRequest;
+import org.apache.slider.server.appmaster.operations.ContainerReleaseOperation;
+import org.apache.slider.server.appmaster.operations.ContainerRequestOperation;
+import org.apache.slider.server.appmaster.state.AppState;
+import org.apache.slider.server.appmaster.state.ContainerAssignment;
+import org.apache.slider.server.appmaster.state.RoleInstance;
+import org.apache.slider.server.appmaster.state.RoleStatus;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.apache.slider.server.appmaster.state.ContainerPriority.buildPriority;
+import static org.apache.slider.server.appmaster.state.ContainerPriority.extractRole;
+
+/**
+ * Test app state RM operations.
+ */
+public class TestMockAppStateRMOperations extends BaseMockAppStateTest
+    implements MockRoles {
+  private static final Logger LOG =
+      LoggerFactory.getLogger(BaseMockAppStateTest.class);
+
+  @Override
+  public String getTestName() {
+    return "TestMockAppStateRMOperations";
+  }
+
+  @Test
+  public void testPriorityOnly() throws Throwable {
+    assertEquals(5, extractRole(buildPriority(5, false)));
+  }
+
+  @Test
+  public void testPriorityRoundTrip() throws Throwable {
+    assertEquals(5, extractRole(buildPriority(5, false)));
+  }
+
+  @Test
+  public void testPriorityRoundTripWithRequest() throws Throwable {
+    int priority = buildPriority(5, false);
+    assertEquals(5, extractRole(priority));
+  }
+
+  @Test
+  public void testMockAddOp() throws Throwable {
+    getRole0Status().setDesired(1);
+    List<AbstractRMOperation> ops = appState.reviewRequestAndReleaseNodes();
+    assertListLength(ops, 1);
+    ContainerRequestOperation operation = (ContainerRequestOperation)ops.get(0);
+    int priority = operation.getRequest().getPriority().getPriority();
+    assertEquals(extractRole(priority), getRole0Status().getKey());
+    MockRMOperationHandler handler = new MockRMOperationHandler();
+    handler.execute(ops);
+
+    AbstractRMOperation op = handler.getFirstOp();
+    assertTrue(op instanceof ContainerRequestOperation);
+  }
+
+  /**
+   * Test of a flex up and down op which verifies that outstanding
+   * requests are cancelled first.
+   * <ol>
+   *   <li>request 5 nodes, assert 5 request made</li>
+   *   <li>allocate 1 of them</li>
+   *   <li>flex cluster size to 3</li>
+   *   <li>assert this generates 2 cancel requests</li>
+   * </ol>
+   */
+  @Test
+  public void testRequestThenCancelOps() throws Throwable {
+    RoleStatus role0 = getRole0Status();
+    role0.setDesired(5);
+    List<AbstractRMOperation> ops = appState.reviewRequestAndReleaseNodes();
+    assertListLength(ops, 5);
+    // now 5 outstanding requests.
+    assertEquals(5, role0.getRequested());
+
+    // allocate one
+    List<AbstractRMOperation> processed = new ArrayList<>();
+    processed.add(ops.get(0));
+    List<ContainerId> released = new ArrayList<>();
+    List<AppState.NodeCompletionResult> completionResults = new ArrayList<>();
+    submitOperations(processed, released);
+    List<RoleInstance> instances = createAndSubmitNodes(released);
+    processSubmissionOperations(instances, completionResults, released);
+
+
+    // four outstanding
+    assertEquals(4, role0.getRequested());
+
+    // flex cluster to 3
+    role0.setDesired(3);
+    ops = appState.reviewRequestAndReleaseNodes();
+
+    // expect two cancel operation from review
+    assertListLength(ops, 2);
+    for (AbstractRMOperation op : ops) {
+      assertTrue(op instanceof CancelSingleRequest);
+    }
+
+    MockRMOperationHandler handler = new MockRMOperationHandler();
+    handler.setAvailableToCancel(4);
+    handler.execute(ops);
+    assertEquals(2, handler.getAvailableToCancel());
+    assertEquals(2, role0.getRequested());
+
+    // flex down one more
+    role0.setDesired(2);
+    ops = appState.reviewRequestAndReleaseNodes();
+    assertListLength(ops, 1);
+    for (AbstractRMOperation op : ops) {
+      assertTrue(op instanceof CancelSingleRequest);
+    }
+    handler.execute(ops);
+    assertEquals(1, handler.getAvailableToCancel());
+    assertEquals(1, role0.getRequested());
+  }
+
+  @Test
+  public void testCancelNoActualContainers() throws Throwable {
+    RoleStatus role0 = getRole0Status();
+    role0.setDesired(5);
+    List<AbstractRMOperation> ops = appState.reviewRequestAndReleaseNodes();
+    assertListLength(ops, 5);
+    // now 5 outstanding requests.
+    assertEquals(5, role0.getRequested());
+    role0.setDesired(0);
+    ops = appState.reviewRequestAndReleaseNodes();
+    assertListLength(ops, 5);
+
+  }
+
+
+  @Test
+  public void testFlexDownOutstandingRequests() throws Throwable {
+    // engine only has two nodes, so > 2 will be outstanding
+    engine = new MockYarnEngine(1, 2);
+    List<AbstractRMOperation> ops;
+    // role: desired = 2, requested = 1, actual=1
+    RoleStatus role0 = getRole0Status();
+    role0.setDesired(4);
+    createAndSubmitNodes();
+
+    assertEquals(2, role0.getRequested());
+    assertEquals(2, role0.getRunning());
+    // there are now two outstanding, two actual
+    // Release 3 and verify that the two
+    // cancellations were combined with a release
+    role0.setDesired(1);
+    assertEquals(-3, role0.getDelta());
+    ops = appState.reviewRequestAndReleaseNodes();
+    assertListLength(ops, 3);
+    int numCancel = 0;
+    int numRelease = 0;
+    for (AbstractRMOperation op : ops) {
+      if (op instanceof CancelSingleRequest) {
+        numCancel++;
+      }
+      if (op instanceof ContainerReleaseOperation) {
+        numRelease++;
+      }
+    }
+    assertEquals(2, numCancel);
+    assertEquals(1, numRelease);
+    assertEquals(0, role0.getRequested());
+    // TODO releasing?
+//    assertEquals(1, role0.getReleasing());
+  }
+
+  @Test
+  public void testCancelAllOutstandingRequests() throws Throwable {
+
+    // role: desired = 2, requested = 1, actual=1
+    RoleStatus role0 = getRole0Status();
+    role0.setDesired(2);
+    List<AbstractRMOperation> ops;
+    ops = appState.reviewRequestAndReleaseNodes();
+    int count = 0;
+    for (AbstractRMOperation op : ops) {
+      if (op instanceof ContainerRequestOperation) {
+        count++;
+      }
+    }
+    assertEquals(2, count);
+
+    // there are now two outstanding, two actual
+    // Release 3 and verify that the two
+    // cancellations were combined with a release
+    role0.setDesired(0);
+    ops = appState.reviewRequestAndReleaseNodes();
+    assertEquals(2, ops.size());
+
+    for (AbstractRMOperation op : ops) {
+      assertTrue(op instanceof CancelSingleRequest);
+    }
+  }
+
+
+  @Test
+  public void testFlexUpOutstandingRequests() throws Throwable {
+
+    List<AbstractRMOperation> ops;
+    // role: desired = 2, requested = 1, actual=1
+    RoleStatus role0 = getRole0Status();
+    role0.setDesired(2);
+    appState.incRunningContainers(role0);
+    appState.incRequestedContainers(role0);
+
+    // flex up 2 nodes, yet expect only one node to be requested,
+    // as the  outstanding request is taken into account
+    role0.setDesired(4);
+    appState.incRequestedContainers(role0);
+
+    assertEquals(1, role0.getRunning());
+    assertEquals(2, role0.getRequested());
+    assertEquals(3, role0.getActualAndRequested());
+    assertEquals(1, role0.getDelta());
+    ops = appState.reviewRequestAndReleaseNodes();
+    assertListLength(ops, 1);
+    assertTrue(ops.get(0) instanceof ContainerRequestOperation);
+    assertEquals(3, role0.getRequested());
+  }
+
+  @Test
+  public void testFlexUpNoSpace() throws Throwable {
+    // engine only has two nodes, so > 2 will be outstanding
+    engine = new MockYarnEngine(1, 2);
+    // role: desired = 2, requested = 1, actual=1
+    RoleStatus role0 = getRole0Status();
+    role0.setDesired(4);
+    createAndSubmitNodes();
+
+    assertEquals(2, role0.getRequested());
+    assertEquals(2, role0.getRunning());
+    role0.setDesired(8);
+    assertEquals(4, role0.getDelta());
+    createAndSubmitNodes();
+    assertEquals(6, role0.getRequested());
+  }
+
+
+  @Test
+  public void testAllocateReleaseOp() throws Throwable {
+    getRole0Status().setDesired(1);
+
+    List<AbstractRMOperation> ops = appState.reviewRequestAndReleaseNodes();
+    ContainerRequestOperation operation = (ContainerRequestOperation)ops.get(0);
+    AMRMClient.ContainerRequest request = operation.getRequest();
+    Container cont = engine.allocateContainer(request);
+    List<Container> allocated = new ArrayList<>();
+    allocated.add(cont);
+    List<ContainerAssignment> assignments = new ArrayList<>();
+    List<AbstractRMOperation> operations = new ArrayList<>();
+    appState.onContainersAllocated(allocated, assignments, operations);
+
+    assertListLength(ops, 1);
+    assertListLength(assignments, 1);
+    ContainerAssignment assigned = assignments.get(0);
+    Container target = assigned.container;
+    assertEquals(target.getId(), cont.getId());
+    int roleId = assigned.role.getPriority();
+    assertEquals(roleId, extractRole(request.getPriority()));
+    assertEquals(assigned.role.getName(), ROLE0);
+    RoleInstance ri = roleInstance(assigned);
+    //tell the app it arrived
+    appState.containerStartSubmitted(target, ri);
+    appState.innerOnNodeManagerContainerStarted(target.getId());
+    assertEquals(1, getRole0Status().getRunning());
+
+    //now release it by changing the role status
+    getRole0Status().setDesired(0);
+    ops = appState.reviewRequestAndReleaseNodes();
+    assertListLength(ops, 1);
+
+    assertTrue(ops.get(0) instanceof ContainerReleaseOperation);
+    ContainerReleaseOperation release = (ContainerReleaseOperation) ops.get(0);
+    assertEquals(release.getContainerId(), cont.getId());
+  }
+
+  @Test
+  public void testComplexAllocation() throws Throwable {
+    getRole0Status().setDesired(1);
+    getRole1Status().setDesired(3);
+
+    List<AbstractRMOperation> ops = appState.reviewRequestAndReleaseNodes();
+    List<Container> allocations = engine.execute(ops);
+    List<ContainerAssignment> assignments = new ArrayList<>();
+    List<AbstractRMOperation> releases = new ArrayList<>();
+    appState.onContainersAllocated(allocations, assignments, releases);
+    // we expect four release requests here for all the allocated containers
+    assertListLength(releases, 4);
+    for (AbstractRMOperation op : releases) {
+      assertTrue(op instanceof CancelSingleRequest);
+    }
+    assertListLength(assignments, 4);
+    for (ContainerAssignment assigned : assignments) {
+      Container target = assigned.container;
+      RoleInstance ri = roleInstance(assigned);
+      appState.containerStartSubmitted(target, ri);
+    }
+    //insert some async operation here
+    for (ContainerAssignment assigned : assignments) {
+      Container target = assigned.container;
+      appState.innerOnNodeManagerContainerStarted(target.getId());
+    }
+    assertEquals(4, engine.containerCount());
+    getRole1Status().setDesired(0);
+    ops = appState.reviewRequestAndReleaseNodes();
+    assertListLength(ops, 3);
+    allocations = engine.execute(ops);
+    assertEquals(1, engine.containerCount());
+
+    appState.onContainersAllocated(allocations, assignments, releases);
+    assertTrue(assignments.isEmpty());
+    assertTrue(releases.isEmpty());
+  }
+
+  @Test
+  public void testDoubleNodeManagerStartEvent() throws Throwable {
+    getRole0Status().setDesired(1);
+
+    List<AbstractRMOperation> ops = appState.reviewRequestAndReleaseNodes();
+    List<Container> allocations = engine.execute(ops);
+    List<ContainerAssignment> assignments = new ArrayList<>();
+    List<AbstractRMOperation> releases = new ArrayList<>();
+    appState.onContainersAllocated(allocations, assignments, releases);
+    assertListLength(assignments, 1);
+    ContainerAssignment assigned = assignments.get(0);
+    Container target = assigned.container;
+    RoleInstance ri = roleInstance(assigned);
+    appState.containerStartSubmitted(target, ri);
+    RoleInstance ri2 = appState.innerOnNodeManagerContainerStarted(target
+        .getId());
+    assertEquals(ri2, ri);
+    //try a second time, expect an error
+    try {
+      appState.innerOnNodeManagerContainerStarted(target.getId());
+      fail("Expected an exception");
+    } catch (RuntimeException expected) {
+      // expected
+    }
+    //and non-faulter should not downgrade to a null
+    LOG.warn("Ignore any exception/stack trace that appears below");
+    LOG.warn("===============================================================");
+    RoleInstance ri3 = appState.onNodeManagerContainerStarted(target.getId());
+    LOG.warn("===============================================================");
+    LOG.warn("Ignore any exception/stack trace that appeared above");
+    assertNull(ri3);
+  }
+
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: common-commits-unsubscribe@hadoop.apache.org
For additional commands, e-mail: common-commits-help@hadoop.apache.org