[ 
https://issues.apache.org/jira/browse/HDDS-17425?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Siyao Meng updated HDDS-17425:
------------------------------
    Attachment: TestBugMC1OlderDatanodeRegistersDuringFinalize.java

> Datanode on older software that registers while SCM finalizes stays HEALTHY 
> on the finalized SCM and is never asked to register again
> -------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HDDS-17425
>                 URL: https://issues.apache.org/jira/browse/HDDS-17425
>             Project: Apache Ozone
>          Issue Type: Bug
>    Affects Versions: 2.3.0
>            Reporter: Siyao Meng
>            Priority: Minor
>         Attachments: MC-1-reregister-older-datanode-after-finalize.patch, 
> TestBugMC1OlderDatanodeRegistersDuringFinalize.java
>
>
> h3. Mechanism
> {{SCMBlockProtocolServer.finalizeUpgrade}} checks that every HEALTHY datanode 
> runs SCM's software version ({{validateDatanodeVersionsBeforeFinalize}}) and 
> then submits the finalization through Ratis. Applying it 
> ({{FinalizationStateManagerImpl.finalizeUpgrade}}) finalizes 
> {{ScmVersionManager}} and does not re-check registered datanodes. 
> {{SCMNodeManager.register}} runs {{shouldFenceDatanode}} once and shares no 
> lock with that path, and {{shouldFenceDatanode}} rejects older software only 
> once SCM is finalized. So a datanode on older software whose registration is 
> handled after the check and before the apply is accepted.
> After the apply nothing checks a HEALTHY node again. On heartbeat, 
> {{SCMNodeManager.sendFinalizeToDatanodeIfNeeded}} finds the same fence 
> condition but only logs "Invalid datanode in the cluster" and returns, 
> {{SCMDatanodeHeartbeatDispatcher}} asks only unknown nodes to register again, 
> and the health check does not look at versions. The datanode stays HEALTHY 
> and IN_SERVICE, and eligible for pipelines, until it restarts.
> This is the HEALTHY counterpart of HDDS-16798 (a STALE or DEAD older datanode 
> that heartbeats back after finalization). The fix for that issue in 
> apache/ozone#11692 re-checks only nodes that are not HEALTHY, so it does not 
> cover this path.
> h3. Trigger
> # A rolling upgrade from a release that already reports datanode versions 
> ({{DatanodeVersionProto}}). Releases that do not report them are rejected at 
> registration, so this first applies to upgrades from the first release with 
> this code.
> # OM finalizes SCM. The version check passes.
> # A datanode still on the older release starts and its registration reaches 
> the SCM before that SCM has applied the finalization. In SCM HA it has to 
> reach every SCM before each applies it, because an SCM that has applied it 
> rejects the registration and the datanode shuts down. On the leader the 
> window is the Ratis round trip of the finalize request.
> In SCM HA the same state is reachable without a timing window (read from 
> code, not run). The check reads only the leader's node set and skips nodes 
> that are not HEALTHY, so an older datanode that is STALE on the leader but 
> HEALTHY on a follower (cut off from the leader only) passes it and stays 
> HEALTHY on that follower after the follower applies the finalization; if that 
> follower becomes leader, the node is HEALTHY on the leader, where #11692 does 
> not re-check it. Even with #11692, a STALE older datanode whose heartbeat 
> arrives inside the window becomes HEALTHY without registering, because SCM is 
> still not finalized when it checks that heartbeat.
> h3. Impact
> * A datanode that the finalized SCM would reject at registration stays 
> HEALTHY and IN_SERVICE and stays in the set that pipeline placement chooses 
> from, with two ERROR log lines on every heartbeat, until it restarts. No data 
> loss or write failure was shown or is expected from this alone.
> * {{queryUpgradeStatus}} then reports HDDS FINALIZED with the older datanode 
> counted as finalized, which is tracked in HDDS-16799.
> h3. Reproduction
> PASS, deterministic, unmodified source. 
> [^TestBugMC1OlderDatanodeRegistersDuringFinalize.java] 
> ({{hadoop-hdds/server-scm}}) uses the real 
> {{SCMBlockProtocolServer.finalizeUpgrade}}, {{SCMNodeManager}} with its 
> health check thread, {{SCMDatanodeHeartbeatDispatcher}} and a real 
> {{ScmVersionManager}} on a VERSION file at {{ZDU}}. The older datanode runs 
> and reports {{ZDU}} (100), SCM and the upgraded datanode run 
> {{STREAM_PUTBLOCK_WITHOUT_RAFT}} (101). The {{FinalizationManager}} is 
> replaced by a stand in for the Ratis round trip that lets the older 
> datanode's registration complete on another thread and then runs 
> {{ScmVersionManager.finalizeUpgrade}}. Output on the unmodified code: the 
> registration during finalize returns success, the heartbeat after finalize 
> logs "Invalid datanode in the cluster ... Datanode software version = ZDU 
> (100)" and its reply carries no command, the older datanode is IN_SERVICE 
> HEALTHY and in the healthy in service node set, {{queryUpgradeStatus}} 
> reports scm=FINALIZED hdds=FINALIZED with 2 of 2 datanodes finalized, and a 
> registration of a datanode on the same version after finalize is rejected. A 
> passing test means the defect is present. Only the SCM side was run; the 
> datanode reaction to the rejection is read from code. The follower route 
> above was not run.
> h3. Behaviour change after a forced finalize
> {{ozone admin upgrade finalize --force}} skips the datanode version check. 
> Today the older datanodes that are HEALTHY at a forced finalize keep serving 
> until they restart. With the patch every one of them is told to register 
> again on its next heartbeat (30s by default), is rejected and shuts down, all 
> at about the same time (read from code, the datanode side was not run). 
> Containers whose replicas are all on older datanodes are unavailable until 
> they are re-replicated after those nodes are declared dead, and an EC group 
> that loses more datanodes than its parity count is unavailable as well. This 
> follows the upgrade rule that all components run the new software when 
> finalization is given, but {{--force}} exists to bypass that check, so the 
> upgrade owners need to decide whether a forced finalize should remove older 
> datanodes right away. SCM does not record whether a finalization was forced, 
> so the patch cannot treat the two cases differently without new replicated 
> state.
> h3. Suggested fix
> The attached patch adds the re-register on the heartbeat side of the fence 
> for HEALTHY nodes and leaves STALE and DEAD nodes to #11692. An alternative 
> that covers both in one place is a single check in 
> {{SCMDatanodeHeartbeatDispatcher}} for a node of any health, on the software 
> version rules only (older software on a finalized SCM, or newer software than 
> SCM), which also skips {{processHeartbeat}} for the node that is sent away. 
> It must not include the apparent version rule, which an SCM that has not 
> applied the finalization yet sees from a correctly finalized datanode 
> (HDDS-17090).
> h3. Patch
> [^MC-1-reregister-older-datanode-after-finalize.patch], against 
> 679c9ccbee740a0af3ca9f76fc2d4bc6586049eb. It also applies to master at 
> 69d77beff60 and on top of apache/ozone#11692.
> When the heartbeat side of the fence finds a datanode on older software than 
> SCM and SCM has applied the finalization, {{sendFinalizeToDatanodeIfNeeded}} 
> now queues a {{ReregisterCommand}}, which the same heartbeat reply carries. 
> The datanode registers again and {{register}} rejects it with 
> {{errorNodeNotPermitted}}, as it already does for an older datanode that 
> registers after finalization. The rule is extracted into one method that 
> {{shouldFenceDatanode}} also uses.
> The re-register is limited to that rule on purpose. The other rule that 
> fences on heartbeat, a datanode apparent version above SCM's, is what an SCM 
> that has not applied the finalization yet sees from a datanode the leader has 
> already finalized (HDDS-17090), so re-registering on it would shut down 
> correctly finalized datanodes. The older software rule holds only on an SCM 
> that has applied the finalization, and a datanode's software version changes 
> only with a restart. With #11692 applied, STALE and DEAD nodes are handled in 
> the dispatcher before this code runs, so the two changes act on disjoint 
> nodes. Recon is not affected, it overrides {{sendFinalizeToDatanodeIfNeeded}}.
> Covered by {{testOlderDatanodeRegisteredBeforeFinalizeIsAskedToReregister}} 
> in the existing {{TestSCMNodeManager}}: a datanode on {{ZDU}} that registered 
> while SCM was not finalized gets {{reregisterCommand}} on its heartbeat after 
> finalization, and an upgraded datanode gets nothing. 
> {{testProcessVersionReportHigherApparentVersion}} now also asserts that a 
> datanode whose apparent version is above SCM's gets no command, and its 
> comments are narrowed to that case. Without the change the new test fails 
> because the reply is empty. With it {{TestSCMNodeManager}}, 
> {{TestSCMDatanodeHeartbeatDispatcher}}, {{TestSCMBlockProtocolServer}}, 
> {{TestNodeStateManager}} and {{TestDeadNodeHandler}} (109 tests) pass, and on 
> a mini cluster with only this patch applied {{TestScmHAFinalization}} 
> (including {{testFinalizedDatanodesShutDownWithPrefinalizedScm}}) and 
> {{TestOMUpgradeFinalization}} pass. Checkstyle is clean. With the patch the 
> reproduction fails at the assertion on the heartbeat reply, which then 
> carries the re-register command. With the patch, an SCM that has not applied 
> the finalization still sends no command to a finalized datanode that is STALE 
> on it (run on the SCM side).
> Found by TLA+ model checking and code review of the SCM and datanode upgrade 
> finalization paths under HDDS-15926, on commit 
> 679c9ccbee740a0af3ca9f76fc2d4bc6586049eb. 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
(v10.3.26#10030026)

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

Reply via email to