Siyao Meng created HDDS-16906:
---------------------------------

             Summary: Pipeline with a DEAD datanode stays OPEN if SCM 
leadership changes while the node goes STALE and DEAD, and its EC container is 
not repaired
                 Key: HDDS-16906
                 URL: https://issues.apache.org/jira/browse/HDDS-16906
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: MC-7-scrub-open-pipelines-with-dead-nodes.patch, 
TestBugMC7DeadEcPipelineMemberAfterFailovers.java

h3. Mechanism
Every SCM, leader or follower, runs its own node health check and fires 
{{STALE_NODE}} and {{DEAD_NODE}} once per transition. {{StaleNodeHandler}} and 
{{DeadNodeHandler}} close the node's pipelines through 
{{PipelineManager.closePipeline}}, which is a Ratis request: on a follower it 
fails with NotLeaderException. {{StaleNodeHandler}} logs "Could not finalize 
pipeline" at INFO without the exception, {{DeadNodeHandler}} logs the 
exception, and both drop it. No SCM fires the events again for a node that is 
already DEAD, and nothing re-runs the handlers when an SCM becomes leader. The 
only periodic check, {{PipelineManagerImpl.scrubPipelines}} on the leader, 
closes an OPEN pipeline only if a member is not registered (HDDS-7341); a DEAD 
datanode stays registered.

So if the SCM that becomes leader saw the STALE transition while it was a 
follower, and the same happens for the DEAD transition, no SCM closes the 
pipeline. For RATIS/THREE pipelines the remaining datanodes can still trigger 
the close through pipeline actions (argued, not run). A RATIS/ONE pipeline on 
the dead datanode and EC pipelines have no such path, because no other member 
is left or because EC datanodes are not aware of pipelines.

h3. Trigger
# An OPEN EC pipeline, three SCMs, and a datanode of the pipeline stops.
# The datanode goes STALE. A follower sees it first, its handler fails, and 
that follower becomes leader before the old leader's close has been applied.
# The datanode goes DEAD. Again the SCM that becomes leader saw it first as a 
follower.

Each SCM checks node health on its own timer 
({{ozone.scm.heartbeat.thread.interval}}, default 3s), so each leader change 
has to fall in a window of a few seconds around the transition, at both 
transitions (5 minutes apart with the default stale and dead intervals). A 
close that fails on the leader for another reason is lost the same way (argued, 
not run).

h3. Impact
* The EC pipeline stays OPEN with a DEAD member. 
{{SCMBlockProtocolServer.allocateBlock}} keeps returning it to requests that do 
not exclude it. The client then fails over: it adds the pipeline to its exclude 
list and asks for another one (argued from the client code, not run).
* Its OPEN container keeps 4 of 5 replicas. {{ReplicationManager}} does not 
reconstruct OPEN containers, and the container cannot fill, so it stays under 
replicated until the datanode returns, an admin closes the pipeline or 
container, or an SCM that was restarted after the node died becomes leader 
(then the node is unregistered there and the existing scrubber rule closes the 
pipeline).
* In the same run, two RATIS/THREE pipelines and one RATIS/ONE pipeline with 
the dead datanode were also still OPEN on the new leader after a scrubber pass. 
Whether the datanodes close the RATIS/THREE ones later was not observed.

h3. Reproduction
PASS, unmodified source. [^TestBugMC7DeadEcPipelineMemberAfterFailovers.java] 
({{hadoop-ozone/integration-test}}) starts 3 SCMs, 1 OM and 5 datanodes with 
the stale and dead intervals shortened to 6s and 40s, and writes one EC 3-2 key 
with the client. It widens the two windows with the existing 
{{SCMNodeManager.pauseHealthCheck}} test hook on the current leader and moves 
leadership with the admin {{transferLeadership}} RPC: followers B and C see 
STALE, leadership moves A to B, followers A and C see DEAD, leadership moves B 
to C. On C it then runs one scrubber pass and one ReplicationManager pass. 
Output on the pin: the EC pipeline is OPEN with a DEAD member, its container is 
OPEN with 4 replicas, {{allocateBlock}} for EC 3-2 returns that pipeline, and 
the log shows the stale node handler's "Could not finalize pipeline" lines and 
the dead node handler failing with NotLeaderException on the followers. As a 
control, a second member stopped while C is leader gets the pipeline closed. A 
passing test means the defect is present. With the patch it fails at the first 
check, because the scrubber pass closes the pipeline and the container moves to 
CLOSING.

h3. Patch
[^MC-7-scrub-open-pipelines-with-dead-nodes.patch], against 
b0aa6475b7856d2c7f467dd3db12b9a1dd116f63. It also applies to master at 
b9b33851b20.

{{scrubPipelines}} also closes an OPEN pipeline that has a DEAD member, next to 
the existing rule for unregistered members. The scrubber runs only on the 
leader, so the close no longer depends on which SCM saw the transition, and it 
also covers a close that failed on a stable leader. {{closePipeline}} closes 
the pipeline's containers too, so ReplicationManager can repair them once they 
are closed. The scrubber runs only on a leader that is ready and out of safe 
mode. In normal operation the leader's stale or dead node handler has already 
closed every pipeline of a DEAD node, of any type, so the rule finds nothing. 
If such a close was lost, the scrubber does it at most one scrub interval 
(default 5 minutes) after the node is DEAD. It does not act on STALE members. 
Covered by {{testScrubOpenWithDeadNodes}} in the existing 
{{TestPipelineManagerImpl}} (a STALE member keeps the pipeline open, a DEAD 
member closes it), which fails without the change. With it the SCM pipeline 
unit suites, {{TestSCMSafeModeManager}}, {{TestSCMNodeManager}} and 
{{TestDeadNodeHandler}} (359 tests) and {{TestDecommissionAndMaintenance}} (9 
tests) pass, checkstyle is clean.

Related: HDDS-16887. If {{DeadNodeHandler}} deletes the pipeline during a 
scrubber pass, the new close hits the {{PipelineNotFoundException}} that 
HDDS-16887 handles (today it ends the pass, as it already can for the existing 
rules). HDDS-16887 also wraps the loop body in {{scrubPipelines}} in a {{try}} 
block, so the patches conflict textually and whichever lands second needs a 
trivial rebase.

Found by TLA+ model checking and code review of the SCM pipeline lifecycle 
paths under HDDS-15926, on commit b0aa6475b7856d2c7f467dd3db12b9a1dd116f63. 
Checked against HDDS issues and apache/ozone pull requests for duplicates 
before filing. The attached patch is a proposal for review. Generated with 
Specula (Claude Opus 5.5).



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to