New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[YARN-11421] Graceful Decommission ignores launched containers and gets deactivated before timeout #5905
base: trunk
Are you sure you want to change the base?
[YARN-11421] Graceful Decommission ignores launched containers and gets deactivated before timeout #5905
Changes from all commits
64ec927
6312eb5
4adedca
5db1905
1d9c17c
e933d60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1439,10 +1439,15 @@ public NodeState transition(RMNodeImpl rmNode, RMNodeEvent event) { | |
* @return true if node has any AM scheduled on it. | ||
*/ | ||
private boolean hasScheduledAMContainers(RMNodeImpl rmNode) { | ||
return rmNode.context.getScheduler() | ||
boolean hasScheduledAMContainers = rmNode.context.getScheduler() | ||
.getSchedulerNode(rmNode.getNodeID()) | ||
.getCopiedListOfRunningContainers() | ||
.stream().anyMatch(RMContainer::isAMContainer); | ||
if (hasScheduledAMContainers) { | ||
LOG.info("Node " + rmNode.nodeId + " has AM containers scheduled on it." | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use {} logger format. |
||
+ " Will not deactivate it."); | ||
} | ||
return hasScheduledAMContainers; | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -162,6 +162,80 @@ public void testDecommissioningNodesWatcherWithPreviousRunningApps() | |
watcher.checkDecommissioningStatus(id1)); | ||
} | ||
|
||
@Test | ||
public void testDecommissioningNodesWatcherWithScheduledAMContainers() | ||
throws Exception { | ||
Configuration conf = new Configuration(); | ||
// decommission timeout is 10 min | ||
conf.set(YarnConfiguration.RM_NODE_GRACEFUL_DECOMMISSION_TIMEOUT, "600"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. setInt or ideally setTimeDuration? |
||
// watcher delay is 5min | ||
conf.set(YarnConfiguration.RM_DECOMMISSIONING_NODES_WATCHER_DELAY_MS, "300000"); | ||
|
||
rm = new MockRM(conf); | ||
rm.start(); | ||
|
||
DecommissioningNodesWatcher watcher = | ||
new DecommissioningNodesWatcher(rm.getRMContext()); | ||
watcher.init(conf); | ||
|
||
MockNM nm1 = rm.registerNode("host1:1234", 10240); | ||
RMNodeImpl node1 = | ||
(RMNodeImpl) rm.getRMContext().getRMNodes().get(nm1.getNodeId()); | ||
NodeId id1 = nm1.getNodeId(); | ||
|
||
rm.waitForState(id1, NodeState.RUNNING); | ||
|
||
// just submit the app | ||
RMApp app = MockRMAppSubmitter.submit(rm, | ||
MockRMAppSubmissionData.Builder.createWithMemory(2000, rm).build()); | ||
MockAM am = MockRM.launchAndRegisterAM(app, rm, nm1); | ||
|
||
// rmnode still thinks there are 0 apps because it hasn't received updated node status | ||
Assert.assertEquals(0, node1.getRunningApps().size()); | ||
|
||
// Setup nm1 as DECOMMISSIONING for DecommissioningNodesWatcher. Right now | ||
// there is no container running on the node. | ||
rm.sendNodeGracefulDecommission(nm1, | ||
YarnConfiguration.DEFAULT_RM_NODE_GRACEFUL_DECOMMISSION_TIMEOUT); | ||
rm.waitForState(id1, NodeState.DECOMMISSIONING); | ||
|
||
// we should still get WAIT_SCHEDULED_APPS as expiry time is not over | ||
NodeHealthStatus status = NodeHealthStatus.newInstance(true, "", | ||
System.currentTimeMillis() - 1000); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indentation looks funny. |
||
NodeStatus nodeStatus = NodeStatus.newInstance(id1, 0, | ||
new ArrayList<>(), Collections.emptyList(), status, null, null, null); | ||
watcher.update(node1, nodeStatus); | ||
|
||
Assert.assertFalse(watcher.checkReadyToBeDecommissioned(id1)); | ||
Assert.assertEquals(DecommissioningNodeStatus.WAIT_SCHEDULED_APPS, | ||
watcher.checkDecommissioningStatus(id1)); | ||
|
||
// update node with 3 running containers | ||
nodeStatus = createNodeStatus(id1, app, 3); | ||
node1.handle(new RMNodeStatusEvent(nm1.getNodeId(), nodeStatus)); | ||
watcher.update(node1, nodeStatus); | ||
Assert.assertEquals(1, node1.getRunningApps().size()); | ||
Assert.assertFalse(watcher.checkReadyToBeDecommissioned(id1)); | ||
Assert.assertEquals(DecommissioningNodeStatus.WAIT_CONTAINER, | ||
watcher.checkDecommissioningStatus(id1)); | ||
|
||
// update node with 0 running containers | ||
nodeStatus = createNodeStatus(id1, app, 0); | ||
node1.handle(new RMNodeStatusEvent(nm1.getNodeId(), nodeStatus)); | ||
watcher.update(node1, nodeStatus); | ||
Assert.assertFalse(watcher.checkReadyToBeDecommissioned(id1)); | ||
// we still get status as WAIT_APP since app is still running even if | ||
// containers are 0 | ||
Assert.assertEquals(DecommissioningNodeStatus.WAIT_APP, | ||
watcher.checkDecommissioningStatus(id1)); | ||
|
||
// Set app to be FINISHED and verified DecommissioningNodeStatus is READY. | ||
MockRM.finishAMAndVerifyAppState(app, rm, nm1, am); | ||
rm.waitForState(app.getApplicationId(), RMAppState.FINISHED); | ||
Assert.assertEquals(0, node1.getRunningApps().size()); | ||
watcher.update(node1, nodeStatus); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do we update this for if we don't assert later? |
||
} | ||
|
||
@After | ||
public void tearDown() { | ||
if (rm != null) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -351,6 +351,65 @@ public void testGracefulDecommissionNoApp() throws Exception { | |
Assert.assertEquals(NodeAction.SHUTDOWN, nodeHeartbeat3.getNodeAction()); | ||
} | ||
|
||
@Test (timeout = 60000) | ||
public void testGracefulDecommissionRaceCondition() throws Exception { | ||
Configuration conf = new Configuration(); | ||
conf.set(YarnConfiguration.RM_NODES_EXCLUDE_FILE_PATH, hostFile | ||
.getAbsolutePath()); | ||
conf.set(YarnConfiguration.RM_DECOMMISSIONING_NODES_WATCHER_DELAY_MS, "120000"); | ||
|
||
writeToHostsFile(""); | ||
rm = new MockRM(conf); | ||
rm.start(); | ||
|
||
MockNM nm1 = rm.registerNode("host1:1234", 10240); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make it 10*1024. |
||
MockNM nm2 = rm.registerNode("host2:5678", 20480); | ||
MockNM nm3 = rm.registerNode("host3:4433", 10240); | ||
|
||
NodeId id1 = nm1.getNodeId(); | ||
NodeId id2 = nm2.getNodeId(); | ||
NodeId id3 = nm3.getNodeId(); | ||
|
||
rm.waitForState(id1, NodeState.RUNNING); | ||
rm.waitForState(id2, NodeState.RUNNING); | ||
rm.waitForState(id3, NodeState.RUNNING); | ||
|
||
// Create an app and schedule AM on host1. | ||
RMApp app = MockRMAppSubmitter.submitWithMemory(2000, rm); | ||
MockAM am = MockRM.launchAM(app, rm, nm1); | ||
|
||
// Before sending heartbeat we gracefully decommission the node on which AM | ||
// is scheduled to simulate race condition. | ||
writeToHostsFile("host1", "host3"); | ||
rm.getNodesListManager().refreshNodesGracefully(conf, 30); | ||
rm.waitForState(id1, NodeState.DECOMMISSIONING); | ||
rm.waitForState(id3, NodeState.DECOMMISSIONING); | ||
|
||
nm1.nodeHeartbeat(true); | ||
nm3.nodeHeartbeat(true); | ||
// host1 should stay in DECOMMISSIONING as we are waiting for am launch timeout | ||
// and the there are scheduled am containers on host1. | ||
// host3 should be decommissioned as there is no container scheduled on it. | ||
rm.waitForState(id1, NodeState.DECOMMISSIONING); | ||
rm.waitForState(id3, NodeState.DECOMMISSIONED); | ||
|
||
ApplicationAttemptId aaid = app.getCurrentAppAttempt().getAppAttemptId(); | ||
nm1.nodeHeartbeat(aaid, 1, ContainerState.RUNNING); | ||
nm3.nodeHeartbeat(true); | ||
|
||
// host1 is in Decommissioning due to containers on the node, host3 should be in DECOMMISSIONED | ||
// because no containers on the node | ||
rm.waitForState(id1, NodeState.DECOMMISSIONING); | ||
rm.waitForState(id3, NodeState.DECOMMISSIONED); | ||
|
||
am.registerAppAttempt(); | ||
rm.waitForState(app.getApplicationId(), RMAppState.RUNNING); | ||
MockRM.finishAMAndVerifyAppState(app, rm, nm1, am); | ||
nm1.nodeHeartbeat(aaid, 1, ContainerState.COMPLETE); | ||
rm.waitForState(app.getApplicationId(), RMAppState.FINISHED); | ||
rm.waitForState(id1, NodeState.DECOMMISSIONED); | ||
} | ||
|
||
@Test | ||
public void testGracefulDecommissionDefaultTimeoutResolution() | ||
throws Exception { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we use getTimeDuration()?