@@ -137,17 +137,19 @@ public void serDeTest() throws IOException {
137137 }
138138
139139 @ Test
140- public void metadataRetryExhaustionCleansUpNodeRemoval () throws Exception {
141- assertFinalizationFailureCleanup (RemoveRegionPeerState .REMOVE_REGION_LOCATION_CACHE , false );
140+ public void metadataRetryExhaustionRetriesFailedNodeStatusCleanup () throws Exception {
141+ assertFinalizationExhaustionCleanup (RemoveRegionPeerState .REMOVE_REGION_LOCATION_CACHE , true );
142142 }
143143
144144 @ Test
145- public void peerListRetryExhaustionRetriesFailedNodeStatusCleanup () throws Exception {
146- assertFinalizationFailureCleanup (RemoveRegionPeerState .RESET_IOT_CONSENSUS_V1_PEER_LIST , true );
145+ public void peerListRetryExhaustionCompletesNodeRemoval () throws Exception {
146+ assertFinalizationExhaustionCleanup (
147+ RemoveRegionPeerState .RESET_IOT_CONSENSUS_V1_PEER_LIST , false );
147148 }
148149
149- private void assertFinalizationFailureCleanup (
150+ private void assertFinalizationExhaustionCleanup (
150151 RemoveRegionPeerState failingState , boolean failFirstStatusRestoration ) throws Exception {
152+ boolean metadataFailure = failingState == RemoveRegionPeerState .REMOVE_REGION_LOCATION_CACHE ;
151153 TDataNodeLocation runningNode = fromDataNodeLocation ;
152154 TDataNodeLocation readOnlyNode = location (12 );
153155 TDataNodeLocation drainedNode = location (13 );
@@ -178,10 +180,12 @@ private void assertFinalizationFailureCleanup(
178180 "root.remove" ,
179181 new TRegionReplicaSet (
180182 consensusGroupId , new ArrayList <>(Arrays .asList (runningNode , retainedNode ))));
181- groups .addRegionGroup (
182- "root.remove" ,
183- new TRegionReplicaSet (
184- pendingRunningRegion , new ArrayList <>(Arrays .asList (runningNode , retainedNode ))));
183+ if (metadataFailure ) {
184+ groups .addRegionGroup (
185+ "root.remove" ,
186+ new TRegionReplicaSet (
187+ pendingRunningRegion , new ArrayList <>(Arrays .asList (runningNode , retainedNode ))));
188+ }
185189 groups .addRegionGroup (
186190 "root.remove" ,
187191 new TRegionReplicaSet (
@@ -229,7 +233,7 @@ private void assertFinalizationFailureCleanup(
229233 when (regionHandler .removeRegionLocation (consensusGroupId , runningNode ))
230234 .thenAnswer (
231235 ignored ->
232- failingState == RemoveRegionPeerState . REMOVE_REGION_LOCATION_CACHE
236+ metadataFailure
233237 ? new TSStatus (TSStatusCode .MIGRATE_REGION_ERROR .getStatusCode ())
234238 : partitionInfo .removeRegionLocation (
235239 new RemoveRegionLocationPlan (consensusGroupId , runningNode )));
@@ -302,53 +306,71 @@ private void assertFinalizationFailureCleanup(
302306 new RemoveDataNodesProcedure (removalTargets , originalStatuses );
303307 long rootId = executor .submitProcedure (root );
304308 ProcedureTestUtil .waitForProcedure (executor , rootId );
305- Assert .assertTrue ("Node removal must finish failure cleanup" , executor .isFinished (rootId ));
306- Assert .assertEquals (ProcedureState .ROLLEDBACK , root .getState ());
307- Assert .assertFalse (root .isSuccess ());
308- RemoveRegionPeerProcedure failedChild =
309+ Assert .assertTrue ("Node removal must finish cleanup" , executor .isFinished (rootId ));
310+ RemoveRegionPeerProcedure removalChild =
309311 store .recorded .values ().stream ()
310312 .filter (RemoveRegionPeerProcedure .class ::isInstance )
311313 .map (RemoveRegionPeerProcedure .class ::cast )
312314 .findFirst ()
313315 .get ();
314- Procedure <?> migrationParent = store .recorded .get (failedChild .getParentProcId ());
316+ Procedure <?> migrationParent = store .recorded .get (removalChild .getParentProcId ());
315317 Assert .assertTrue (migrationParent instanceof RegionMigrateProcedure );
316318 Assert .assertEquals (rootId , migrationParent .getParentProcId ());
317- Assert .assertSame (failedChild .getException (), root .getException ());
318- Assert .assertTrue (root .getException ().getMessage ().contains (failingState .name ()));
319- if (failingState == RemoveRegionPeerState .REMOVE_REGION_LOCATION_CACHE ) {
319+ if (metadataFailure ) {
320+ Assert .assertEquals (ProcedureState .ROLLEDBACK , root .getState ());
321+ Assert .assertFalse (root .isSuccess ());
322+ Assert .assertSame (removalChild .getException (), root .getException ());
323+ Assert .assertTrue (root .getException ().getMessage ().contains (failingState .name ()));
320324 verify (regionHandler , times (4 )).removeRegionLocation (consensusGroupId , runningNode );
321325 verify (regionHandler , never ()).resetPeerListAfterRemoveForIoTConsensusV1 (any (), any ());
322326 } else {
327+ Assert .assertTrue (root .isSuccess ());
328+ Assert .assertTrue (migrationParent .isSuccess ());
329+ Assert .assertTrue (removalChild .isSuccess ());
330+ Assert .assertNull (root .getException ());
331+ Assert .assertNull (migrationParent .getException ());
332+ Assert .assertNull (removalChild .getException ());
323333 verify (regionHandler ).removeRegionLocation (consensusGroupId , runningNode );
324334 verify (regionHandler , times (4 ))
325335 .resetPeerListAfterRemoveForIoTConsensusV1 (consensusGroupId , runningNode );
326336 }
327337
328- // Failed children bypass STOP_DATA_NODE: root rollback must still finalize node statuses.
329- Assert .assertEquals (NodeStatus .Running , currentStatuses .get (runningNode .getDataNodeId ()));
338+ // Metadata failures use rollback cleanup; exhausted peer-list resets use normal
339+ // STOP_DATA_NODE.
340+ if (metadataFailure ) {
341+ Assert .assertEquals (NodeStatus .Running , currentStatuses .get (runningNode .getDataNodeId ()));
342+ }
330343 Assert .assertEquals (NodeStatus .ReadOnly , currentStatuses .get (readOnlyNode .getDataNodeId ()));
331- Assert .assertTrue ( registeredNodes .contains (runningNode .getDataNodeId ()));
344+ Assert .assertEquals ( metadataFailure , registeredNodes .contains (runningNode .getDataNodeId ()));
332345 Assert .assertTrue (registeredNodes .contains (readOnlyNode .getDataNodeId ()));
333346 Assert .assertFalse (registeredNodes .contains (drainedNode .getDataNodeId ()));
334- Assert .assertEquals (Collections .singleton (drainedNode .getDataNodeId ()), stoppedNodes );
347+ Assert .assertEquals (
348+ metadataFailure
349+ ? Collections .singleton (drainedNode .getDataNodeId ())
350+ : Set .of (runningNode .getDataNodeId (), drainedNode .getDataNodeId ()),
351+ stoppedNodes );
335352 Assert .assertEquals (failFirstStatusRestoration ? 2 : 1 , restorationAttempts .get ());
336353 if (failFirstStatusRestoration ) {
337354 // A node-lock wakeup must not bypass the five-second rollback retry timeout.
338355 Assert .assertTrue (
339356 "Status restoration must wait for the rollback retry timeout" ,
340357 restorationRetryDelay .get () >= TimeUnit .SECONDS .toNanos (4 ));
341358 }
342- verify (nodeHandler ).broadcastDataNodeStatusChange (Arrays .asList (runningNode , readOnlyNode ));
359+ verify (nodeHandler )
360+ .broadcastDataNodeStatusChange (
361+ metadataFailure
362+ ? Arrays .asList (runningNode , readOnlyNode )
363+ : Collections .singletonList (readOnlyNode ));
343364
344365 // Cleanup must not undo the completed add peer or a committed removal.
345366 Assert .assertTrue (
346367 partitionManager .isDataNodeContainsRegion (
347368 toDataNodeLocation .getDataNodeId (), consensusGroupId ));
348369 Assert .assertEquals (
349- failingState == RemoveRegionPeerState . REMOVE_REGION_LOCATION_CACHE ,
370+ metadataFailure ,
350371 partitionManager .isDataNodeContainsRegion (runningNode .getDataNodeId (), consensusGroupId ));
351- Assert .assertTrue (
372+ Assert .assertEquals (
373+ metadataFailure ,
352374 partitionManager .isDataNodeContainsRegion (
353375 runningNode .getDataNodeId (), pendingRunningRegion ));
354376 Assert .assertTrue (
0 commit comments